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