欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Android自定义View实现注销图案的加载动画

程序员文章站 2022-06-25 17:27:09
先看效果图:有那味了。。。(懂得都懂^ ^ √)我们先来分析一下怎么画,然后再研究怎么让他动起来这个View是由内部的注销图案和外面一圈圆环构成。而内部的注销图案又是由一个基本满角度的圆弧和一根竖线组成一、绘制内部注销图案首先初始化画笔和圆弧的外切矩形:private Paint logOffPaint; //注销图案的画笔private Paint circlePaint; //外圆的画笔private RectF logOffRect; //注销图案中圆弧的外切...

先看效果图:

Android自定义View实现注销图案的加载动画

有那味了。。。(懂得都懂^ ^ √)

我们先来分析一下怎么画,然后再研究怎么让他动起来

这个View是由内部的注销图案和外面一圈圆环构成。而内部的注销图案又是由一个基本满角度的圆弧和一根竖线组成

一、绘制内部注销图案

首先初始化画笔和圆弧的外切矩形:

private Paint logOffPaint;  //注销图案的画笔
private Paint circlePaint;  //外圆的画笔
private RectF logOffRect;   //注销图案中圆弧的外切矩形
private int logOffLength=50;    //注销图案中竖线的长度
public MyLogOffLoadView(Context context, @Nullable AttributeSet attrs){
    super(context, attrs);
    init();
}

private void init(){
    logOffRect=new RectF();
}


@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    initStyle();
}

//设置控件属性
private void initStyle(){
    logOffLength=getWidth()/4;
    logOffPaint=setPaint(Color.WHITE,8, Paint.Style.STROKE,true);
    circlePaint=setPaint(Color.WHITE,12, Paint.Style.STROKE,true);
}

//设置画笔属性
private Paint setPaint(int color, int strokeWidth, Paint.Style style,boolean openAntiAlias){
    Paint paint=new Paint();
    paint.setColor(color);
    paint.setStrokeWidth(strokeWidth+getWidth()/200);
    paint.setStyle(style);
    paint.setAntiAlias(openAntiAlias);
    return paint;
}

圆弧的中心是View的中心,坐标为(getWidth()/2,getWidth()/2),半径设置为getWidth/4,获取圆弧外切矩形:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawLogOff(canvas);
}

//绘制注销图片
private void drawLogOff(Canvas canvas){
    logOffRect.set(getWidth()/4,getWidth()/4,getWidth()*3/4,getWidth()*3/4);
}

设置圆弧的初始位置为-75度,以及总共需要绘制的角度为360-(90-75)*2=330,调用drawArc()绘制圆弧:

//绘制注销图片
private void drawLogOff(Canvas canvas){
    logOffRect.set(getWidth()/4,getWidth()/4,getWidth()*3/4,getWidth()*3/4);
    canvas.drawArc(logOffRect,-75,330,false,logOffPaint);
}

计算出竖线的起点和终点坐标,分别为(getWidth()/2,(getWidth()-logOffLength)/4);(getWidth()/2,(getWidth()+logOffLength*3)/4),logOffLength为竖线的长度,调用drawLine()绘制竖线:

//绘制注销图片
private void drawLogOff(Canvas canvas){
    logOffRect.set(getWidth()/4,getWidth()/4,getWidth()*3/4,getWidth()*3/4);
    canvas.drawArc(logOffRect,-75,330,false,logOffPaint);
    canvas.drawLine(getWidth()/2,(getWidth()-logOffLength)/4,getWidth()/2,(getWidth()+logOffLength*3)/4,logOffPaint);
}

二、绘制外部圆环

圆环的中心即View的中心,半径为View的半径减去圆环宽度的一半,调用drawCircle绘制:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawLogOff(canvas);
    canvas.drawCircle(getWidth()/2,getWidth()/2,getWidth()/2-circlePaint.getStrokeWidth()/2,circlePaint);
}

三、实现绿条加载动画

外圆环中的绿条实际上就是一个圆弧,我这里将它的圆弧的总角度设置为了90度,并且每次顺时针移动5度

首先初始化一系列属性:

private Paint loadArcPaint; //加载时的圆弧画笔
private RectF loadArcRect;   //加载圆弧的外切矩形
private boolean isStarted=false,isLoadFinished=false;    //是否开始加载;是否加载完毕
private int totolAngle=90,movingAngle=0,movePerAngle=5;        //加载时圆弧总共的长度;加载时圆弧目前所在的角度;加载时圆弧每次移动的角度

