Android 加载菊花图 自定义

本来想找一个ios的菊花加载图旋转一下搞定这个的,但是发现使用图片后由于图片的不对称导致动画时有抖动现象,不能忍,于是搜了下自定义菊花图,额,然后就没有然后了,都不是想要的,于是开始了苦逼的自定义View...

先来张效果图:

菊花图.png

再来张动画效果图:

嗯是的我动起来了.gif

看图说话:
1、12条颜色线,带圆角
2、动画起来后12个颜色跟着变化

需要解决的问题:
1、12个颜色,也太多了吧!!!!我可不想吸这么多次,有毒的!(嗯,我为吸管代言)

于是愉快的决定了只是用两种颜色,即开始颜色和结束颜色,其余使用渐变获取。(额,好吧,我是懒了!)

2、动画

什么,动画是问题吗?一个旋转搞定了!嗯,你大佬,你来,反正我做不来!!
由于旋转动画是转动的,难以实现颜色跟着变化的效果,所以我使用了ValueAnimator,
嗯,是的,你猜对了,既然使用了ValueAnimator,就要调用 invalidate(); 或 postInvalidate(); 来实现重绘。

嗯,看起来还是挺简单的,来试试吧:

先定义一下属性:

    /**
     * 线圆角及宽度
     */
    private int mLineBold;
    /**
     * 线条开始颜色 默认白色
     */
    private int mStartColor = Color.parseColor("#FFFFFF");
    /**
     * 线条结束颜色 默认灰色
     */
    private int mEndColor = Color.parseColor("#9B9B9B");
    /**
     * view的宽度 高度
     */
    private int mWidth;
    /**
     * view的高度
     */
    private int mHeight;
    /**
     * 线条长度
     */
    private int mLineLength;
    /**
     * 线条个数 默认12条
     */
    private int mLineCount = 12;
    /**
     * 背景画笔
     */
    private Paint mBgPaint;
    /**
     * 渐变颜色
     */
    private int[] mColors;
    /**
     * 动画是否已开启
     */
    private boolean isAnimationStart;
    /**
     * 开始index
     */
    private int mStartIndex;
    /**
     * 动画
     */
    private ValueAnimator mValueAnimator;

>>>>>>>>>>>>>>> 然后继续 >>>>>>>>>>>>>>>>>

1.第一步:

自定义属性:
    // 由于考虑到后续可能使用更多数量的线,所以索性加了个线个数的属性
    
    
        
        
        
        
        
        
    

2.第二步,初始化:

  public ChrysanthemumView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        loadAttrs(context, attrs);
        initPaint();
        initColor();
    }
   /**
     * 加载自定义的属性
     */
    private void loadAttrs(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ChrysanthemumView);
        mStartColor = array.getColor(R.styleable.ChrysanthemumView_startColor, mStartColor);
        mEndColor = array.getColor(R.styleable.ChrysanthemumView_endColor, mEndColor);
        mLineCount = array.getInt(R.styleable.ChrysanthemumView_lineCount, mLineCount);
        //TypedArray回收
        array.recycle();
    }
    /**
     * 初始化画笔
     */
    private void initPaint() {
        mBgPaint = new Paint();
        //使得画笔更加圆滑
        mBgPaint.setAntiAlias(true);
        mBgPaint.setStrokeJoin(Paint.Join.ROUND);
        mBgPaint.setStrokeCap(Paint.Cap.ROUND);
    }

3.第三步,解决颜色的问题:

     /**
     * 初始化颜色
     */
    private void initColor() {
        // 渐变色计算类
        ArgbEvaluator argbEvaluator = new ArgbEvaluator();
        // 初始化对应空间
        mColors = new int[mLineCount];
        // 获取对应的线颜色 此处由于是白色起头 黑色结尾所以需要反过来计算 即线的数量到0的数量递减 对应的ValueAnimator 是从0到线的数量-1递增
        for (int i = mLineCount; i > 0; i--) {
            float alpha = (float) i / mLineCount;
            mColors[mLineCount - i] = (int) argbEvaluator.evaluate(alpha, mStartColor, mEndColor);
        }
    }

4.第四步,解决尺寸的问题:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取view的宽度 默认40dp
        mWidth = getViewSize(dp2px(getContext(), 40), widthMeasureSpec);
        // 获取view的高度 默认40dp
        mHeight = getViewSize(dp2px(getContext(), 40), heightMeasureSpec);
        // 使宽高保持一致
        mHeight = mWidth = Math.min(mWidth, mHeight);
        // 获取线的长度
        mLineLength = mWidth / 6;
        // 获取线圆角及宽度
        mLineBold = mWidth / mLineCount;
        // 设置线的圆角及宽度
        mBgPaint.setStrokeWidth(mLineBold);
        setMeasuredDimension(mWidth,mHeight);
    }
  /**
     * 测量模式       表示意思
     * UNSPECIFIED  父容器没有对当前View有任何限制,当前View可以任意取尺寸
     * EXACTLY      当前的尺寸就是当前View应该取的尺寸
     * AT_MOST      当前尺寸是当前View能取的最大尺寸
     *
     * @param defaultSize 默认大小
     * @param measureSpec 包含测量模式和宽高信息
     * @return 返回View的宽高大小
     */
    private int getViewSize(int defaultSize, int measureSpec) {
        int viewSize = defaultSize;
        //获取测量模式
        int mode = MeasureSpec.getMode(measureSpec);
        //获取大小
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                //如果没有指定大小,就设置为默认大小
                viewSize = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                //如果测量模式是最大取值为size
                //我们将大小取最大值,你也可以取其他值
                viewSize = size;
                break;
            case MeasureSpec.EXACTLY:
                //如果是固定的大小,那就不要去改变它
                viewSize = size;
                break;
            default:
        }
        return viewSize;
    }

