Как вписать содержимое GIF-анимации в View и в живые обои?
Фон
У меня есть небольшое приложение с живыми обоями, которое я хочу добавить для поддержки GIF-анимации.
Для этого я нашел различные решения. Существует решение для показа анимации GIF на виде ( здесь), и даже есть решение для показа ее на живых обоях ( здесь).
Тем не менее, для них обоих я не могу найти, как правильно вписать содержимое анимации GIF в имеющееся пространство, что означает любое из следующего:
- центральная обрезка - подходит для 100% контейнера (в данном случае экрана), обрезая по бокам (сверху и снизу или слева и справа) при необходимости. Ничего не растягивает. Это означает, что содержимое выглядит нормально, но не все из него могут быть показаны.
- Fit-Center - растянуть, чтобы соответствовать ширине / высоте
- 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()
}
}
}
Вопросы
- Учитывая анимацию в формате GIF, как я могу масштабировать ее каждым из указанных выше способов?
- Возможно ли иметь одно решение для обоих случаев?
- Можно ли использовать библиотеку 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
Масштаб, чтобы заполнить и масштаб, чтобы соответствовать:
На это уже есть хороший ответ: