Android自定义View--一个简单的loading效果

先看效果图:默认效果:
默认效果
可以自定义修改一些属性:
这个gif由于录制的狮虎帧数太小,所以效果不怎么好

一.实现思路:
思路其实很简单:相当于是内外层两个圆弧在不停地旋转,将内外两个圆弧绘制出来,通过改变绘制起点位置的角度,模拟动画效果。做一个定时器,不断改变圆弧的绘制起点,然后重新绘制整个View,就可以模拟出动画效果。在定时器结束的时候,调用自己重新开始,动画就可以一直执行下去。

二.实现代码:

1.首先需要继承View:
public class CustomLoadingView extends View
2.设置一些默认的属性
    //定义画笔
    private Paint paint;
    //倒计时的时间
    private int countDownTimeTotal = 1500;//总的倒计时默认是1.5秒
    //view默认的长度和高度
    private int defaultWidth = 200;
    private int defaultHeight = 300;
    //内外层圆弧的颜色,默认都为黑色
    private int outsideArcColor = Color.BLACK;
    private int insideArcColor = Color.BLACK;
    //内外层圆弧的宽度
    private float outsideArcWidth = 15f;
    private float insideArcWidth = 15f;
    //内外层圆弧转过的角度,
    private float outsideArcAngle = 330f;
    private float insideArcAngle = 60f;
    //默认起始角度
    private float defaultStartAngle = 105;
    //计算改变之后的起始角度
    private float startAngle = defaultStartAngle;
3.为了增强扩展性,可以通过xml文件定义一些自己需要的属性,内外层圆弧的颜色,角度,起始角度,动画执行一次的时间都
可以自定义,因此需要在构造方法中获取自己定义的属性:
public CustomLoadingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //取出自定义的属性并赋值
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.CustomLoadingView);
        countDownTimeTotal = array.getInteger(R.styleable.CustomLoadingView_round_once_time,1500);

        outsideArcColor = array.getColor(R.styleable.CustomLoadingView_round_outside_color,Color.BLACK);
        insideArcColor = array.getColor(R.styleable.CustomLoadingView_round_inside_color,Color.BLACK);

        outsideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_outside_angle,300f);
        insideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_inside_angle,60f);

        defaultStartAngle = array.getFloat(R.styleable.CustomLoadingView_round_start_angle,105);

        array.recycle();

        init();
    }
4.既然是自定义的属性,也就需要对这些属性进行声明,声明放在attr.xml文件中:
 <!--自定义等待view中的属性-->
    <declare-styleable name="CustomLoadingView">
        <!--loading动画执行一次所需的时间-->
        <attr name="round_once_time" format="integer"></attr>
        <!--外层圆弧的颜色-->
        <attr name="round_outside_color" format="color"></attr>
        <!--内层圆弧的颜色-->
        <attr name="round_inside_color" format="color"></attr>
        <!--内层圆弧的角度-->
        <attr name="round_inside_angle" format="float"></attr>
        <!--外层圆弧的角度默认为330度-->
        <attr name="round_outside_angle" format="float"></attr>
        <!--圆弧旋转的起始角度-->
        <attr name="round_start_angle" format="float"></attr>
    </declare-styleable>
5.定义一个倒计时,通过倒计时来对整个View进行重绘:
 private CountDownTimer countDownTimer;
6.在init()方法中执行实际的倒计时操作,init()方法可以在任意位置调用
 private void init() {
        if (countDownTimer != null) {
            countDownTimer.cancel();
            countDownTimer.onFinish();
            countDownTimer = null;
        }

        countDownTimer = new CountDownTimer(countDownTimeTotal, 10) {
            @Override
            public void onTick(long millisUntilFinished) {
                //计算当前剩余时间的比例
                float radio = (float) millisUntilFinished / (float) countDownTimeTotal;

                float addAngle = 360 - 360 * radio;
                //根据比较改变开始位置的角度
                startAngle = defaultStartAngle;
                startAngle = defaultStartAngle + addAngle;

                invalidate();
            }

            @Override
            public void onFinish() {
                if(countDownTimer != null){
                    countDownTimer.start();
                }
            }
        };

        countDownTimer.start();
    }
7.最关键的是onDraw方法,在这里对两个圆环进行绘制,这里需要明白圆弧正反方向的绘制,否则绘制出来的是同一个方向旋转
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint = new Paint();
        //首先绘制最外层的圆
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(outsideArcWidth);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setColor(outsideArcColor);
        Path path = new Path();
        path.addArc(10, 10, defaultWidth - 10, defaultHeight - 10, startAngle, outsideArcAngle);
        canvas.drawPath(path, paint);
        //绘制内层的圆
        paint.reset();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(insideArcWidth);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setColor(insideArcColor);

        canvas.drawArc(30 + outsideArcWidth, 30 + outsideArcWidth, defaultWidth - (30 + outsideArcWidth), defaultHeight - (30 + outsideArcWidth), (360 - startAngle), -insideArcAngle,false,paint);
    }