private void init(){
    loadArcRect=new RectF();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //......
    if(isStarted&&!isLoadFinished){
        canvas.drawArc(loadArcRect,movingAngle,totolAngle,false,loadArcPaint);
        //顺时针转动movePerAngle角度
        movingAngle+=movePerAngle;
        if(movingAngle>360)movingAngle=movePerAngle%360;
        postInvalidateDelayed(5);
    }
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    initStyle();
}

//设置控件属性
private void initStyle(){
    loadArcPaint=setPaint(getResources().getColor(R.color.green),12, Paint.Style.STROKE,true);
    float arcHalfWidth=loadArcPaint.getStrokeWidth()/2;
    loadArcRect.set(arcHalfWidth,arcHalfWidth,getWidth()-arcHalfWidth,getWidth()-arcHalfWidth);
}

View要向外界提供start方法来控制View是否开始绘制动画:

//开始绘制动画
public void start(){
    isStarted=true;
    invalidate();
}

当外界数据加载完毕后,View需提供finish方法来绘制加载完成时的图案

//完成加载
public void finish(){
    isLoadFinished=true;
    logOffPaint.setColor(getResources().getColor(R.color.green));
    circlePaint.setColor(getResources().getColor(R.color.green));
    invalidate();
}

外界也可以中途停止加载,View还需提供stop方法:

 //停止加载
public void stop(){
    reset();
}

//重置View属性
private void reset(){
    isStarted=false;
    isLoadFinished=false;
    logOffDrawScale=0;
    hasScaleMax=false;
    scaleFinished=false;
    logOffPaint.setColor(Color.WHITE);
    circlePaint.setColor(Color.WHITE);
    invalidate();
}

四、实现注销图案的缩放动画

大家可能还发现,当点击View的时候,除了有绿条在旋转,注销图案也会有一个放大缩小的效果,这个效果其实挺好实现的

首先来思考一下,注销图案中的圆弧在缩放到时候,其实改变的是它的外切矩形,并且它的圆心位置是不变的,所以我们需要等长度改变矩形左上角的点和右下角的点。同时图案中的竖线的底部和圆心的距离也应该是不变的,我们缩放的时候只需要改变竖线顶部点的位置。

首先做初始化:

private int logOffDrawScale=0,maxScale=60,perScale=5;  //注销图案的缩放像素值;最大缩放值,每次缩放值
private boolean hasScaleMax=false,scaleFinished=false;  //是否缩放到最大;是否缩放完毕

当logOffDrawScale扩大到60的时候,我们设置hasScaleMax为true,并开始缩小logOffDrawScale

当logOffDrawScale缩小至0的时候,我们设置scaleFinished为true,表示整体缩放动画结束

我们修改一下绘制注销图案的方法,让它的外切矩阵随着logOffDrawScale的变化而变化:

//绘制注销图片
private void drawLogOff(Canvas canvas){
    //如果没有缩放完毕,并且开始加载时,才可以改变缩放值
    if(!scaleFinished&&isStarted){
        if(!hasScaleMax)logOffDrawScale+=perScale;
        else logOffDrawScale-=perScale;
    }
    if(logOffDrawScale>maxScale){
        logOffDrawScale=maxScale;
        hasScaleMax=true;
    }else if(logOffDrawScale<0){
        logOffDrawScale=0;
        hasScaleMax=false;
        scaleFinished=true;
    }
    logOffRect.set(getWidth()/4-logOffDrawScale,getWidth()/4-logOffDrawScale,getWidth()*3/4+logOffDrawScale,getWidth()*3/4+logOffDrawScale);
    canvas.drawArc(logOffRect,-75,330,false,logOffPaint);
    canvas.drawLine(getWidth()/2,(getWidth()-logOffLength)/4-logOffDrawScale,getWidth()/2,(getWidth()+logOffLength*3)/4,logOffPaint);
}

五、外界调用

Activity类中,我们设置对该View的监听,并用Timer延时调用的方法来模拟数据的加载:

public class MyLogOffLoadAct extends AppCompatActivity {
    private MyLogOffLoadView logOffLoadView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_logoff_load);
        init();
    }

    private void init(){
        logOffLoadView=findViewById(R.id.logoffview);
        logOffLoadView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!logOffLoadView.hasStart()){
                    logOffLoadView.start();
                    loadTask=new LoadTask();
                    timer.schedule(loadTask,3000);
                }
                else{
                    logOffLoadView.stop();
                    loadTask.cancel();
                }
            }
        });
    }

    private Timer timer=new Timer();
    private LoadTask loadTask;
    class LoadTask extends TimerTask{
        @Override
        public void run() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(),"加载完成!",Toast.LENGTH_LONG).show();
                }
            });
            logOffLoadView.finish();
        }
    }
}

xml布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:background="@color/blue2">
    <com.hualinfo.myviewtext.MyLogOffLoadView
        android:id="@+id/logoffview"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:layout_centerInParent="true"/>
