在 Kaspresso 中比较两个 VectorDrawables 失败 - Android

Comparing two VectorDrawables fails in Kaspresso - Android

我正在尝试使用 Kaspresso 进行测试,我正在使用以下方法检查视图是否具有特定的可绘制对象:

withId<KImageView>(R.id.criticalErrorImage) {
    hasDrawable(R.drawable.error_graphic)
}

使用 PNG 效果非常好,而比较存储在 imageView 和恢复中的图像与 VectorDrawable 失败。

进行检查的文件是this one

特别是这部分代码:

            var expectedDrawable: Drawable? = drawable ?: getResourceDrawable(resId)?.mutate()

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && expectedDrawable != null) {
                expectedDrawable = DrawableCompat.wrap(expectedDrawable).mutate()
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                tintColorId?.let { tintColorId ->
                    val tintColor = getResourceColor(tintColorId)
                    expectedDrawable?.apply {
                        setTintList(ColorStateList.valueOf(tintColor))
                        setTintMode(PorterDuff.Mode.SRC_IN)
                    }
                }
            }

            if (expectedDrawable == null) {
                return false
            }

            val convertDrawable = (imageView as ImageView).drawable.mutate()
            val bitmap = toBitmap?.invoke(convertDrawable) ?: convertDrawable.toBitmap()

            val otherBitmap = toBitmap?.invoke(expectedDrawable) ?: expectedDrawable.toBitmap()

有趣的是,如果我通过数据绑定设置图像,它会起作用,而如果我以所有其他方式设置它,它就不起作用。不明白为什么。

我在这里创建了一个 POC:https://github.com/filnik/proof_of_concept

特别是,这里是测试:https://github.com/filnik/proof_of_concept/blob/master/app/src/androidTest/java/com/neato/test/POCInstrumentationTest.kt

虽然在这里我动态设置了图像: https://github.com/filnik/proof_of_concept/blob/master/app/src/main/java/com/neato/test/FirstFragment.kt

问题是因为实际上图像被缩放了。所以缩放后的图像与原始图像不同。

为了避免这个问题,我使用了这个“修改过的”KImageView:

package your_package

import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.PorterDuff
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.StateListDrawable
import android.os.Build
import android.view.View
import android.widget.ImageView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.test.espresso.DataInteraction
import androidx.test.espresso.assertion.ViewAssertions
import com.agoda.kakao.common.assertions.BaseAssertions
import com.agoda.kakao.common.builders.ViewBuilder
import com.agoda.kakao.common.utilities.getResourceColor
import com.agoda.kakao.common.utilities.getResourceDrawable
import com.agoda.kakao.common.views.KBaseView
import com.agoda.kakao.image.KImageView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher

class KImageView2 : KBaseView<KImageView>, ImageViewAssertions2 {
    constructor(function: ViewBuilder.() -> Unit) : super(function)
    constructor(parent: Matcher<View>, function: ViewBuilder.() -> Unit) : super(parent, function)
    constructor(parent: DataInteraction, function: ViewBuilder.() -> Unit) : super(parent, function)
}


interface ImageViewAssertions2 : BaseAssertions {
    /**
     * Checks if the view displays given drawable
     *
     * @param resId Drawable resource to be matched
     * @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
     */
    fun hasDrawable(@DrawableRes resId: Int, toBitmap: ((drawable: Drawable) -> Bitmap)? = null) {
        view.check(ViewAssertions.matches(DrawableMatcher2(resId = resId, toBitmap = toBitmap)))
    }
}


class DrawableMatcher2(
    @DrawableRes private val resId: Int = -1,
    @ColorRes private val tintColorId: Int? = null,
    private val toBitmap: ((drawable: Drawable) -> Bitmap)? = null
) : TypeSafeMatcher<View>(View::class.java) {

    override fun describeTo(desc: Description) {
        desc.appendText("with drawable id $resId or provided instance")
    }

    override fun matchesSafely(view: View?): Boolean {
        if (view !is ImageView) {
            return false
        }

        if (resId < 0) {
            return view.drawable == null
        }
        val bitmap = extractFromImageView(view)
        view.setImageResource(resId)
        val otherBitmap = extractFromImageView(view)

        return bitmap?.sameAs(otherBitmap) ?: false
    }

    private fun extractFromImageView(view: View?): Bitmap? {
        return view?.let { imageView ->
            var expectedDrawable: Drawable? = getResourceDrawable(resId)?.mutate()

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && expectedDrawable != null) {
                expectedDrawable = DrawableCompat.wrap(expectedDrawable).mutate()
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                tintColorId?.let { tintColorId ->
                    val tintColor = getResourceColor(tintColorId)
                    expectedDrawable?.apply {
                        setTintList(ColorStateList.valueOf(tintColor))
                        setTintMode(PorterDuff.Mode.SRC_IN)
                    }
                }
            }

            if (expectedDrawable == null) {
                return null
            }

            val convertDrawable = (imageView as ImageView).drawable.mutate()
            toBitmap?.invoke(convertDrawable) ?: convertDrawable.toBitmap()
        }
    }
}

internal fun Drawable.toBitmap(): Bitmap {
    if (this is BitmapDrawable && this.bitmap != null) {
        return this.bitmap
    }

    if (this is StateListDrawable && this.getCurrent() is BitmapDrawable) {
        val bitmapDrawable = this.getCurrent() as BitmapDrawable
        if (bitmapDrawable.bitmap != null) {
            return bitmapDrawable.bitmap
        }
    }

    val bitmap = if (this.intrinsicWidth <= 0 || this.intrinsicHeight <= 0) {
        Bitmap.createBitmap(
            1,
            1,
            Bitmap.Config.ARGB_8888
        ) // Single color bitmap will be created of 1x1 pixel
    } else {
        Bitmap.createBitmap(this.intrinsicWidth, this.intrinsicHeight, Bitmap.Config.ARGB_8888)
    }

    val canvas = Canvas(bitmap)
    this.setBounds(0, 0, canvas.width, canvas.height)
    this.draw(canvas)
    return bitmap
}

这可能不是最干净的解决方案,但它确实有效。如果有人能提供更好的解决方案那就太好了:-)