美文网首页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