</RelativeLayout>

下面是自定义View完整代码:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
public class MyLogOffLoadView extends View {
    private Paint logOffPaint;  //注销图案的画笔
    private Paint circlePaint;  //外圆的画笔
    private Paint loadArcPaint; //加载时的圆弧画笔
    private RectF logOffRect,loadArcRect;   //注销图案中圆弧的外切矩形;加载圆弧的外切矩形
    private int logOffLength=50;    //注销图案中竖线的长度
    private boolean isStarted=false,isLoadFinished=false;    //是否开始加载;是否加载完毕
    private int totolAngle=90,movingAngle=0,movePerAngle=5;        //加载时圆弧总共的长度;加载时圆弧目前所在的角度;加载时圆弧每次移动的角度
    private int logOffDrawScale=0,maxScale=60,perScale=5;  //注销图案的缩放像素值;最大缩放值,每次缩放值
    private boolean hasScaleMax=false,scaleFinished=false;  //是否缩放到最大;是否缩放完毕
    public MyLogOffLoadView(Context context, @Nullable AttributeSet attrs){
        super(context, attrs);
        init();
    }

    private void init(){
        logOffRect=new RectF();
        loadArcRect=new RectF();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawLogOff(canvas);
        canvas.drawCircle(getWidth()/2,getWidth()/2,getWidth()/2-circlePaint.getStrokeWidth()/2,circlePaint);
        if(isStarted&&!isLoadFinished){
            canvas.drawArc(loadArcRect,movingAngle,totolAngle,false,loadArcPaint);
            //顺时针转动movePerAngle角度
            movingAngle+=movePerAngle;
            if(movingAngle>360)movingAngle=movePerAngle%360;
            postInvalidateDelayed(5);
        }
    }

    //绘制注销图片
    private void drawLogOff(Canvas canvas){
        //如果没有缩放完毕,并且开始加载时,才可以改变缩放值
        if(!scaleFinished&&isStarted){
            if(!hasScaleMax)logOffDrawScale+=perScale;
            else logOffDrawScale-=perScale;
        }
        if(logOffDrawScale>maxScale){
            logOffDrawScale=maxScale;
            hasScaleMax=true;
        }else if(logOffDrawScale<0){
            logOffDrawScale=0;
            hasScaleMax=false;
            scaleFinished=true;
        }
        logOffRect.set(getWidth()/4-logOffDrawScale,getWidth()/4-logOffDrawScale,getWidth()*3/4+logOffDrawScale,getWidth()*3/4+logOffDrawScale);
        canvas.drawArc(logOffRect,-75,330,false,logOffPaint);
        canvas.drawLine(getWidth()/2,(getWidth()-logOffLength)/4-logOffDrawScale,getWidth()/2,(getWidth()+logOffLength*3)/4,logOffPaint);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        initStyle();
    }

    //设置控件属性
    private void initStyle(){
        logOffLength=getWidth()/4;
        logOffPaint=setPaint(Color.WHITE,8, Paint.Style.STROKE,true);
        circlePaint=setPaint(Color.WHITE,12, Paint.Style.STROKE,true);
        loadArcPaint=setPaint(getResources().getColor(R.color.green),12, Paint.Style.STROKE,true);
        float arcHalfWidth=loadArcPaint.getStrokeWidth()/2;
        loadArcRect.set(arcHalfWidth,arcHalfWidth,getWidth()-arcHalfWidth,getWidth()-arcHalfWidth);
    }

    //设置画笔属性
    private Paint setPaint(int color, int strokeWidth, Paint.Style style,boolean openAntiAlias){
        Paint paint=new Paint();
        paint.setColor(color);
        paint.setStrokeWidth(strokeWidth+getWidth()/200);
        paint.setStyle(style);
        paint.setAntiAlias(openAntiAlias);
        return paint;
    }

    //开始绘制动画
    public void start(){
        isStarted=true;
        invalidate();
    }

    public boolean hasStart(){
        return isStarted;
    }

    //停止加载
    public void stop(){
        reset();
    }

    //完成加载
    public void finish(){
        isLoadFinished=true;
        logOffPaint.setColor(getResources().getColor(R.color.green));
        circlePaint.setColor(getResources().getColor(R.color.green));
        invalidate();
    }

    //重置View属性
    private void reset(){
        isStarted=false;
        isLoadFinished=false;
        logOffDrawScale=0;
        hasScaleMax=false;
        scaleFinished=false;
        logOffPaint.setColor(Color.WHITE);
        circlePaint.setColor(Color.WHITE);
        invalidate();
    }
} 

本文地址:https://blog.csdn.net/zz51233273/article/details/108151287