5.第五步,绘制:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 获取半径
        int r = mWidth / 2;
        // 绘制前先旋转一个角度,使最顶上开始位置颜色与开始颜色匹配
        canvas.rotate(360f / mLineCount, r, r);
        for (int i = 0; i < mLineCount; i++) {
            // 获取颜色下标
            int index = (mStartIndex + i) % mLineCount;
            // 设置颜色
            mBgPaint.setColor(mColors[index]);
            // 绘制线条 mLineBold >> 1 == mLineBold / 2 使居中显示
            canvas.drawLine(r, mLineBold >> 1, r, (mLineBold >> 1) + mLineLength, mBgPaint);
            // 旋转角度
            canvas.rotate(360f / mLineCount, r, r);
        }
    }

6.第六步,动起来!!!!:

  /**
     * 开始动画
     *
     * @param duration 动画时间
     */
    public void startAnimation(int duration) {
        Log.d(TAG, "startAnimation: " + mStartIndex);
        if (mValueAnimator == null) {
            mValueAnimator = ValueAnimator.ofInt(mLineCount, 0);
            mValueAnimator.setDuration(duration);
            mValueAnimator.setTarget(0);
            mValueAnimator.setRepeatCount(-1);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // 此处会回调3次 需要去除后面的两次回调
                    if (mStartIndex != (int) animation.getAnimatedValue()) {
                        mStartIndex = (int) animation.getAnimatedValue();
                        Log.d(TAG, "onAnimationUpdate: " + mStartIndex);
                        invalidate();
                    }
                }
            });
        }
        mValueAnimator.start();
        isAnimationStart = true;
    }

完成了!!!

额,少了东西了。

    /**
     * 结束动画
     */
    public void stopAnimation() {
        Log.d(TAG, "stopAnimation: " + mStartIndex);
        if (mValueAnimator != null) {
            mValueAnimator.cancel();
            isAnimationStart = false;
        }
    }

不行,还不够!

    /**
     * 防止内存溢出 未结束动画并退出页面时,需使用此函数,或手动释放此view
     */
    public void detachView() {
        if (mValueAnimator != null) {
            mValueAnimator.cancel();
            mValueAnimator = null;
            isAnimationStart = false;
        }
    }

所有源码:

    
    
        
        
        
        
        
        
    

/**
 * @author : Kingsley
 * info: 菊花图
 * @date : 2019/4/30 10:12
 */
public class ChrysanthemumView extends View {
    private static final String TAG = "ChrysanthemumView";
    /**
     * 线圆角及宽度
     */
    private int mLineBold;
    /**
     * 线条开始颜色 默认白色
     */
    private int mStartColor = Color.parseColor("#FFFFFF");
    /**
     * 线条结束颜色 默认灰色
     */
    private int mEndColor = Color.parseColor("#9B9B9B");
    /**
     * view的宽度 高度
     */
    private int mWidth;
    /**
     * view的高度
     */
    private int mHeight;
    /**
     * 线条长度
     */
    private int mLineLength;
    /**
     * 线条个数 默认12条
     */
    private int mLineCount = 12;
    /**
     * 背景画笔
     */
    private Paint mBgPaint;
    /**
     * 渐变颜色
     */
    private int[] mColors;
    /**
     * 动画是否已开启
     */
    private boolean isAnimationStart;
    /**
     * 开始index
     */
    private int mStartIndex;
    /**
     * 动画
     */
    private ValueAnimator mValueAnimator;

    public ChrysanthemumView(Context context) {
        this(context, null);
    }

