自定义动画效果的Drawable

Drawable具有轻量级的、高效性、复用性强的特点,相对于View来说,自定义Drawable更加简单,它不用与用户进行交互和点击事件处理。 自定义动画Drawable只要继承Drawable并实现以下4个方法,和同时实现Animatable接口里的方法。

kotlin 复制代码
class CircleDrawable : Drawable(), Animatable {
    private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private var mValueAnimator: ValueAnimator? = null
    private var mRadius: Float = 0f //扩散半径

    companion object {
        const val STROKE_WIDTH = 5f
        const val DEFAULT_DURATION = 1200L
        const val DEFAULT_DELAY_TIME = 0L
    }

    //绘制的矩形框
    private var mRect = RectF()

    //动画启动延迟时间
    private var mStartDelay: Long = DEFAULT_DELAY_TIME

    private var mRadiusProperty: Property<CircleDrawable, Int> =
        object : Property<CircleDrawable, Int>(
            Int::class.java, "radius"
        ) {
            override fun set(circleDrawable: CircleDrawable, value: Int) {
                circleDrawable.setRadius(value)
            }

            override fun get(circleDrawable: CircleDrawable): Int {
                return circleDrawable.getRadius().toInt()
            }
        }

    fun getRadius(): Float {
        return mRadius
    }

    fun setRadius(radius: Int) {
        mRadius = radius.toFloat()
    }

    init {
        mPaint.color = Color.WHITE
        mPaint.style = Paint.Style.STROKE
        mPaint.strokeWidth = STROKE_WIDTH
    }

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)
        mRect.set(clipSquare(bounds))
        if (isRunning) {
            stop()
        }
        val maxRadius = ((mRect.right - mRect.left) / 2).toInt()
        val radiusHolder = PropertyValuesHolder.ofInt(mRadiusProperty, 0, maxRadius)
        val alphaHolder = PropertyValuesHolder.ofInt("alpha", 255, 0)

        mValueAnimator = ObjectAnimator.ofPropertyValuesHolder(this, radiusHolder, alphaHolder)
        mValueAnimator?.startDelay = mStartDelay
        mValueAnimator?.duration = DEFAULT_DURATION
        mValueAnimator?.addUpdateListener {
            invalidateSelf()
        }
        mValueAnimator?.repeatMode = ValueAnimator.RESTART
        mValueAnimator?.repeatCount = ValueAnimator.INFINITE
        start()
    }

    /**
     * 裁剪为正方形
     */
    private fun clipSquare(bounds: Rect): Rect {
        val min = min(bounds.width(), bounds.height())
        val cx = bounds.centerX()
        val cy = bounds.centerY()
        val r = min / 2
        return Rect(cx - r, cy - r, cx + r, cy + r)
    }

    override fun draw(canvas: Canvas) {
        canvas.drawCircle(mRect.centerX(), mRect.centerY(), mRadius, mPaint)
    }

    override fun setAlpha(alpha: Int) {
        mPaint.alpha = alpha
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        mPaint.colorFilter = colorFilter
    }

    override fun getOpacity(): Int {
        return PixelFormat.UNKNOWN
    }

    override fun start() {
        mValueAnimator?.start()
    }

    override fun stop() {
        mValueAnimator?.end()
    }

    override fun isRunning(): Boolean {
        return mValueAnimator?.isRunning == true
    }
}

代码流程大概,在构造函数里进行画笔的初始化,重写了onBoundsChanged(bounds:Rect)来获取图形边框参数(这个方法和View#onSizeChaned方法类似),是Drawable的Rect的4个参数值改变时候回调的。当Drawable设置给ImageView时,这个方法就能获取到ImageView的大小,在我们进行动画执行,需要对这个Rect进行裁剪一下(有可能不是正方形),后面还自定义了一个扩散半径的属性mRadiusProperty用来控制绘制圆圈的半径,

kotlin 复制代码
private var mRadiusProperty: Property<CircleDrawable, Int> =
    object : Property<CircleDrawable, Int>(
        Int::class.java, "radius"
    ) {
        override fun set(circleDrawable: CircleDrawable, value: Int) {
            circleDrawable.setRadius(value)
        }

        override fun get(circleDrawable: CircleDrawable): Int {
            return circleDrawable.getRadius().toInt()
        }
    }

fun getRadius(): Float {
    return mRadius
}

fun setRadius(radius: Int) {
    mRadius = radius.toFloat()
}

我们View源码中像alpha,translationX,等

使用也比较简单:

ini 复制代码
<ImageView
    android:id="@+id/image"
    android:layout_width="200dp"
    android:layout_height="100dp"
    android:background="@color/purple_200" />
ini 复制代码
val imageview = findViewById<ImageView>(R.id.image)
imageview.setImageDrawable(CircleDrawable())

这样就可以看到跟随半径变大,透明度变化的圆圈。

相关推荐
Godikov1 小时前
硬件异步事件下的业务一致性:智能柜领取/归还流程的竞态设计
android
Godikov1 小时前
不用 Netty:Android 手写 TCP 客户端实战——自定义协议帧、粘包处理与可靠重连
android
事圆则缓2 小时前
Java 面向对象如何落到 Android View 体系
android
iPad协议个微协议3 小时前
# 微信自动营销系统如何基于 WechatApi 做事件驱动设计
android·微信·企业微信·个人开发·wechatapi·微信个人号开发
AirDroid_cn3 小时前
隐私沙盒:第三方应用读取剪切板内容如何拦截?
android·gitee
2501_915918415 小时前
怎么把 Python 写的 Flet 应用打包成 iOS App 并上架 App Store?
android·ios·小程序·https·uni-app·iphone·webview
邪修king6 小时前
Re:Linux系统篇(二十六):文件系统(二):Ext 文件系统底层详解:从 inode、块组到软硬链接,结合 Windows 讲透文件管理本质
android·java·linux
又见情义7 小时前
RK3568 Android 13 版本号管理实战:基于 ROCKCHIP_BUILD_NUMBER 的定制化方案
android
终端安全笔记7 小时前
iOS 27 强制 TLS 1.2:租赁设备的注册链路会在哪一环断
android·网络·安全·ios·智能手机
mmsx7 小时前
MapLibre 实战 11|用户说"我的地块丢了":一个 sealed class 图层模型,和四个让我重构三版的坑
android·前端·开源