偶然看到月相变化的oading view,当时觉得挺好看的,正好想到可以用paint.setxfermode
设置 ProterDuffMode SrcOut模式实现。

实现分析

  1. 根据效果图可以看到,月相有两个周期变化,右偏 从0 到满月 ,左偏从 0 到满月
    所以需要通过动画实现 就需要用到ValueAnimator

2.弯月的实现有两种方法

第一种
通过画两个圆 设置后一个圆paint.xfermode 为 SRC_OUT 可以实现两个园混合模式
镂空,既第一个园src 第二个圆 dst ,相交部分留下第一个园部分,同时第二个圆需要
设置透明色

第二种
此种方式通过 drawArc 实现具体实现不表 略复杂

具体实现

  1. 疑难点,直接在onDraw() 用canvas 实现会有问题,出现第二个圆部分为黑色
    解决方式 创建一个canvas 作为新的画布实现 具体看代码
     circleBitmap?.recycle()
        if (measuredWidth != 0 && measuredHeight != 0) {
            circleBitmap = Bitmap.createBitmap(measuredWidth,measuredHeight,Bitmap.Config.ARGB_8888)
            circleCanvas = Canvas(circleBitmap)
        }

2 。动画执行,这里使用属性动画实现[0,1] ,[0,-1] 两段取值
使用同一动画 在onRepaetEnd 回调中 设置动画执行次数 count++
在绘制时根据奇偶 设置 if(count % 2 == 0)fraction else -fraction)

     if (startAnimator == null) {
            startAnimator = ValueAnimator.ofFloat(0f,1f).apply {
                repeatMode = ValueAnimator.RESTART
                repeatCount = 2
                duration = 2000
                addUpdateListener {
                    fraction = it.animatedValue as Float
                    postInvalidateOnAnimation()
                }
                addListener(object : AnimatorListenerAdapter(){
                    override fun onAnimationEnd(animation: Animator?) {
                        eclipseEnd = true
                        fraction = 0f
                        ValueAnimator.ofFloat(0f,4f).apply {
                            duration = 1000
                            repeatCount = 1
                            repeatMode = ValueAnimator.RESTART
                            addUpdateListener({
                                fraction = it.animatedValue as Float
                                postInvalidateOnAnimation()
                            })
                            start()
                        }
                    }

                    override fun onAnimationRepeat(animation: Animator?) {
                        super.onAnimationRepeat(animation)
                        count ++
                        Log.d("EclipseAnimView : " ,"count :${count}")
                    }
                })
                start()
            }

        }

在onAttachWindow 中 开启动画
onDetachWindow 中取消动画
以及做释放操作

  1. 具体绘制看代码 集体实现注意在新的画布中
    实现 需要主要 调用canvas.translate 前后最好 canvas.save() canvsa.restore()
    还有paint 的前后绘制 属性比如颜色的设置 要做
    以及 paint.setxfermode 后绘制完成 paintsetxfermode(null)

view 的画布直接 canvas.drawbitmap(0,0,bitmap,null)

 override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        canvas.drawARGB(255,23,12,53)
        if (eclipseEnd) {
            canvas.save()
            mPaint.color = Color.YELLOW
            mPaint.style = Paint.Style.STROKE
            canvas.translate(mWidth/2f,mHeight/2f)
            val rectf = RectF(-radius,-radius,radius,radius)
            canvas.drawArc(rectf,-90 * (1 + fraction),90f ,false,mPaint)
            if (fraction >= 4) {
                mPaint.style = Paint.Style.FILL
                canvas.drawCircle(rectf.centerX(),rectf.centerY(),radius,mPaint)
            }
            canvas.restore()

        } else {
            circleCanvas?.let {
                it.save()
                it.drawARGB(255,23,12,53)
                mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
                it.drawPaint(mPaint)
                mPaint.xfermode = null
                it.translate(mWidth / 2f , mHeight / 2f)
                mPaint.color = Color.YELLOW
                it.drawCircle(0f,0f,radius,mPaint)
                mPaint.color = Color.TRANSPARENT
                mPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
                Log.d("EclipseAnimView : " ,"fraction :${if(count % 2 == 0)fraction else -fraction}")
                it.drawCircle((if(count % 2 == 0)fraction else -fraction) * radius * 2,0f,radius,mPaint)
                mPaint.xfermode = null
                it.restore()
                canvas.drawBitmap(circleBitmap,0f,0f,null)
            }
        }

github地址