自定义Material Design风格炫酷加载进度条

首先要感谢Ricky大神的分享,对于那些想入门自定义view的同学来说,这个例子还是不错的,先看一张效果图:



效果是不是很炫呢,下面咱们就解析下这个动画,这个动画一共分为四部分:

1、小圆旋转:不断改变小圆位置,形成旋转的效果;

2、小圆向中心聚合:不断减小小圆离中心点的距离,形成向中心聚合的效果;

3、圆缩放:不断改变绘制圆的半径,形成缩放的效果;

4、扩散:将画笔的宽度设置的非常宽,绘制空心圆,不断增加空心圆的半径,同时减小画笔的宽度,形成扩散的动画;

大家都知道,绘制view是在onDraw(Canvas canvas)方法中执行的,如果将所有的绘制和逻辑都写在一个方法中,那代码肯定是相当的臃肿,可读性也会变的特别的差,然而不得不说JAVA的多态真是一个很棒的设计,这里设计一个接口,用四个类去实现这个接口,每个类对应一个动画,动态改变变量的指向,以达到绘制不同动画的目的:

</pre><p><pre name="code" class="html">/**
 * 画不同图形的接口
 */
public interface LoadState {
    /**
     * 画不同图形的接口
     */
    public abstract void drawState(Canvas canvas);
}

 

接口的四个实现类:

1、RotationState:旋转动画;

2、MergingState:聚合动画;

    3、CircleStae:圆缩放动画;

    4、ExpandingStae:扩散动画;

这样的话,我们只需在 onDraw(Canvas canvas)中简单的写这几行代码就搞定了:

@Override
    protected void onDraw(Canvas canvas) {
        //如果开始为空,则执行旋转动画
        if(mState == null ){
            mState = new RotationState();
        }
        //这边采用多态的方式,不断改变mState对象类型,执行不同的绘图动作
        mState.drawState(canvas);
        super.onDraw(canvas);
    }

具体代码如下:

package com.longshao.loadanimademo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;

/**
 * 加载动画view
 * 动画共分为四部分:
 * 1、无限循环旋转(加载中)
 * 2、六个圆聚合在一起(加载完成执行的动画)
 * 3、圆缩放
 * 4、扩散动画,显示下面view
 * Created by Administrator on 2015/8/5.
 */
public class LoadView extends View{
    /**
     * 大圆的半径(里面含有好多小圆)
     */
    private float mRotationRadius = 90;
    /**
     * 小圆的半径
     */
    private float mCircleRadius = 18;
    /**
     * 小圆的颜色列表
     */
    private int[] mCircleColors ;

    /**
     *小圆旋转一周需要的时间
     */
    private long mRotationDuration = 1200;
    /**
     *其他动画执行的时间(除了旋转的动画)
     */
    private long mSplashDuration = 500;
    /**
     * view的背景颜色
     */
    private int mBgColor = Color.WHITE;
    /**
     * 当前大圆的半径(动态变化)
     */
    private float mCurrentRatationRadios;
    /**
     * 当前大圆旋转的角度(弧度)
     */
    private float mCurrentRotationAngle = 0f;
    /**
     * 空心圆半径
     */
    private float mHoleRadius =0f;
    /**
     * 绘制圆的画笔
     */
    private Paint mPaint = new Paint();
    /**
     * 绘制背景的画笔
     */
    private Paint mBgPaint = new Paint();

    /**
     *view中心的坐标
     */
    private float mCenterX;
    private float mCenterY;

    /**
     * view对角线的一半
     */
    private float mDiagonalDist;
    /**
     * 保存当前动画状态-->当前在执行那种动画
     */
    private LoadState mState = null;

    /**
     *小圆之间的间隔角度
     */
    private float mRotationAngle = 0f;
    /**
     * 聚合后缩放圆的半径
     */
    private float mScaleCircle ;

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

