Как вписать содержимое GIF-анимации в View и в живые обои?

Фон

У меня есть небольшое приложение с живыми обоями, которое я хочу добавить для поддержки GIF-анимации.

Для этого я нашел различные решения. Существует решение для показа анимации GIF на виде ( здесь), и даже есть решение для показа ее на живых обоях ( здесь).

Тем не менее, для них обоих я не могу найти, как правильно вписать содержимое анимации GIF в имеющееся пространство, что означает любое из следующего:

  1. центральная обрезка - подходит для 100% контейнера (в данном случае экрана), обрезая по бокам (сверху и снизу или слева и справа) при необходимости. Ничего не растягивает. Это означает, что содержимое выглядит нормально, но не все из него могут быть показаны.
  2. Fit-Center - растянуть, чтобы соответствовать ширине / высоте
  3. center-inside - задайте исходный размер, отцентрируйте и растяните по ширине / высоте, только если они слишком велики.

Эта проблема

На самом деле ни один из них не касается ImageView, поэтому я не могу просто использовать атрибут scaleType.

Что я нашел

Существует решение, которое дает вам GifDrawable ( здесь), который вы можете использовать в ImageView, но в некоторых случаях он кажется довольно медленным, и я не могу понять, как использовать его в LiveWallpaper и затем подогнать его.

Основной код обработки GIF LiveWallpaper таков ( здесь):

class GIFWallpaperService : WallpaperService() {
    override fun onCreateEngine(): WallpaperService.Engine {
        val movie = Movie.decodeStream(resources.openRawResource(R.raw.cinemagraphs))
        return GIFWallpaperEngine(movie)
    }

    private inner class GIFWallpaperEngine(private val movie: Movie) : WallpaperService.Engine() {
        private val frameDuration = 20

        private var holder: SurfaceHolder? = null
        private var visible: Boolean = false
        private val handler: Handler = Handler()
        private val drawGIF = Runnable { draw() }

        private fun draw() {
            if (visible) {
                val canvas = holder!!.lockCanvas()
                canvas.save()
                movie.draw(canvas, 0f, 0f)
                canvas.restore()
                holder!!.unlockCanvasAndPost(canvas)
                movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())

                handler.removeCallbacks(drawGIF)
                handler.postDelayed(drawGIF, frameDuration.toLong())
            }
        }

        override fun onVisibilityChanged(visible: Boolean) {
            this.visible = visible
            if (visible)
                handler.post(drawGIF)
            else
                handler.removeCallbacks(drawGIF)
        }

        override fun onDestroy() {
            super.onDestroy()
            handler.removeCallbacks(drawGIF)
        }

        override fun onCreate(surfaceHolder: SurfaceHolder) {
            super.onCreate(surfaceHolder)
            this.holder = surfaceHolder
        }
    }
}

Основной код для обработки GIF-анимации в представлении таков:

class CustomGifView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
    private var gifMovie: Movie? = null
    var movieWidth: Int = 0
    var movieHeight: Int = 0
    var movieDuration: Long = 0
    var mMovieStart: Long = 0

    init {
        isFocusable = true
        val gifInputStream = context.resources.openRawResource(R.raw.test)

        gifMovie = Movie.decodeStream(gifInputStream)
        movieWidth = gifMovie!!.width()
        movieHeight = gifMovie!!.height()
        movieDuration = gifMovie!!.duration().toLong()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        setMeasuredDimension(movieWidth, movieHeight)
    }

    override fun onDraw(canvas: Canvas) {

        val now = android.os.SystemClock.uptimeMillis()
        if (mMovieStart == 0L) {   // first time
            mMovieStart = now
        }
        if (gifMovie != null) {
            var dur = gifMovie!!.duration()
            if (dur == 0) {
                dur = 1000
            }
            val relTime = ((now - mMovieStart) % dur).toInt()
            gifMovie!!.setTime(relTime)
            gifMovie!!.draw(canvas, 0f, 0f)
            invalidate()
        }
    }
}

