Android, 自定义View实现 加载进度动画,仿app“王者营地“

废话在前:

昨天打王者,队友实在是太坑了,硬是卡在了星耀3。然后关闭游戏,打开王者荣耀助手,(王者营地),结果网络波动,界面一直在加载状态中,我就在想这个加载动画怎么能实现呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
两个小正方形左右移动,一个中间大正方形大小随着小正方形靠近而变大,疏远而变小,”加载中“后面的点变化1,2,3,4。

废话不多说,直接上实现效果
在这里插入图片描述
实现效果和“王者营地”的差不多,小正方形运动速度可能有些差异,但大抵一致。

实现原理也很简单, 自定义view继承 TextView,因为有文字,所以继承Text View会好些。

5个画笔, 分别绘制左正方形,右正方形,中间大正方形,文字,点。
绘制左右正方形时,
画布先旋转(remote)45度,再移动(translate)
具体代码如下:
自定义View 的 属性注册 attrs.xml
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>
   <!--王者营地加载-->
    <declare-styleable name="WangZheLoad">
        <attr name="leftSmallRectColor" format="color"></attr>
        <attr name="rightSmallRectColor" format="color"></attr>
        <attr name="smallRectSize" format="dimension"></attr>
        <attr name="bigRectSize" format="dimension"></attr>
        <attr name="bigRectColor" format="color"></attr>
    </declare-styleable>
</resources>

自定义View WangZheLoad.class