    public LoadView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /**
     * 初始化
     */
    private void init(Context context) {
        //抗锯齿
        mPaint.setAntiAlias(true);
        mBgPaint.setAntiAlias(true);
        //设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
        mPaint.setDither(true);
        mBgPaint.setDither(true);
        //设置画笔的样式:空心
        mBgPaint.setStyle(Paint.Style.STROKE);
        //设置画笔颜色
        mBgPaint.setColor(mBgColor);
        //旋转的时候大圆的半径不变,为初始化值
        mCurrentRatationRadios = mRotationRadius;
        //聚合后放大圆的初始半径为小圆半径
        mScaleCircle = mCircleRadius;
        //获取颜色数组,小圆的个数和定义颜色的个数相等
        mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors);
        if(mCircleColors.length>0){
            //每个小圆之间的间隔角度(弧度)
            mRotationAngle = (float) (2*Math.PI/mCircleColors.length);
        }
    }

    /**
     *获取view的中点坐标,view对角线长度的一半
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCenterX = w/2f;
        mCenterY = h/2f;
        mDiagonalDist = (float) (Math.sqrt(w*w + h*h)/2f);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //如果开始为空,则执行旋转动画
        if(mState == null ){
            mState = new RotationState();
        }
        //这边采用多态的方式,不断改变mState对象类型,执行不同的绘图动作
        mState.drawState(canvas);
        super.onDraw(canvas);
    }

    /**
     * 数据加载完毕,关闭第一个动画,执行后面三个动画
     */
    public void splashAndDisappear(){
        //取消第一个动画
        if(mState != null && mState instanceof RotationState){
            ((RotationState) mState).cancel();
            post(new Runnable() {
                @Override
                public void run() {
                    mState = new MergingState();
                }
            });
        }
    }

    /**
     * 旋转动画
     */
    private class RotationState implements LoadState{
        private ValueAnimator animator;
        public RotationState() {
            animator = ValueAnimator.ofFloat(0f,(float)(2*Math.PI));
            animator.setDuration(mRotationDuration);
            //设置无限循环
            animator.setRepeatCount(ValueAnimator.INFINITE);
            //匀速旋转
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //获取大圆旋转的当前角度
                    mCurrentRotationAngle = (float) animation.getAnimatedValue();
                    //重绘图像
                    invalidate();
                }
            });
            animator.start();
        }

        @Override
        public void drawState(Canvas canvas) {
            clearCanvas(canvas);
            darwCircle(canvas);
        }

        /**
         * 取消旋转动画
         */
        public void cancel(){
            animator.cancel();
        }
    }

    /**
     * 绘制小圆
     * @param canvas
     */
    private void darwCircle(Canvas canvas) {
        for (int i = 0; i < mCircleColors.length; i++){
            //设置画笔颜色
            mPaint.setColor(mCircleColors[i]);
            //小圆的x坐标
            float cx = (float) (mCurrentRatationRadios*Math.cos(mCurrentRotationAngle + mRotationAngle * i) + mCenterX);
            float cy = (float) (mCurrentRatationRadios*Math.sin(mCurrentRotationAngle+mRotationAngle*i) + mCenterY);
            canvas.drawCircle(cx,cy,mCircleRadius,mPaint);
        }
    }

    /**
     * 清空画布
     * @param canvas
     */
    private void clearCanvas(Canvas canvas) {
        //如果空心圆的半径为0,则清空画布,如果mHoleRadius不为零,说明正在执行扩散动画
        if(mHoleRadius>0f){
            //画笔的宽度
            mBgPaint.setStrokeWidth(mDiagonalDist - mHoleRadius);
            //空心圆的半径
            float radius = mDiagonalDist/2 + mHoleRadius/2;
            canvas.drawCircle(mCenterX,mCenterY,radius,mBgPaint);
        }else{
            canvas.drawColor(mBgColor);
        }
    }


    /**
     * 聚合动画
     */
    private class MergingState implements LoadState{
        private ValueAnimator animator;

        public MergingState() {
            animator = ValueAnimator.ofFloat(0f,mRotationRadius);
            //开始有个弹射效果,输入的参数越大,弹射效果越明显
            animator.setInterpolator(new OvershootInterpolator(6f));
            animator.setDuration(mSplashDuration);
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurrentRatationRadios = (float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            //反向计算
            animator.reverse();
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    post(new Runnable() {
                        @Override
                        public void run() {
                            mState = new CircleStae();
                        }
                    });
                }
            });
        }

        @Override
        public void drawState(Canvas canvas) {
            clearCanvas(canvas);
            darwCircle(canvas);
        }
    }

    /**
     * 圆缩放动画
     */
    private class CircleStae implements LoadState{
        private ValueAnimator animator;
        public CircleStae() {
            animator = ValueAnimator.ofFloat(mCircleRadius,2.5f*mCircleRadius,mCircleRadius);
            animator.setDuration(mSplashDuration);
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //当前圆的圆心
                    mScaleCircle = (float) animation.getAnimatedValue();
                    //重绘图像
                    invalidate();
                }
            });
            animator.start();
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    post(new Runnable() {
                        @Override
                        public void run() {
                            mState = new ExpandingStae();
                        }
                    });
                }
            });
        }

        @Override
        public void drawState(Canvas canvas) {
            clearCanvas(canvas);
            canvas.drawCircle(mCenterX, mCenterY, mScaleCircle,mPaint);
        }
    }


    /**
     * 扩散动画
     */
    private class ExpandingStae implements LoadState{
        private ValueAnimator animator;
        public ExpandingStae() {
            animator = ValueAnimator.ofFloat(0,mDiagonalDist);
            animator.setDuration(mSplashDuration);
            //扩散我这边使用的是匀速,可以换成加速:AccelerateInterpolator
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //获取空心圆的半径
                    mHoleRadius = (float) animation.getAnimatedValue();
                    //重绘图像
                    invalidate();
                }
            });
            animator.start();
        }

        @Override
        public void drawState(Canvas canvas) {
            clearCanvas(canvas);
        }
    }
}


