Android自定义view-WaveCircle

Android自定义view(1)
开始写博客,不在懒惰,不在懒惰,不在懒惰,重要的事情说三遍!哈哈,开始自定义view之旅
首先上图:
一个有进度的波浪圆
如何达到该View的效果?波浪如何绘制?
(1)在实现该View之前,先要了解Xfermode
什么是Xfermode:称之为图像混合模式,Android官方给了张图来解释
这里写图片描述
也就是图像的集合操作
其中我们先绘制Dst,然后给Paint设置Xfermode模式,然后在绘制Src,呈现的效果就如上图所示
几个常用的
SrcIn:取两层交集,显示上层的,DstIn与之相反
SrcOut:取上层图像的非交集部分,DstOut与之相反等等
那么来看我们实现该View的思路:
这种效果可以使用PorterDuff.Mode.SRC_IN模式,也就是取两层交集,显示上层的,即会先绘制一个圆, 然后设置Xfermode的模式为SrcIn,然后在绘制一个波浪,使其与圆相交,不断的移动绘制的波浪就可以达到波动的效果,同时将波浪向上移动,一次有水流逐渐上升的效果。
其中使用到的知识有
(1)Xfermode
(2)使用Handler控制动画逻辑
(3)使用贝塞尔曲线实现波浪效果
来看原理图:
这里写图片描述
(1)使用Path的贝塞尔曲线方法rQuadTo构建波浪曲线,代码如下:

        mPath.reset();
        mPath.moveTo(-2 * radius + count, 2 * radius - progress);
        mPath.rQuadTo(radius / 2, A, radius, 0);
        mPath.rQuadTo(radius / 2, -A, radius, 0);
        mPath.rQuadTo(radius / 2, A, radius, 0);
        mPath.rQuadTo(radius / 2, -A, radius, 0);
        mPath.lineTo(2 * radius + count, 2 * radius);
        mPath.lineTo(-2 * radius + count, 2 * radius);
        mPath.close();

可以看到使用Path的rQuadTo方法来构建波浪,最后使其封闭,其中rQuadTo的方法相对于前一个点的步进,类似scrollby。在构建完波浪后,我们通过不停的mPath.moveTo,使波浪超前移动,在与圆相交后显示出来,这里注意,波浪与圆的位置关系,波浪在圆的正下方并且有两个完整的波,这样当我们moveTo到一个波浪时,我们在讲其moveTo到原始的位置,波浪有回到了远点,就源源不断的流下来了。
代码如下,当波浪的步进移动了一个完整的波时,就置为为0,让其回到远点

  if (count >= 2 * radius) {
                    count = 0;
               }

(2)Xfermode的使用:代码如下:

xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制圆
        mPaint.setColor(circleColor);
        canvas.drawCircle(radius, radius, radius, mPaint);
        //设置Xfermode
        mPaint.setXfermode(xfermode);
        resetPath2();
        mPaint.setColor(textColor);
        //画波浪
        canvas.drawPath(mPath, mPaint);
        mPaint.setXfermode(null);
        if (A < radius / 4) {
            textPaint.setAlpha(getAlpha(A));
            canvas.drawText(mTragetValue, radius - mTextWidth / 2, radius + mTextHeight / 2, textPaint);
        }
    }

(3)动画逻辑的判断:
使用Handler的sendEmptyMessage方法,通过在Handler的handleMessage方法中sendEmptyMessage(0);以此达到不停回调的效果,注意Handler的退出条件

完整的代码,有详细注释,希望大家能学到知识,共同进步

public class CustomCircle extends View {

    /**
     * 退出Handler的MSG
     */
    public static final int EXIT_FLAG = 1;
    /**
     * 进行动画的的MSG
     */
    public static final int GOON_FLAG = 0;
    public static final int MAX_PROGRESS = 100;

    /**
     * 圆的半径
     */
    private int radius;
    private Paint mPaint;
    private Path mPath;

    private Paint textPaint;
    /**
     * 图像混合模式
     */
    private Xfermode xfermode;
    /**
     * 颜色
     */
    private int textColor;
    private int circleColor;
    private int progress = 0;

    //y = Asin(bx+c);
    public static final int CIRCLE_DEGREE = 180;
    /**
     * 振幅
     */
    private int A = 50;
    private double[] pis = new double[360];
    /**
     * 要达到的进度
     */
    private String mTragetValue = "";
    /**
     * 波浪前进的step
     */
    private int count = 0;

    /**
     * 真实的进度
     */
    private int mActualHei;

    private int mTextWidth;
    private int mTextHeight;

