penfeizhou / APNG4Android

Android animation support for APNG & Animated WebP & Gif & Animated AVIF, High performance
Apache License 2.0
570 stars 75 forks source link

使用ImageSpan时,不会自动播放,有没有不使用定时刷新invalidate的方案 #210

Closed wojiaosuxiaobai closed 6 months ago

wojiaosuxiaobai commented 8 months ago

我看到其他issue里有使用定时invalidate的方案,但是我目前的需求不是很支持使用invalidate,我目前是在一个recyclerview中的item的TextView支持apng的ImageSpan用作处理表情。大佬有没有除去定时invalidate的其他方案。

penfeizhou commented 8 months ago

请看一下FrameAnimationDrawable的API,有手动开始或停止的方法

wojiaosuxiaobai commented 8 months ago
class ExpressionSpan : DynamicDrawableSpan {

    private var mContextRef: WeakReference<Context>? = null
    private var mTextViewRef: WeakReference<TextView>? = null
    private var mResourceId = 0
    private var mPath: String? = null
    private var mSize = 0
    private var mTextSize = 0
    private var mHeight = 0
    private var mWidth = 0
    private var mTop = 0
    private var mDrawable: Drawable? = null
    private var mDrawableRef: WeakReference<Drawable>? = null

    constructor(context: Context, textView: TextView?, resourceId: Int, size: Int, alignment: Int, textSize: Int) : super(alignment) {
        mContextRef = WeakReference<Context>(context)
        mTextViewRef = WeakReference<TextView>(textView)
        mResourceId = resourceId
        mSize = size
        mHeight = mSize
        mWidth = mHeight
        mTextSize = textSize
        mPath = null
    }

    constructor(context: Context, textView: TextView?, path: String, size: Int, alignment: Int, textSize: Int) : super(alignment) {
        mContextRef = WeakReference<Context>(context)
        mTextViewRef = WeakReference<TextView>(textView)
        mPath = path
        mSize = size * 7 / 6
        mHeight = mSize
        mWidth = mHeight
        mTextSize = textSize
        mResourceId = -1
    }

    override fun getDrawable(): Drawable {
        if (mDrawable == null) {
            try {
                val context = mContextRef!!.get()
                if (context != null) {
                    mDrawable = if (mResourceId > 0) {
                        ResourcesCompat.getDrawable(context.resources, mResourceId, null)
                    } else {
                        val file = File(mPath)
                        if (file.exists()) {
                            try {
                                if (APNGParser.isAPNG(file.path)) {
                                    val apngDrawable = APNGDrawable.fromFile(file.absolutePath)
                                    apngDrawable.setAutoPlay(true)
                                    apngDrawable.setVisible(true, true)
                                    apngDrawable.start()
                                    /**
                                     * 通过传入TextView弱引用定时刷新备选方案,该方案有诸多缺点,还需寻找替代方案
                                     */
//                                    CoroutineScope(Dispatchers.IO).launch {
//                                        while (mTextViewRef?.get() != null) {
//                                            withContext(Dispatchers.Main) {
//                                                mTextViewRef?.get()?.invalidate()
//                                            }
//                                            delay(100)
//                                        }
//                                    }
                                    apngDrawable.mutate()
                                } else {
                                    Drawable.createFromPath(file.absolutePath)
                                }
                            } catch (e: Throwable) {
                                ResourcesCompat.getDrawable(context.resources, R.drawable.ic_post_image, null)
                            }
                        } else {
                            ResourcesCompat.getDrawable(context.resources, R.drawable.ic_post_image, null)
                        }
                    }
                }
                mHeight = mSize
                mWidth = mHeight * mDrawable!!.intrinsicWidth / mDrawable!!.intrinsicHeight
                mTop = if (mHeight > mTextSize * 7 / 6) {
                    0
                } else {
                    (mTextSize * 7 / 6 - mHeight) / 2
                }
                mDrawable!!.setBounds(0, mTop, mWidth, mTop + mHeight)
            } catch (e: Exception) {
                HBLogger.e("ExpressionSpan getDrawable onError: ${e.message}")
            }
        }
        if (mDrawable == null) {
            val context = mContextRef!!.get()
            if (context != null) {
                mDrawable = context.resources.getDrawable(R.drawable.ic_post_image)
            }
        }
        return mDrawable!!
    }

    override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
        val b = getCachedDrawable()
        canvas.save()

        val fm = paint.fontMetricsInt
        // int transY = y + fm.ascent/2  - (b.getBounds().bottom + b.getBounds().top) / 2; // 图片中心与文字中心(baseline为底部)对齐
        var transY = y - (b!!.bounds.bottom + b.bounds.top) / 2 + (fm.ascent + fm.descent) / 2 // 图片中心与文字中心对齐
        if (transY + b.bounds.bottom > bottom) {
            transY = bottom - b.bounds.bottom
        }
        if (mVerticalAlignment == ALIGN_BASELINE) {
            transY = top + (bottom - top) / 2 - (b.bounds.bottom - b.bounds.top) / 2 - mTop
        }
        canvas.translate(x, transY.toFloat())
        b.draw(canvas)
        canvas.restore()
//        if (b is APNGDrawable) {
//            b.setAutoPlay(true)
//            b.setVisible(true, true)
//            b.start()
//        }
    }

    private fun getCachedDrawable(): Drawable? {
        if (mDrawableRef == null || mDrawableRef?.get() == null) {
            mDrawableRef = WeakReference(drawable)
        }
        return mDrawableRef?.get()
    }
}

大佬,目前span的代码是这个,我尝试在getDrawable和draw的时候调用setVisible和start,但是并没有动画效果。

wojiaosuxiaobai commented 8 months ago

当我把注释掉的invalidate代码恢复后,动画效果也随之恢复,但是这个方案存在不少问题

penfeizhou commented 8 months ago

autoplay为true时就是根据生命周期自动start和stop, 手动控制autoplay设为false

wojiaosuxiaobai commented 8 months ago

我现在是在一个自定义Span里用的 手动控制autoplay设为false后,手动调用start(),依然没效果 只要我不调用textview的invalidate,就没有效果,我还有找到其他方案

//尝试是用的代码
apngDrawable.setAutoPlay(false)
apngDrawable.setLoopLimit(-1)
apngDrawable.setVisible(true, true)
apngDrawable.start()
penfeizhou commented 8 months ago

看一下textview的源码吧, 这个库本身没有对这个场景做适配

wojiaosuxiaobai commented 8 months ago

好的,谢谢大佬帮忙