一、效果图
二、设计思路
这是一个由基本图形组成的loading效果图,从图上可以看出,整个效果分为两个部分:1、三角形 2、圆环。当圆环转动一圈时,三角形并未发生变化,当圆环转动第二圈时,三角形随圆环的消失而旋转。有一定自定义View经验的朋友一定知道,我们可以用画笔画出圆、矩形、扇形等基本图形,也能通过Path(路径)来绘制三角形等任何图形。这里实现的方式有很多,我这里用了一个更为简单易懂的方法来做:
三角形:
使用Path路径绘制一个等边三角形,并将三角形绘制在最上层。
三角形旋转:
三角形的旋转我们可以通过旋转画布Canvas来实现,就不用每次都计算三角形每个点的位置了。
圆环:
使用绿色画笔绘制一个外圆,然后使用黑色画笔绘制一个内圆,内圆压在外圆上,并且内圆的半径略小于外圆的半径,这样重叠绘制之后,就能达到一个圆环的效果。但是,由于我们的动画是圆环从无到有再从有到无,所以最外层就不能直接绘制成圆形,而是绘制成一个扇形,我们通过不断改变扇形的角度,来达到圆环从无到有的动画。
圆环从有到无:
圆环绘制完一圈之后,最外圈则是绿色的外圆,内圈是黑色的内圆。那么我们只需要一个跟绿色外圆同样半径的黑色扇形来重叠在外圆上,并且通过增加黑色扇形的角度来逐渐覆盖外圈,从而实现一个看似圆环“从有到无”的效果。这时,就应该有三层,从下到上的顺序依次为:绿色外圆,黑色扇形,黑色内圆。
三、代码展示
在项目中,有三个核心的类:LoadingView(自定义控件的视图)、CircleModel(绘制圆环的类)、TriangleModel(绘制三角形的类)。LoadingView主要控制另外两个类的绘制和动画的实施。
package com.example.iqyloadingview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
public class TriangleModel {
/**三角形的三个点*/
private float pointP1X;
private float pointP1Y;
private float pointP2X;
private float pointP2Y;
private float pointP3X;
private float pointP3Y;
/**中点位置*/
private float tmX;
private float tmY;
private Context mContext;
/**距离边缘的距离*/
private float gap;
/**中心点到每个点的长度*/
private float cmRadius;
private Path trianglePath;
private Paint trianglePaint;
private int greenColor = Color.parseColor("#0BBE06");
public TriangleModel(Context context, float centerWidth, float centerHeight) {
mContext = context;
/**初始化的时候传入三角形的中心点坐标*/
tmX = centerWidth;
tmY = centerHeight;
initialRes();
}
private void initialRes() {
//gap为绘制的图形的区域离边缘的间隙
gap = Utils.dpTopx(mContext, 2);
//cmRdius主要作用是确定三角形的大小
cmRadius = (Math.min(tmX, tmY) - gap) / 2;
trianglePath = new Path();
trianglePaint = new Paint();
trianglePaint.setAntiAlias(true);
trianglePaint.setColor(greenColor);
//距中心点左上点
pointP1X = tmX - cmRadius / 2;
pointP1Y = (float) (tmY - Math.sin(Math.PI / 3) * cmRadius);
//距中心点左下点
pointP2X = pointP1X;
pointP2Y = (float) (tmY + Math.sin(Math.PI / 3) * cmRadius);
//距中心点右侧点
pointP3X = tmX + cmRadius;
pointP3Y = tmY;
//路径绘制三角形区域
trianglePath.moveTo(pointP1X, pointP1Y);
trianglePath.lineTo(pointP2X, pointP2Y);
trianglePath.lineTo(pointP3X, pointP3Y);
trianglePath.close();
}
/**
* 绘制三角形并转动相应角度
* @param canvas
* @param sweepRatio 旋转比例
*/
public void drawTriangle(Canvas canvas, float sweepRatio) {
canvas.save();
if (sweepRatio > 1)//比例大于1时,说明准备开始旋转三角形
canvas.rotate(360 * (sweepRatio - 1), tmX, tmY);
countPointAndDrawTriangle(canvas);
canvas.restore();
}
private void countPointAndDrawTriangle(Canvas canvas) {
canvas.drawPath(trianglePath,trianglePaint);
}
public void setPosition(float centerWidth, float centerHeight) {
tmX = centerWidth;
tmY = centerHeight;
}
接下来看下圆环的绘制类:
package com.example.iqyloadingview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
public class CircleModel {
private Context mContext;
//背景色为绿色的类型
public static int GREEN_TYPE = 0X001;
//背景色为黑色的类型
public static int TRANSPARENT_TYPE = 0X002;
private float cmX;
private float cmY;
/**
* 图形离边缘的间隙
*/
private float gap;
private Paint mPaint;
private int greenColor = Color.parseColor("#0BBE06");
private int blackColor = Color.parseColor("#000000");
private RectF rectF;
public CircleModel(Context context, float centerWidth, float centerHeight) {
mContext = context;
//圆中心点位置坐标
cmX = centerWidth;
cmY = centerHeight;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(greenColor);
initialRes();
}
private void initialRes() {
gap = Utils.dpTopx(mContext, 2);
rectF = new RectF(0, 0, cmX * 2, cmY * 2);
}
private float currentSweep;
/**
* 画圆环
*
* @param canvas
* @param sweepRatio 扫过的百分比 0-1
*/
public void drawRing(Canvas canvas, float sweepRatio) {
currentSweep = 360 * sweepRatio;
//按照一定比例绘制圆环
canvas.drawArc(rectF, -90, currentSweep, true, mPaint);
}
public void setPaintType(int type){
if(GREEN_TYPE == type){
mPaint.setColor(greenColor);
}else if(TRANSPARENT_TYPE == type){
mPaint.setColor(blackColor);
}
}
public void setPosition(float centerWidth, float centerHeight) {
cmX = centerWidth;
cmY = centerHeight;
}
/**
* 设置内圆的绘制区域
*/
public void setInnerRadius() {
rectF.set(0+2*gap,0+2*gap,cmX * 2-2*gap, cmY * 2-2*gap);
mPaint.setColor(blackColor);
}
最后则是整体控制类:
package com.example.iqyloadingview;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
public class LoadingView extends View {
private Context mContext;
/**中心点宽度*/
private float centerWidth;
/**中心点高度*/
private float centerHeight;
/**圆环相关类*/
private CircleModel circleModel;//绿色外圆
private CircleModel coverCircleModel;//黑色扇形
private CircleModel frontCircleModel;//黑色内圆
/** 三角形相关类*/
private TriangleModel triangleModel;
/**属性动画*/
private ValueAnimator valueAnimator;
private Canvas mCanvas;
/**是否需要开启动画*/
private boolean ifAnimStart = false;
/**当前的转动值*/
private float currentPoint = 0f;
public LoadingView(Context context) {
this(context, null);
}
public LoadingView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
//初始化属性动画的属性值
valueAnimator = ValueAnimator.ofFloat(0, 2);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setDuration(1800);
valueAnimator.setRepeatCount(Animation.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//每次获取到当前绘制比例值之后刷新视图
currentPoint = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw) {
centerWidth = w / 2;
}
if (h != oldh) {
centerHeight = h / 2;
}
if (circleModel == null) {
circleModel = new CircleModel(mContext, centerWidth, centerHeight);
coverCircleModel = new CircleModel(mContext, centerWidth, centerHeight);
frontCircleModel = new CircleModel(mContext, centerWidth, centerHeight);
triangleModel = new TriangleModel(mContext, centerWidth, centerHeight);
} else {
//如果视图大小有所调整,则重新设置中心点
circleModel.setPosition(centerWidth, centerHeight);
coverCircleModel.setPosition(centerWidth, centerHeight);
frontCircleModel.setPosition(centerWidth, centerHeight);
triangleModel.setPosition(centerWidth, centerHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mCanvas == null) {
mCanvas = canvas;
}
if(currentPoint <= 1){//0-1时绘制绿色扇形
circleModel.drawRing(canvas, currentPoint);
}else{//1-2时,绘制黑色扇形
circleModel.drawRing(canvas, 1);
coverCircleModel.setPaintType(CircleModel.TRANSPARENT_TYPE);
coverCircleModel.drawRing(canvas, currentPoint-1);
}
frontCircleModel.setInnerRadius();
frontCircleModel.drawRing(canvas, 1);
//最后绘制三角形,保证三角形压在其他图形之上
triangleModel.drawTriangle(canvas,currentPoint);
}
/**
* 开启动画
*/
public void animationOpen(){
if(valueAnimator != null && !valueAnimator.isRunning() && ifAnimStart == false){
valueAnimator.start();
ifAnimStart = true;
}
}
四、总结
总体来说,这个自定义控件相对容易,需要读者掌握两个知识点:自定义View的基本方法、属性动画,这也是自定义View最基本的知识点,希望大家能从这个控件中学习到相关方法,巩固相关知识。
AndroidStudio的实例代码请点击以下链接:
http://download.csdn.net/detail/qq_30227229/9716779