8.到这里,整个view就绘制完成,可以直接在代码中使用,同时,也可以堆属性提供getter/setter方法,在外部(activity或者fragment中调用),同时刷新init()方法就可以了

9.全部代码如下:
package com.fanjuan.project.smartcarsrecignition;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Build;
import android.os.CountDownTimer;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/*
 * 自定义一个等待的动画
 * */
public class CustomLoadingView extends View {
    //定义画笔
    private Paint paint;
    //倒计时的时间
    private int countDownTimeTotal = 1500;//总的倒计时默认是1.5秒
    //view默认的长度和高度
    private int defaultWidth = 200;
    private int defaultHeight = 300;
    //内外层圆弧的颜色,默认都为黑色
    private int outsideArcColor = Color.BLACK;
    private int insideArcColor = Color.BLACK;
    //内外层圆弧的宽度
    private float outsideArcWidth = 15f;
    private float insideArcWidth = 15f;
    //内外层圆弧转过的角度,
    private float outsideArcAngle = 330f;
    private float insideArcAngle = 60f;
    //默认起始角度
    private float defaultStartAngle = 105;
    //计算改变之后的起始角度
    private float startAngle = defaultStartAngle;

    public CustomLoadingView(Context context) {
        this(context,null);

    }

    public CustomLoadingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //取出自定义的属性并赋值
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.CustomLoadingView);
        countDownTimeTotal = array.getInteger(R.styleable.CustomLoadingView_round_once_time,1500);

        outsideArcColor = array.getColor(R.styleable.CustomLoadingView_round_outside_color,Color.BLACK);
        insideArcColor = array.getColor(R.styleable.CustomLoadingView_round_inside_color,Color.BLACK);

        outsideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_outside_angle,300f);
        insideArcAngle = array.getFloat(R.styleable.CustomLoadingView_round_inside_angle,60f);

        defaultStartAngle = array.getFloat(R.styleable.CustomLoadingView_round_start_angle,105);

        array.recycle();

        init();
    }

    public CustomLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs);

    }

    //定义一个倒计时,用于执行动画效果,每隔10毫秒执行一次
    private CountDownTimer countDownTimer;

    private void init() {
        if (countDownTimer != null) {
            countDownTimer.cancel();
            countDownTimer.onFinish();
            countDownTimer = null;
        }

        countDownTimer = new CountDownTimer(countDownTimeTotal, 10) {
            @Override
            public void onTick(long millisUntilFinished) {
                //计算当前剩余时间的比例
                float radio = (float) millisUntilFinished / (float) countDownTimeTotal;

                float addAngle = 360 - 360 * radio;
                //根据比较改变开始位置的角度
                startAngle = defaultStartAngle;
                startAngle = defaultStartAngle + addAngle;

                invalidate();
            }

            @Override
            public void onFinish() {
                if(countDownTimer != null){
                    countDownTimer.start();
                }
            }
        };

        countDownTimer.start();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint = new Paint();
        //首先绘制最外层的圆
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(outsideArcWidth);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setColor(outsideArcColor);
        Path path = new Path();
        path.addArc(10, 10, defaultWidth - 10, defaultHeight - 10, startAngle, outsideArcAngle);
        canvas.drawPath(path, paint);
        //绘制内层的圆
        paint.reset();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(insideArcWidth);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setColor(insideArcColor);

        canvas.drawArc(30 + outsideArcWidth, 30 + outsideArcWidth, defaultWidth - (30 + outsideArcWidth), defaultHeight - (30 + outsideArcWidth), (360 - startAngle), -insideArcAngle,false,paint);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        defaultWidth = dealReadSize(widthMeasureSpec, defaultWidth);
        defaultHeight = dealReadSize(heightMeasureSpec, defaultHeight);
        //这两个哪个小就用哪个
        if (defaultHeight > defaultWidth) {
            defaultHeight = defaultWidth;
        } else {
            defaultWidth = defaultHeight;
        }
        setMeasuredDimension(defaultWidth, defaultHeight);
    }

    //根据不同的model处理不同的尺寸
    private int dealReadSize(int measureSpec, int defaultSize) {
        int result = 0;
        int model = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        switch (model) {
            case MeasureSpec.UNSPECIFIED:
                //不限制,使用默认的尺寸
                result = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                //上限
                result = Math.min(defaultSize, size);
                break;
            case MeasureSpec.EXACTLY:
                result = size;
                break;
        }

        return result;
    }

    //设置结束
    public void setFinish(){
        if(countDownTimer != null){
            countDownTimer.onFinish();
            countDownTimer.cancel();
            countDownTimer = null;
        }
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值