public class WangZheLoad extends androidx.appcompat.widget.AppCompatTextView {
    
    
    //自定义控件的属性说明
    private int mLeftSmallRectColor = Color.GRAY;//左侧小正方形默认颜色
    private int mRightSmallRectColor = Color.GRAY;//右侧小正方形默认颜色
    private int mBigRectColor = Color.GRAY;//中间大正方形默认颜色
    private int mtextcolor = Color.GRAY;//“加载中”文字默认颜色
    private int mPointColor = Color.BLACK;//"..."点颜色
    private int mSmallRectSize = 7;//小正方形默认大小
    private int mBigRectSize = 15;//中间大正方形默认大小
    private float progress = 0;//进度,根据不同的进度,左右两小正方形左右移动,以及中间大小的变换
    private float pp = 0.4f;//命名可能不规范,这个是大正方形边长大小变换率
    //画笔声明
    private Paint mLeftPaint;//左侧小正方形画笔
    private Paint mRightPaint;//右侧小正方形画笔
    private Paint mBigPaint;//中间大正方形画笔
    private Paint mTextPaint;//“加载中”文字画笔,不能写死了,有些地方可能不叫“加载中”
    private Paint mPointPaint;//绘制点,这个点就是加载中后面闪动的点,当然你也可以把“加载中”和点在一个画笔绘制
                              //我分开来主要是怕需求变化,要是老板要求点要加粗、颜色为黑色,文字保持灰色,你就麻烦呀
    //三个基本构造函数,不多说
    public WangZheLoad(Context context){
    
    
        this(context,null);
    }
    public WangZheLoad(Context context, AttributeSet attrs){
    
    
        this(context,attrs,0);
    }
    public WangZheLoad(Context context,AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
        //初始化
        init(context,attrs);
    }
    //初始化方法
    public void init(Context context,AttributeSet attrs){
    
    
        //获得自定义属性,实例化,如果控件使用者未给明,就用默认的。
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.WangZheLoad);
        mLeftSmallRectColor = array.getColor(R.styleable.WangZheLoad_leftSmallRectColor,mLeftSmallRectColor);
        mRightSmallRectColor = array.getColor(R.styleable.WangZheLoad_rightSmallRectColor,mRightSmallRectColor);
        mBigRectColor  = array.getColor(R.styleable.WangZheLoad_bigRectColor,mBigRectColor);
        mSmallRectSize = array.getDimensionPixelSize(R.styleable.WangZheLoad_smallRectSize,sp2px(mSmallRectSize));
        mBigRectSize = array.getDimensionPixelSize(R.styleable.WangZheLoad_bigRectSize,sp2px(mBigRectSize));
        //画笔初始化,对于抗锯齿,防抖动,颜色等封装成一个函数
        mLeftPaint = new Paint();
        initPaint(mLeftPaint,mLeftSmallRectColor);
        mRightPaint = new Paint();
        initPaint(mRightPaint,mRightSmallRectColor);
        mBigPaint = new Paint();
        initPaint(mBigPaint,mBigRectColor);
        mTextPaint = new Paint();
        mTextPaint.setTextSize(getTextSize());
        initPaint(mTextPaint,mtextcolor);
        mPointPaint = new Paint();
        mPointPaint.setTextSize(getTextSize());
        mPointPaint.setFakeBoldText(true);
        initPaint(mPointPaint,mPointColor);
    }
    //画笔初始化
    public void initPaint(Paint c_paint,int c_color){
    
    
        c_paint.setAntiAlias(true);
        c_paint.setColor(c_color);
        c_paint.setDither(true);
    }
    //sp  to  px  不多说,就是 sp转化为 px,要是控件使用者用dp怎么办,那就让他自己添加个dp转sp的函数,就你事多
    public int sp2px(int sp){
    
    
         return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, this.getResources().getDisplayMetrics());
    }
    //测量函数
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        //获得 控件总宽度和总高度的模式
        int widthmode = MeasureSpec.getMode(widthMeasureSpec);
        int heightmode = MeasureSpec.getMode(heightMeasureSpec);
        //控件要是填 精确的值,就拿精确的值
        int widthsize = MeasureSpec.getSize(widthMeasureSpec);
        int heightsize = MeasureSpec.getSize(heightMeasureSpec);
        //要是填的是 wrap_content,就默认宽度70, 高度70
        if (widthmode==MeasureSpec.AT_MOST||heightmode==MeasureSpec.AT_MOST){
    
    
            widthsize = sp2px(70)+getPaddingLeft()+getPaddingRight();
            heightsize = sp2px(70)+getPaddingTop()+getPaddingBottom();
        }
        setMeasuredDimension(widthsize,heightsize);
    }
    // 绘制函数
    @Override
    protected void onDraw(Canvas canvas) {
    
    
        //画笔通过方法调用修改格式后,需要再次赋值
        initPaint(mLeftPaint,mLeftSmallRectColor);
        initPaint(mRightPaint,mRightSmallRectColor);
        initPaint(mBigPaint,mBigRectColor);
        mTextPaint.setTextSize(getTextSize());
        initPaint(mTextPaint,mtextcolor);
        mPointPaint.setTextSize(getTextSize());
        mPointPaint.setFakeBoldText(true);
        initPaint(mPointPaint,mPointColor);
        /*  move, 左右两正方形移动的距离, 从 最左边移动到最中间
         *  这个值的算法很简单,你画个图就明白了, 0.7是 根号2的一般,由于根号的值存在小数在取值乘除加减会有误差
         *  后面的0.73本来应该是0.7,但是误差导致小正方形移动到两边时距离不够,与默认值有差值,所以会有“闪现”的问题
         *  所以我试着调节,消除闪现,0.73是比较完美的,肉眼看不出问题,建议计算时使用math库
         */
        float move = (float)((getWidth()/2 -0.7*mSmallRectSize-getPaddingLeft())*0.73);

        canvas.save();//保存画布
        //画布旋转,第一个是角度, 第二个是旋转中心点的x坐标,第三个是旋转中心的 y坐标
        canvas.rotate(45,getPaddingLeft()+mSmallRectSize/2,getHeight()/2);
        //画布平移,x方向平移距离,y方向平移距离,注意小正方形是先顺时针旋转了45度,在进行平移,顺序不能反了
        //小正方形的移动距离与 progress有关的
        canvas.translate(move *progress,- move*progress);
        //这样就可以绘制左边小正方形了
        canvas.drawRect(getPaddingLeft(),getHeight()/2-mSmallRectSize/2,mSmallRectSize+getPaddingLeft(),getHeight()/2+mSmallRectSize/2,mLeftPaint);
        canvas.restore();//恢复画布

        canvas.save();//中间大的正方形绘制,大正方形会随着小正方形的靠近而变大,疏远而变小
        //大正方形的旋转,这个就简单多了,因为大正方形位置不变
        canvas.rotate(45,getWidth()/2,getHeight()/2);
        //要是左边小正方形从左边运动到中间阶段,大正方形的边长大小为基础的0.4*最大边长加上另外随小正方形运动距离改变而变动的0.6
        if (progress>=0&&progress<1) {
    
    
            pp = (float) 0.6*progress+0.4f;
        } else if(progress>=1&&progress<2){
    
    //要是左边小正方形从中间运动到右边,正方形的边长大小也是为基础的0.4*最大边长加上另外
            pp=(float)((2-progress)*0.6)+0.4f;
        }
        //绘制大正方形
        canvas.drawRect(getWidth()/2-(mBigRectSize/2)*pp,getHeight()/2-(mBigRectSize/2)*pp,getWidth()/2+(mBigRectSize/2)*pp,getHeight()/2+(mBigRectSize/2)*pp,mBigPaint);
        canvas.restore();
        //同理绘制右边小正方形
        canvas.save();
        canvas.rotate(45,getWidth()-mSmallRectSize/2-getPaddingRight(),getHeight()/2);
        canvas.translate(-move*progress,move*progress);
        canvas.drawRect(getWidth()-mSmallRectSize-getPaddingRight(),getHeight()/2-mSmallRectSize/2,getWidth()-getPaddingRight(),getHeight()/2+mSmallRectSize/2,mRightPaint);
        canvas.restore();

        //绘制文字    通过画笔测量“加载中”文字的宽度长度
        Rect bounds = new Rect();
        mTextPaint.getTextBounds(getText().toString(),0,getText().toString().length(),bounds);
        //文字的x坐标
        int x = getWidth()/2-bounds.width()/2;
        //基线的获取,网上有很多关于基线的资料,都是固定的样式,没啥好说的
        Paint.FontMetricsInt fontMetricsInt = new Paint.FontMetricsInt();
        int dy = (fontMetricsInt.bottom-fontMetricsInt.top)/2-fontMetricsInt.bottom;
        int baseline = getHeight()/2+dy;
        canvas.save();
        //开始绘制
        canvas.drawText(getText().toString(),x,(float)(baseline+1.4*mBigRectSize),mTextPaint);
        canvas.restore();
        //绘制跳动的点
        if ((progress>=0&&progress<0.5)){
    
    
            canvas.drawText(".",x+bounds.width()+4,(float)(baseline+1.4*mBigRectSize),mTextPaint);
        }else if (progress>=0.5&&progress<1){
    
    
            canvas.drawText("..",x+bounds.width()+4,(float)(baseline+1.4*mBigRectSize),mPointPaint);
        }else if (progress>=1&&progress<1.5){
    
    
            canvas.drawText("...",x+bounds.width()+4,(float)(baseline+1.4*mBigRectSize),mPointPaint);
        }else{
    
    
            canvas.drawText("....",x+bounds.width()+4,(float)(baseline+1.4*mBigRectSize),mPointPaint);
        }


    }
    public void setProgress(float c_progress){
    
    
        this.progress = c_progress;
        invalidate();//画布刷新
    }
     //接下来就是基本的属性外界修改方法,属性的值完全自由调用方法修改
    public void initStyle(int c_mLeftSmallRectColor,int c_mRightSmallRectColor,
                          int c_mBigRectColor,int c_mtextcolor,int c_mPointColor,int c_mSmallRectSize,int c_mBigRectSize){
    
    
        this.mLeftSmallRectColor = c_mLeftSmallRectColor;
        this.mRightSmallRectColor = c_mRightSmallRectColor;
        this.mBigRectColor = c_mBigRectColor;
        this.mSmallRectSize = c_mSmallRectSize;
        this.mtextcolor = c_mtextcolor;
        this.mBigRectSize = c_mBigRectSize;
        this.mPointColor = c_mPointColor;
    }
}