    public ChrysanthemumView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ChrysanthemumView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        loadAttrs(context, attrs);
        initPaint();
        initColor();
    }

    /**
     * 初始化颜色
     */
    private void initColor() {
        // 渐变色计算类
        ArgbEvaluator argbEvaluator = new ArgbEvaluator();
        // 初始化对应空间
        mColors = new int[mLineCount];
        // 获取对应的线颜色 此处由于是白色起头 黑色结尾所以需要反过来计算 即线的数量到0的数量递减 对应的ValueAnimator 是从0到线的数量-1递增
        for (int i = mLineCount; i > 0; i--) {
            float alpha = (float) i / mLineCount;
            mColors[mLineCount - i] = (int) argbEvaluator.evaluate(alpha, mStartColor, mEndColor);
        }
    }

    /**
     * 加载自定义的属性
     */
    private void loadAttrs(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ChrysanthemumView);
        mStartColor = array.getColor(R.styleable.ChrysanthemumView_startColor, mStartColor);
        mEndColor = array.getColor(R.styleable.ChrysanthemumView_endColor, mEndColor);
        mLineCount = array.getInt(R.styleable.ChrysanthemumView_lineCount, mLineCount);
        //TypedArray回收
        array.recycle();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        mBgPaint = new Paint();
        //使得画笔更加圆滑
        mBgPaint.setAntiAlias(true);
        mBgPaint.setStrokeJoin(Paint.Join.ROUND);
        mBgPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取view的宽度 默认40dp
        mWidth = getViewSize(dp2px(getContext(), 40), widthMeasureSpec);
        // 获取view的高度 默认40dp
        mHeight = getViewSize(dp2px(getContext(), 40), heightMeasureSpec);
        // 使宽高保持一致
        mHeight = mWidth = Math.min(mWidth, mHeight);
        // 获取线的长度
        mLineLength = mWidth / 6;
        // 获取线圆角及宽度
        mLineBold = mWidth / mLineCount;
        // 设置线的圆角及宽度
        mBgPaint.setStrokeWidth(mLineBold);
        setMeasuredDimension(mWidth,mHeight);
    }

    /**
     * 测量模式       表示意思
     * UNSPECIFIED  父容器没有对当前View有任何限制,当前View可以任意取尺寸
     * EXACTLY      当前的尺寸就是当前View应该取的尺寸
     * AT_MOST      当前尺寸是当前View能取的最大尺寸
     *
     * @param defaultSize 默认大小
     * @param measureSpec 包含测量模式和宽高信息
     * @return 返回View的宽高大小
     */
    private int getViewSize(int defaultSize, int measureSpec) {
        int viewSize = defaultSize;
        //获取测量模式
        int mode = MeasureSpec.getMode(measureSpec);
        //获取大小
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                //如果没有指定大小,就设置为默认大小
                viewSize = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                //如果测量模式是最大取值为size
                //我们将大小取最大值,你也可以取其他值
                viewSize = size;
                break;
            case MeasureSpec.EXACTLY:
                //如果是固定的大小,那就不要去改变它
                viewSize = size;
                break;
            default:
        }
        return viewSize;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 获取半径
        int r = mWidth / 2;
        // 绘制前先旋转一个角度,使最顶上开始位置颜色与开始颜色匹配
        canvas.rotate(360f / mLineCount, r, r);
        for (int i = 0; i < mLineCount; i++) {
            // 获取颜色下标
            int index = (mStartIndex + i) % mLineCount;
            // 设置颜色
            mBgPaint.setColor(mColors[index]);
            // 绘制线条 mLineBold >> 1 == mLineBold / 2 使居中显示
            canvas.drawLine(r, mLineBold >> 1, r, (mLineBold >> 1) + mLineLength, mBgPaint);
            // 旋转角度
            canvas.rotate(360f / mLineCount, r, r);
        }
    }

    /**
     * 开始动画
     *
     * @param duration 动画时间
     */
    public void startAnimation(int duration) {
        Log.d(TAG, "startAnimation: " + mStartIndex);
        if (mValueAnimator == null) {
            mValueAnimator = ValueAnimator.ofInt(mLineCount, 0);
            mValueAnimator.setDuration(duration);
            mValueAnimator.setTarget(0);
            mValueAnimator.setRepeatCount(-1);
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // 此处会回调3次 需要去除后面的两次回调
                    if (mStartIndex != (int) animation.getAnimatedValue()) {
                        mStartIndex = (int) animation.getAnimatedValue();
                        Log.d(TAG, "onAnimationUpdate: " + mStartIndex);
                        invalidate();
                    }
                }
            });
        }
        mValueAnimator.start();
        isAnimationStart = true;
    }

    /**
     * 开始动画 时间为1800毫秒一次
     */
    public void startAnimation() {
        startAnimation(1800);
    }

    /**
     * 结束动画
     */
    public void stopAnimation() {
        Log.d(TAG, "stopAnimation: " + mStartIndex);
        if (mValueAnimator != null) {
            mValueAnimator.cancel();
            isAnimationStart = false;
        }
    }

    /**
     * 是否在动画中
     *
     * @return 是为 true 否则 false
     */
    public boolean isAnimationStart() {
        return isAnimationStart;
    }

    /**
     * dp转px
     *
     * @param context context
     * @param dpVal   dpVal
     * @return px
     */

    public static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }

    /**
     * 防止内存溢出 未结束动画并退出页面时,需使用此函数,或手动释放此view
     */
    public void detachView() {
        if (mValueAnimator != null) {
            mValueAnimator.cancel();
            mValueAnimator = null;
            isAnimationStart = false;
        }
    }

}

你可能感兴趣的:(Android 加载菊花图 自定义)