点击资源下载


DiscreteSeekBar是一个Material Design风格的、带有气泡指示器的拖动条。可以自定义样式,非常不错的东西。项目地址:https://github.com/AnderWeb/discreteSeekBar效果图:使用起来也非常简单如何使用xml中创建<org.adw.library.widgets.discreteseekbar.DiscreteSeekBar         android:id="@ id/discrete"         android:layout_width="match_parent"         android:layout_height="wrap_content"         app:dsb_min="1"         app:dsb_max="100"         app:dsb_value="20"         app:dsb_indicatorFormatter="d"         />2. javaDiscreteSeekBar discreteSeekBar = (DiscreteSeekBar) findViewById(R.id.discrete); //设置浮动气泡的值,每次进度值改变时,都会触发 discreteSeekBar.setNumericTransformer(new DiscreteSeekBar.NumericTransformer() {             @Override             public int transform(int value) {                 return value * 10;             }         }); //进度值变化监听 discreteSeekBar.setOnProgressChangeListener(new DiscreteSeekBar.OnProgressChangeListener(){             public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser){             }             public void onStartTrackingTouch(DiscreteSeekBar seekBar){             }             public void onStopTrackingTouch(DiscreteSeekBar seekBar){             }         });属性说明属性名类型使用说明dsb_mininteger|dimension最小进度值dsb_maxinteger|dimension最大进度值dsb_valueinteger|dimension当前进度值dsb_mirrorForRtlboolean由于某些语言,比如阿拉伯语、希伯来语,是“从右往左”显示的,和中文、英语……是相反的。所以该属性的含义是,是否根据本地语言进行反向。dsb_allowTrackClickToDragboolean是否允许通过点击进度条上某一点,来改变进度值。默认是truedsb_progressColorcolor|reference设置进度条及滑块(thumb)的颜色dsb_trackColorcolor|reference设置背景条(track)的颜色dsb_indicatorTextAppearancereference设置气泡(bubble)的样式dsb_indicatorColorcolor|reference设置气泡(bubble)的背景色dsb_indicatorElevationdimension很抱歉,这个属性我也不知道怎么用。related to android:elevation. Will only be used on API level 21 dsb_indicatorFormatterstring|reference设置气泡指示器文字的格式dsb_rippleColorcolor|reference设置触碰点波动的颜色dsb_indicatorPopupEnabledboolean是否显示气泡指示器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值