Вопросы

  1. Учитывая анимацию в формате GIF, как я могу масштабировать ее каждым из указанных выше способов?
  2. Возможно ли иметь одно решение для обоих случаев?
  3. Можно ли использовать библиотеку GifDrawable (или любую другую, пригодную для рисования) для живых обоев вместо класса Movie? Если так, то как?

РЕДАКТИРОВАТЬ: после того, как я нашел способ масштабирования для 2-х видов, мне все еще нужно знать, как масштабировать в соответствии с третьим типом, а также узнать, почему происходит сбой после изменения ориентации, и почему он не всегда сразу показывает предварительный просмотр.,

Я также хотел бы знать, каков наилучший способ показать анимацию GIF здесь, потому что в настоящее время я просто обновляю холст ~60 кадров в секунду (1000/60, ожидающий между каждыми 2 кадрами), без учета того, что находится в файле.

Проект доступен здесь.

3 ответа

Если у вас есть Glide в вашем проекте, вы можете легко загружать GIF-файлы, так как он обеспечивает рисование GIF-файлов для ваших ImageViews и поддерживает множество параметров масштабирования (например, центр или заданная ширина и...).

Glide.with(context)
    .load(imageUrl or resourceId)
    .asGif()
    .fitCenter() //or other scaling options as you like
    .into(imageView);

Хорошо, я думаю, что понял, как масштабировать контент. Не уверен, однако, почему приложение иногда падает при изменении ориентации, и почему приложение иногда не показывает предварительный просмотр.

Проект доступен здесь.

Для центра внутри код:

    private fun draw() {
        if (!isVisible)
            return
        val canvas = holder!!.lockCanvas() ?: return
        canvas.save()
        //center-inside
        val scale = Math.min(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
        val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
        val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
        canvas.translate(x, y)
        canvas.scale(scale, scale)
        movie.draw(canvas, 0f, 0f)
        canvas.restore()
        holder!!.unlockCanvasAndPost(canvas)
        movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
        handler.removeCallbacks(drawGIF)
        handler.postDelayed(drawGIF, frameDuration.toLong())
    }

Для центральной культуры код:

    private fun draw() {
        if (!isVisible)
            return
        val canvas = holder!!.lockCanvas() ?: return
        canvas.save()
        //center crop
        val scale = Math.max(canvas.width.toFloat() / movie.width().toFloat(), canvas.height.toFloat() / movie.height().toFloat());
        val x = (canvas.width.toFloat() / 2f) - (movie.width().toFloat() / 2f) * scale;
        val y = (canvas.height.toFloat() / 2f) - (movie.height().toFloat() / 2f) * scale;
        canvas.translate(x, y)
        canvas.scale(scale, scale)
        movie.draw(canvas, 0f, 0f)
        canvas.restore()
        holder!!.unlockCanvasAndPost(canvas)
        movie.setTime((System.currentTimeMillis() % movie.duration()).toInt())
        handler.removeCallbacks(drawGIF)
        handler.postDelayed(drawGIF, frameDuration.toLong())
    }

для фит-центра я могу использовать это:

                    val canvasWidth = canvas.width.toFloat()
                    val canvasHeight = canvas.height.toFloat()
                    val bitmapWidth = curBitmap.width.toFloat()
                    val bitmapHeight = curBitmap.height.toFloat()
                    val scaleX = canvasWidth / bitmapWidth
                    val scaleY = canvasHeight / bitmapHeight
                    scale = if (scaleX * curBitmap.height > canvas.height) scaleY else scaleX
                    x = (canvasWidth / 2f) - (bitmapWidth / 2f) * scale
                    y = (canvasHeight / 2f) - (bitmapHeight / 2f) * scale
                    ...

Измените ширину и высоту фильма:

Добавьте этот код в метод onDraw перед movie.draw

canvas.scale((float)this.getWidth() / (float)movie.width(),(float)this.getHeight() /      (float)movie.height());

или же

canvas.scale(1.9f, 1.21f); //this changes according to screen size

Масштаб, чтобы заполнить и масштаб, чтобы соответствовать:

На это уже есть хороший ответ:

/questions/43851314/canvas-on-resize-sohranit-maksimalnuyu-shirinu-ili-vyisotu-bez-rastyazheniya-zapolneniya/43851315#43851315

Другие вопросы по тегам