    private int alpha;


    public LoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.loadingCircle, 0, 0);
        int count = typedArray.getIndexCount();
        int attr = 0;
        for (int i = 0; i < count; i++) {
            attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.loadingCircle_radius:
                    radius = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()));
                    break;
                case R.styleable.loadingCircle_circleColor:
                    circleColor = typedArray.getColor(attr, Color.BLUE);
                    break;
                case R.styleable.loadingCircle_textColor:
                    textColor = typedArray.getColor(attr, Color.BLACK);
                    break;
            }
        }
        typedArray.recycle();
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setTextSize(radius / 3);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setColor(Color.WHITE);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        //设置图像的混合模式
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mPath = new Path();
        //关闭硬件加速,对Xfermode有影响
        setLayerType(LAYER_TYPE_SOFTWARE, null);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制圆
        mPaint.setColor(circleColor);
        canvas.drawCircle(radius, radius, radius, mPaint);
        //设置Xfermode
        mPaint.setXfermode(xfermode);
        resetPath2();
        mPaint.setColor(textColor);
        //画波浪
        canvas.drawPath(mPath, mPaint);
        mPaint.setXfermode(null);
        if (A < radius / 4) {
            textPaint.setAlpha(getAlpha(A));
            canvas.drawText(mTragetValue, radius - mTextWidth / 2, radius + mTextHeight / 2, textPaint);
        }
    }

    //字体淡出的透明度
    private int getAlpha(int a) {
        if (a<1){
            return 255;
        }
        int b = radius / 4;
        int temp = (b - a) * 255 / b;
        return temp;
    }

    //测量字体的宽高
    private void getTextWidthOrHei(String msg) {
        Rect rect = new Rect();
        textPaint.getTextBounds(msg, 0, msg.length(), rect);
        mTextWidth = rect.width();
        mTextHeight = rect.height();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    //测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        MeasureSpec.getMode(widthMeasureSpec);
        MeasureSpec.getSize(widthMeasureSpec);
        //测量,强制view的大小为园的大小
        setMeasuredDimension(2 * radius, 2 * radius);
    }

    /**
     * 构造我们的path,使用的是贝塞尔曲线
     */
    private void resetPath2() {
        if (mActualHei <= 0) {
            return;
        }
        mPath.reset();
        mPath.moveTo(-2 * radius + count, 2 * radius - progress);
        mPath.rQuadTo(radius / 2, A, radius, 0);
        mPath.rQuadTo(radius / 2, -A, radius, 0);
        mPath.rQuadTo(radius / 2, A, radius, 0);
        mPath.rQuadTo(radius / 2, -A, radius, 0);
        mPath.lineTo(2 * radius + count, 2 * radius);
        mPath.lineTo(-2 * radius + count, 2 * radius);
        mPath.close();
    }

    /**
     * 控制动画的逻辑
     */
    private Handler mPathHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //退出信号
            if (msg.what == EXIT_FLAG) {
                return;
            }
            //如果振幅<0,代表动画的结束
            if (A < 0) {
                removeMessages(GOON_FLAG);
                return;
            }
            //在进度达到设定的进度时,振幅开始减小
            if (progress >= mActualHei) {
                A--;
            } else {
            //波浪的步进,通过moveTo达到移动效果
                count += 5;
                //波浪高度的步进
                progress += 1;
                //如果已近移动了一个完整的波浪,那么时时候回到原点了
                if (count >= 2 * radius) {
                    count = 0;
                }
            }
            //更新
            invalidate();
            sendEmptyMessage(0);
        }
    };

    //停止动画
    private void stopAnimator() {
        Message msg = mPathHandler.obtainMessage(EXIT_FLAG);
        mPathHandler.sendMessageAtFrontOfQueue(msg);
        mPathHandler.removeMessages(GOON_FLAG);
        progress = 0;
        count = 0;
        A = radius / 4;
    }

    //开始动画
    public void animatorStart() {
        stopAnimator();
        mPathHandler.sendEmptyMessage(0);
    }

    //设置比例
    public void setTargetValue(int target) {
        mTragetValue = String.valueOf(target);
        mActualHei = (int) (2 * radius * (target * 1.f / MAX_PROGRESS));
        getTextWidthOrHei(mTragetValue);
    }
}

不足之处:
(1)View的测量太粗暴,没能考虑充分考虑父view的意愿。
(2)不支持padding
(3)动画没有时间的设定
以上三点会在后面的博客中一一解决。
最后附上一句”业精于勤而荒于嬉,行成于思而毁于随“勉励自己!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值