...
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
package bums.lunatic.launcher.wall
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
@@ -10,104 +8,27 @@ import android.view.SurfaceHolder
|
||||
import java.io.File
|
||||
|
||||
class MyWallpaperService : WallpaperService() {
|
||||
|
||||
override fun onCreateEngine(): Engine = NativeRenderEngine()
|
||||
|
||||
inner class NativeRenderEngine : Engine() {
|
||||
|
||||
private lateinit var holder: SurfaceHolder
|
||||
private lateinit var handlerThread: HandlerThread
|
||||
private lateinit var handler: Handler
|
||||
private var running = false
|
||||
|
||||
private var nativeRenderer: NativeRenderer? = null
|
||||
private var mediaFiles: List<File> = emptyList()
|
||||
|
||||
private var currentBitmap: Bitmap? = null
|
||||
private var currentBitmapPixels: IntArray? = null
|
||||
private var currentBitmapWidth = 0
|
||||
private var currentBitmapHeight = 0
|
||||
private val frameDelayMs = 16L // 약 60fps
|
||||
|
||||
private var screenWidth = 0
|
||||
private var screenHeight = 0
|
||||
|
||||
private var renderWidth = 0f
|
||||
private var renderHeight = 0f
|
||||
|
||||
private var maxOffset = 0f
|
||||
private var moveXAxis = false
|
||||
private var moveYAxis = false
|
||||
|
||||
private var offsetX = 0f
|
||||
private var offsetY = 0f
|
||||
private var movingForward = true
|
||||
|
||||
private var fadeAlpha = 1f
|
||||
private val fadeDuration = 3000L // 3초
|
||||
private val imageDisplayDuration = 20_000L // 20초
|
||||
|
||||
private var currentMediaStartTime = 0L
|
||||
private val frameDelayMs = 16L
|
||||
private val videoExtensions = listOf("mp4", "mkv", "avi")
|
||||
private val imageExtensions = listOf("jpg", "jpeg", "png", "bmp", "webp")
|
||||
|
||||
private val renderRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (!running) return
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val mediaElapsedMs = now - currentMediaStartTime
|
||||
|
||||
// 이동 애니메이션 계산
|
||||
val travelDistance = maxOffset
|
||||
val speed = if (travelDistance > 0f) (travelDistance * 2) / imageDisplayDuration.toFloat() else 0f
|
||||
val deltaOffset = speed * frameDelayMs
|
||||
|
||||
if (moveXAxis) {
|
||||
if (movingForward) {
|
||||
offsetX += deltaOffset
|
||||
if (offsetX >= maxOffset) offsetX = maxOffset.also { movingForward = false }
|
||||
} else {
|
||||
offsetX -= deltaOffset
|
||||
if (offsetX <= 0f) offsetX = 0f.also { movingForward = true }
|
||||
}
|
||||
offsetY = 0f
|
||||
} else if (moveYAxis) {
|
||||
if (movingForward) {
|
||||
offsetY += deltaOffset
|
||||
if (offsetY >= maxOffset) offsetY = maxOffset.also { movingForward = false }
|
||||
} else {
|
||||
offsetY -= deltaOffset
|
||||
if (offsetY <= 0f) offsetY = 0f.also { movingForward = true }
|
||||
}
|
||||
offsetX = 0f
|
||||
} else {
|
||||
offsetX = 0f
|
||||
offsetY = 0f
|
||||
}
|
||||
|
||||
// 페이드 인/아웃 계산
|
||||
fadeAlpha = if (mediaElapsedMs >= imageDisplayDuration - fadeDuration) {
|
||||
val elapsed = mediaElapsedMs - (imageDisplayDuration - fadeDuration)
|
||||
1f - (elapsed.toFloat() / fadeDuration)
|
||||
} else 1f
|
||||
if (fadeAlpha < 0f) fadeAlpha = 0f
|
||||
|
||||
// 비트맵에서 픽셀 추출
|
||||
currentBitmap?.let { bmp ->
|
||||
if (currentBitmapPixels == null || currentBitmapWidth != bmp.width || currentBitmapHeight != bmp.height) {
|
||||
currentBitmapWidth = bmp.width
|
||||
currentBitmapHeight = bmp.height
|
||||
currentBitmapPixels = IntArray(currentBitmapWidth * currentBitmapHeight)
|
||||
}
|
||||
bmp.getPixels(currentBitmapPixels!!, 0, currentBitmapWidth, 0, 0, currentBitmapWidth, currentBitmapHeight)
|
||||
}
|
||||
|
||||
// 네이티브 렌더 호출
|
||||
currentBitmapPixels?.let { pixels ->
|
||||
nativeRenderer?.nativeRender(pixels, currentBitmapWidth, currentBitmapHeight, offsetX, offsetY, fadeAlpha)
|
||||
}
|
||||
|
||||
// 20초 후 미디어 교체
|
||||
if (mediaElapsedMs >= imageDisplayDuration) {
|
||||
loadNextMedia()
|
||||
}
|
||||
|
||||
nativeRenderer?.nativeRender()
|
||||
handler.postDelayed(this, frameDelayMs)
|
||||
}
|
||||
}
|
||||
@@ -121,19 +42,10 @@ class MyWallpaperService : WallpaperService() {
|
||||
super.onSurfaceCreated(holder)
|
||||
nativeRenderer = NativeRenderer()
|
||||
nativeRenderer?.nativeInit(holder.surface)
|
||||
|
||||
handlerThread = HandlerThread("NativeRenderThread").apply { start() }
|
||||
handler = Handler(handlerThread.looper)
|
||||
running = true
|
||||
|
||||
val wm = getSystemService(WINDOW_SERVICE) as android.view.WindowManager
|
||||
val metrics = android.util.DisplayMetrics()
|
||||
wm.defaultDisplay.getMetrics(metrics)
|
||||
screenWidth = metrics.widthPixels
|
||||
screenHeight = metrics.heightPixels
|
||||
|
||||
loadMediaFiles()
|
||||
|
||||
handler.post(renderRunnable)
|
||||
}
|
||||
|
||||
@@ -145,97 +57,17 @@ class MyWallpaperService : WallpaperService() {
|
||||
super.onSurfaceDestroyed(holder)
|
||||
}
|
||||
|
||||
private var mediaFiles: List<File> = emptyList()
|
||||
private var currentIndex = 0
|
||||
|
||||
private fun loadMediaFiles() {
|
||||
val mediaDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "wallpapers")
|
||||
val extensions = listOf("jpg", "jpeg", "png", "bmp", "webp")
|
||||
val supportedExtensions = videoExtensions + imageExtensions
|
||||
mediaFiles = if (mediaDir.exists() && mediaDir.isDirectory) {
|
||||
mediaDir.listFiles()?.filter { extensions.contains(it.extension.lowercase()) }?.toList() ?: emptyList()
|
||||
mediaDir.listFiles()?.filter { supportedExtensions.contains(it.extension.lowercase()) }?.toList() ?: emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
loadNextMedia()
|
||||
}
|
||||
|
||||
private fun loadNextMedia() {
|
||||
if (mediaFiles.isEmpty()) return
|
||||
|
||||
val file = mediaFiles[currentIndex]
|
||||
currentIndex = (currentIndex + 1) % mediaFiles.size
|
||||
loadBitmapFromFile(file)
|
||||
currentMediaStartTime = System.currentTimeMillis()
|
||||
offsetX = 0f
|
||||
offsetY = 0f
|
||||
movingForward = true
|
||||
fadeAlpha = 1f
|
||||
|
||||
setupScaling(currentBitmapWidth, currentBitmapHeight)
|
||||
}
|
||||
|
||||
private fun loadBitmapFromFile(file: File) {
|
||||
val originalBitmap = BitmapFactory.decodeFile(file.absolutePath)
|
||||
originalBitmap?.let {
|
||||
currentBitmap?.recycle()
|
||||
|
||||
setupScaling(it.width, it.height)
|
||||
|
||||
val scaledWidth = renderWidth.toInt().coerceAtLeast(1)
|
||||
val scaledHeight = renderHeight.toInt().coerceAtLeast(1)
|
||||
|
||||
val scaledBitmap = Bitmap.createScaledBitmap(it, scaledWidth, scaledHeight, true)
|
||||
|
||||
currentBitmap = scaledBitmap
|
||||
currentBitmapWidth = scaledWidth
|
||||
currentBitmapHeight = scaledHeight
|
||||
|
||||
currentBitmapPixels = IntArray(currentBitmapWidth * currentBitmapHeight)
|
||||
scaledBitmap.getPixels(currentBitmapPixels!!, 0, currentBitmapWidth, 0, 0, currentBitmapWidth, currentBitmapHeight)
|
||||
|
||||
if (it != scaledBitmap) {
|
||||
it.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupScaling(mediaWidth: Int, mediaHeight: Int) {
|
||||
if (screenWidth == 0 || screenHeight == 0) return
|
||||
|
||||
val isLandscape = mediaWidth > mediaHeight
|
||||
var scale = if (isLandscape) screenHeight.toFloat() / mediaHeight else screenWidth.toFloat() / mediaWidth
|
||||
|
||||
var targetWidth = mediaWidth * scale
|
||||
var targetHeight = mediaHeight * scale
|
||||
|
||||
if (targetWidth < screenWidth) {
|
||||
val scaleX = screenWidth.toFloat() / mediaWidth
|
||||
if (scaleX > scale) {
|
||||
scale = scaleX
|
||||
targetWidth = mediaWidth * scale
|
||||
targetHeight = mediaHeight * scale
|
||||
}
|
||||
}
|
||||
|
||||
if (targetHeight < screenHeight) {
|
||||
val scaleY = screenHeight.toFloat() / mediaHeight
|
||||
if (scaleY > scale) {
|
||||
scale = scaleY
|
||||
targetWidth = mediaWidth * scale
|
||||
targetHeight = mediaHeight * scale
|
||||
}
|
||||
}
|
||||
|
||||
renderWidth = targetWidth
|
||||
renderHeight = targetHeight
|
||||
|
||||
val diffWidth = renderWidth - screenWidth
|
||||
val diffHeight = renderHeight - screenHeight
|
||||
|
||||
moveXAxis = diffWidth > 1f
|
||||
moveYAxis = diffHeight > 1f
|
||||
|
||||
maxOffset = if (moveXAxis) diffWidth else if (moveYAxis) diffHeight else 0f
|
||||
val mediaPaths = mediaFiles.map { it.absolutePath }
|
||||
nativeRenderer?.nativeSetMediaList(mediaPaths.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,16 @@ package bums.lunatic.launcher.wall
|
||||
import android.view.Surface
|
||||
|
||||
class NativeRenderer {
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("native-renderer")
|
||||
System.loadLibrary("native_renderer")
|
||||
}
|
||||
}
|
||||
|
||||
external fun nativeInit(surface: Surface)
|
||||
external fun nativeRender(
|
||||
pixels: IntArray,
|
||||
width: Int,
|
||||
height: Int,
|
||||
offsetX: Float,
|
||||
offsetY: Float,
|
||||
fadeAlpha: Float
|
||||
)
|
||||
external fun nativeInit(surface: Surface?): Boolean
|
||||
external fun nativeDestroy()
|
||||
external fun nativeRender()
|
||||
external fun nativeSetMediaList(paths: Array<String?>?)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user