美文网首页Android开发Android知识Android开发经验谈
动态加载的Button(学习自定义View从造轮子开始)

动态加载的Button(学习自定义View从造轮子开始)

作者: 秋阳君 | 来源:发表于2017-11-27 10:14 被阅读103次

    前言:学习Android有一段时间了,但是一直都没有突破这个自定义View的关口,因为不知从何下手,网上有很多大神写的博文都很好,但是如果没有真的去实践的话,隔一段时间就会忘记.这两个星期我就下定决心,要突破自定义View这个关口,从造轮子开始.

    一 学习次博文我们能学习到什么:

    • 1我们能大概知道一个自定义View是按什么流程写出来的;

    • 2学习到自定义View中的一些:View大小的确定;绘制一些图形;PathMeasure的应用;Animator的应用;

    • 3我们直接来看预想图和效果图,预测一下我们的绘制过程. 预想图.gif 制作图.gif

    先别喷我~虽然制作出来的配色很丑,但是麻雀虽丑,五脏俱全.下面来看看我们的制作过程.文末的配色会让这个控件的13格瞬间提升.

    二 把轮子拆开看看要什么零件

    我盯着预想图半天,结合自身的水平,写出了自己要怎么去写这个自定义View.(只有把轮子拆开了,我们才知道这个轮子里面藏着什么乾坤)

    • ①实现点击事件,我这里没有用传统的回调监听,而是用了内部方法调用,这样就可以控制View的加载过程.

    • ②在画布上绘制一个长方形(或者带圆角的矩形),然后把长方形变成一个长圆形,为什么是长圆形呢?因为在矩形(圆角矩形)变成圆形过程中,是要有一个过度的,这个过渡期我们就用下面图示的长圆形来代替.

    • ③长圆形缩成圆形. ②和③的操作就如图所示


      2-3.jpg
    • ④圆内实现滚轮加载

    • ⑤加载完成绘制对勾和圆圈(这是我的版本,原版的话就把圆向上平移再绘制对勾就好了,没有④这一步)
      上述的五步过程就可以让我们把这个轮子给造出来.下面就一步一步来实现

    三 开始组装我们自己的轮子

    按照上面的流程,我们一步一步来实现.可以看一下我的代码,里面的注释写得比较清楚.但是还是建议一步一步去实现,我在写的时候没有一步一步去录制gif,所以就没有一步一步来分析了.下次会记得记录一下!!

    public class YoungButton extends View {
        private static String TAG = YoungButton.class.getSimpleName();
    
        private float mWidth, mHeight;
        private String strBtn = "确定";
        private float mCircleRadius;//绘制圆的半径为控件高度的一半
    
    
        //画笔设置
        private Paint mPaint;
        private Paint mTestPaint;
        private Paint mLoadingPaint;
        private int PAINT_COLOR = Color.RED;
        private int PAINT_LOADING_COLOR = Color.WHITE;
        private int TEST_COLOR = Color.WHITE;
        private float PAINT_WIDTH = 5f;
        private float LOADING_WIDTH = 8f;
        private float TEST_WIDTH = 20f;
    
        //画布设置
        private int COLOR_BG = 0xff00ff00;
    
        //形状路径设置
        private PathMeasure mPathMeasure = new PathMeasure();
        private Path mOKPath = new Path();
        private Path mOKDst = new Path();
        private Path mLoadingPath = new Path(); //加载的实时路径
        private Path mLoadingDst = new Path();
        private RectF rectF;
        private float circleAngle = 30f;//按钮角度
    
    
        //动画的状态
        private static final int STATE_NORMAL = 510;//普通状态
        private static final int STATE_B_LOADING = 194;//加载之前
        private static final int STATE_LOADING = 20;//正在加载
        private static final int STATE_COMP = 666;//完成
        private int nowState = STATE_NORMAL;
    
        //动画监听器
        private float currentValue;//加载进度
        private float currentValueRTC;//矩形到长圆形动画变换的进度
        private float currentValueCTC;//长圆形变成圆型动画变换进度
        private int ANIM_DURATION = 500;//动画时间
        private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener;  //动画的变换  0.00-1.00
        private ValueAnimator mRectToCircleAnimator; //矩形变成长圆形动画监听
        private ValueAnimator mCircleToCircleAnimator; //长圆形变成圆形动画监听
        private ValueAnimator mLoadingAnimator;//加载动画
        private ValueAnimator mCompleteAnimator;//完成动画
        private AnimatorSet mAnimatorSet = new AnimatorSet();
    
    
        public YoungButton(Context context) {
            this(context, null);
        }
    
        public YoungButton(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public YoungButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView();
        }
    
        private void initView() {
            mPaint = new Paint();
            mPaint.setColor(PAINT_COLOR);
            mPaint.setAntiAlias(true);//反锯齿
            mPaint.setStrokeWidth(PAINT_WIDTH);
            mPaint.setStyle(Paint.Style.FILL);
    
            mTestPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mTestPaint.setTextSize(30);
            mTestPaint.setColor(TEST_COLOR);
            mTestPaint.setAntiAlias(true);//反锯齿
            mTestPaint.setStrokeWidth(TEST_WIDTH);
            mTestPaint.setTextAlign(Paint.Align.CENTER);
    
            mLoadingPaint = new Paint();
            mLoadingPaint.setColor(PAINT_LOADING_COLOR);
            mLoadingPaint.setAntiAlias(true);//反锯齿
            mLoadingPaint.setStrokeWidth(LOADING_WIDTH);
            mLoadingPaint.setStyle(Paint.Style.STROKE);
        }
    
    
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mHeight = h;
            mWidth = w;
            resetData();
            initPath();
            initListener();
        }
    
        //重新设置值
        private void resetData() {
            mCircleRadius = mHeight / 2;
            currentValueCTC = mWidth;
            mTestPaint.setAlpha(255);
        }
    
        private void initPath() {
            rectF = new RectF();
    
            //加载Path
            RectF loadingRectF = new RectF(-mCircleRadius*17/20, -mCircleRadius*17/20, mCircleRadius*17/20, mCircleRadius*17/20);
            mLoadingDst.addArc(loadingRectF, -90, 359.9999f);//不要设置360°内部会自动优化,测量不能取到需要的数值
    
            mOKDst.moveTo(-mCircleRadius/3,0);
            mOKDst.lineTo(-mCircleRadius/12,mCircleRadius/4);
            mOKDst.lineTo(mCircleRadius/3,-mCircleRadius/4);
    
        }
    
        //动画监听
        private void initListener() {
            //通用的updateListener
            ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentValue = (float) animation.getAnimatedValue();
                    postInvalidate();
                }
            };
    
            //矩形到长圆形动画
            mRectToCircleAnimator = ValueAnimator.ofFloat(circleAngle, mCircleRadius).setDuration(ANIM_DURATION);
            mRectToCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentValueRTC = (float) animation.getAnimatedValue(); //circleAngle-mCircleRadius
                    postInvalidate();//  !!!一定要加上这句提醒更新
                }
            });
    
    
            //长圆形到圆形动画
            mCircleToCircleAnimator = ValueAnimator.ofFloat(mWidth, mHeight).setDuration(ANIM_DURATION);
            mCircleToCircleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentValueCTC = (float) animation.getAnimatedValue();
    
                    int alpha = (int) (255 - 255 * (mWidth - currentValueCTC) / (mWidth - mHeight));//设置文字透明度
                    mTestPaint.setAlpha(alpha);
    
                    postInvalidate();
                }
            });
            mCircleToCircleAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    nowState = STATE_LOADING;
                    mLoadingAnimator.start();
                }
            });
    
            //加载动画
            mLoadingAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIM_DURATION * 4);
            mLoadingAnimator.setRepeatCount(ValueAnimator.INFINITE);
            mLoadingAnimator.addUpdateListener(animatorUpdateListener);
            mLoadingAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    Log.i(TAG, "onAnimationEnd: ");
                    nowState = STATE_COMP;
                    mCompleteAnimator.start();
                }
            });
    
            //完成动画
            mCompleteAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIM_DURATION * 3);
            mCompleteAnimator.addUpdateListener(animatorUpdateListener);
    
            mAnimatorSet.play(mCircleToCircleAnimator)
                    .with(mRectToCircleAnimator);
            mAnimatorSet.setDuration(ANIM_DURATION*2);
        }
    
        /**
         * 不能直接在onDraw中实例RectF,所以在之前实例之后,
         * 在这里来设置Reccanvas.drawRoundRect(rectF,currentValueRTC,currentValueRTC,mPaint);
         * tF的值,从而改变参数的时候可以改变形状
         *
         * @param canvas 画布
         */
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.translate(mWidth / 2, mHeight / 2);
            canvas.drawColor(Color.GREEN);
            switch (nowState) {
                case STATE_NORMAL:
                    rectF.left = -mWidth / 2;
                    rectF.top = -mHeight / 2;
                    rectF.right = mWidth / 2;
                    rectF.bottom = mHeight / 2;
                    canvas.drawRoundRect(rectF, circleAngle, circleAngle, mPaint);
                    if (mLoadingAnimator.isRunning()||mCompleteAnimator.isRunning()){
                        mLoadingAnimator.end();
                        mCompleteAnimator.end();
                    }
                    break;
                case STATE_B_LOADING:
                    beforeLoad(canvas);
                    break;
                case STATE_LOADING:
                    onLoad(canvas);
                    break;
                case STATE_COMP:
                    onComplete(canvas);
                    break;
            }
    
            drawTest(canvas);
        }
    
        //加载完成
        private void onComplete(Canvas canvas) {
            canvas.drawRoundRect(rectF, currentValueRTC, currentValueRTC, mPaint); //背景圆
    
            mLoadingPath.reset();//必须重置
            mOKPath.reset();//必须重置
    
            //点击一下就开始搜索
            mPathMeasure.setPath(mLoadingDst, true);
            float stop = mPathMeasure.getLength()*currentValue;
            mPathMeasure.getSegment(0,stop,mLoadingPath,true);
            canvas.drawPath(mLoadingPath, mLoadingPaint);
    
    
            mPathMeasure.setPath(mOKDst,false);
            mPathMeasure.getSegment(0,mPathMeasure.getLength()*currentValue,mOKPath,true);
            canvas.drawPath(mOKPath,mLoadingPaint);
    
        }
    
        //正在加载
        private void onLoad(Canvas canvas) {
            mLoadingPath.reset();//必须重置
            //点击一下就开始搜索
            mPathMeasure.setPath(mLoadingDst, false);
            float stop = mPathMeasure.getLength()*currentValue;
            float start = (float) (stop - ((0.5 - Math.abs(currentValue - 0.5)) * mCircleRadius * 4));//abs为绝对值  公式为经验值
            mPathMeasure.getSegment(start,stop,mLoadingPath,true);
            canvas.drawRoundRect(rectF, currentValueRTC, currentValueRTC, mPaint);
            canvas.drawPath(mLoadingPath, mLoadingPaint);
        }
    
        //加载之前的绘制
        private void beforeLoad(Canvas canvas) {
            rectF.left = -currentValueCTC / 2;
            rectF.top = -mHeight / 2;
            rectF.right = currentValueCTC / 2;
            rectF.bottom = mHeight / 2;
            canvas.drawRoundRect(rectF, currentValueRTC, currentValueRTC, mPaint);
        }
    
        /**
         * 绘制画布中的文字
         *
         * @param canvas 画布
         */
        private void drawTest(Canvas canvas) {
            Paint.FontMetrics fontMetrics = mTestPaint.getFontMetrics();
            float top = fontMetrics.top;//为基线到字体上边框的距离
            float bottom = fontMetrics.bottom;//为基线到字体下边框的距离
            int baseLine = (int) (rectF.centerY() - (top + bottom) / 2);//基线中心点y坐标
            canvas.drawText(strBtn, rectF.centerX(), baseLine, mTestPaint);
        }
    
        /**
         * 开始加载
         */
        public void startLoading() {
            Log.i(TAG, "startLoading: ");
            nowState = STATE_B_LOADING;
            mAnimatorSet.start();
        }
    
        /**
         * 停止加载
         */
        public void stopLoading() {
            Log.i(TAG, "stopLoading: ");
        }
    
        /**
         * 回到初始状态
         */
        public void backRect() {
            Log.i(TAG, "backRect: ");
            resetData();
            nowState = STATE_NORMAL;
            postInvalidate();
        }
    
        /**
         * 加载成功
         */
        public void completeLoading() {
            Log.i(TAG, "completeLoading: ");
            mLoadingAnimator.end();
        }
    }
    

    参考的资料
    本文主要参考学习的一篇文章:http://www.jianshu.com/p/3eb9777f6ab7
    GcsSloop大神的博文(学习自定义View零件的好地方):http://www.gcssloop.com/customview/CustomViewIndex/
    Canvas里面的绘制的字怎么居中:http://blog.csdn.net/zly921112/article/details/50401976
    PathMeasure实现加载动画: http://blog.csdn.net/eclipsexys/article/details/51992473

    相关文章

      网友评论

        本文标题:动态加载的Button(学习自定义View从造轮子开始)

        本文链接:https://www.haomeiwen.com/subject/tujlbxtx.html