Comment adapter le contenu de l'animation GIF en vue et en live wallpaper?
Contexte
J'ai une petite application live wallpaper, que je veux ajouter un support pour montrer des animations GIF.
Pour cela, j'ai trouvé diverses solutions. Il y a la solution de montrer une animation GIF dans une vue (tiens), et il y a même une solution pour le montrer dans un fond d'écran en direct(tiens).
Cependant, pour les deux, Je ne trouve pas comment adapter le contenu de l'animation GIF bien dans l'espace qu'il a, ce qui signifie l'un des suivant:
- Centre-Culture-s'adapte à 100% du récipient (l'écran dans ce cas), recadrage sur les côtés (haut et bas ou gauche et droite) si nécessaire. Ne pas étirer quoi que ce soit. Cela signifie que le contenu semble bien, mais pas tout cela pourrait être montré.
- fit-centre - étirer pour s'adapter à la largeur/hauteur de
- center-inside-Définir comme taille d'origine, centrée et extensible pour s'adapter à la largeur / hauteur uniquement si elle est trop grande.
Le problème
Aucun de ceux-ci ne concerne réellement ImageView, donc je ne peux pas simplement utiliser l'attribut scaleType.
Ce que j'ai trouvé
, Il existe une solution qui vous donne un GifDrawable (ici), que vous pouvez utiliser dans ImageView, mais il semble que c'est assez lent dans certains cas, et je ne peux pas comprendre comment l'utiliser dans LiveWallpaper et puis l'insérer.
Le code principal de la gestion GIF LiveWallpaper est en tant que tel (tiens) :
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
}
}
}
Le code principal pour gérer l'animation GIF dans une vue est le suivant tel:
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()
}
}
}
Les questions
- étant donné une animation GIF, Comment puis-je l'adapter de chacune des manières ci-dessus?
- Est-il possible d'avoir une solution unique pour les deux cas?
- est-il possible d'utiliser la bibliothèque GifDrawable (ou tout autre drawable pour la question) pour le live wallpaper, au lieu de la classe Movie? Si oui, comment?
EDIT: après avoir trouvé comment mettre à l'échelle pour 2 types, j'ai encore besoin de savoir comment mettre à l'échelle en fonction du troisième type, et aussi vous voulez savoir pourquoi il continue de se bloquer après les changements d'orientation, et pourquoi il ne montre pas toujours l'aperçu tout de suite.
J'aimerais aussi savoir quelle est la meilleure façon de montrer l'animation gif ici, car actuellement je viens de rafraîchir la toile ~60fps (1000/60 en attente entre chaque 2 images), sans tenir compte de ce qui est dans le fichier.
Projet est disponible ici.
3 réponses
Si vous avez Glide dans votre projet, vous pouvez facilement charger des GIF, car il fournit des GIF de dessin à vos ImageViews et prend en charge de nombreuses options de mise à l'échelle (comme center ou une largeur donnée et ...).
Glide.with(context)
.load(imageUrl or resourceId)
.asGif()
.fitCenter() //or other scaling options as you like
.into(imageView);
Modifier la largeur et la hauteur de la vidéo:
Ajoutez ce code dans la méthode onDraw avant le film.dessiner
canvas.scale((float)this.getWidth() / (float)movie.width(),(float)this.getHeight() / (float)movie.height());
Ou
canvas.scale(1.9f, 1.21f); //this changes according to screen size
Échelle à remplir et échelle pour s'Adapter:
Il y a déjà une bonne réponse à ce sujet:
OK, je pense que j'ai comment mettre à l'échelle le contenu. Je ne sais pas pourquoi l'application se bloque toujours lors du changement d'orientation parfois, et pourquoi l'application ne montre pas l'aperçu tout de suite parfois.
Projet est disponible ici.
Pour centre à l'intérieur, le code est:
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())
}
Pour le centre-culture, le code est:
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())
}
Pour fit-center, je peux utiliser ceci:
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
...