代码注释已经很详细了,我就不多b了

接下来就是要再Activity中使用了
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <LinearLayout
        android:gravity="center"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.ilikexy.zidingyiview.zidingyi.WangZheLoad
            android:id="@+id/wangzhe_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10sp"
            android:text="加 载 中"
            android:textSize="10sp"
            app:leftSmallRectColor="@color/colorPrimaryDark"
            app:rightSmallRectColor="@color/colorPrimaryDark"
            app:bigRectColor="@color/colorPrimaryDark"
            app:smallRectSize="8sp"
            app:bigRectSize="20sp"
            ></com.ilikexy.zidingyiview.zidingyi.WangZheLoad>
    </LinearLayout>

</LinearLayout>

MainActivity.class

public class MainActivity extends AppCompatActivity {
    
    
    private WangZheLoad wangzhe_view;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }
    public void init(){
    
    
        wangzhe_view = (WangZheLoad)findViewById(R.id.wangzhe_view);
        //动画类,下面会给出这个类的定义
                ValueAmanitor.catoonShow1(wangzhe_view, 0, 8, 8000);
    }
}

动画类 ValueAmanitor

public class ValueAmanitor {
    
    
      public static void catoonShow1(final WangZheLoad wangZheLoad, float start, float end, int time){
    
    
        ValueAnimator valueAnimator = ObjectAnimator.ofFloat(start,end);
        valueAnimator.setDuration(time);//变化总体时间
       valueAnimator.setInterpolator(new LinearInterpolator());//匀速变化
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    
    
            @Override
            public void onAnimationUpdate(ValueAnimator c_animator) {
    
    
                float currentprogress = (Float) c_animator.getAnimatedValue();
                wangZheLoad.setProgress(currentprogress%2);//调用自定义view中 设置进度的方法,
                                                          //取余2是为了保证progress在 0----2之间变换
            }
        });
        valueAnimator.start();
    }
}

要是任何关于这个自定义View问题或建议,那就联系本菜鸡的qq
2569658002.

猜你喜欢

转载自blog.csdn.net/qq_41904106/article/details/109107154