效果图请看这个帖子写的,跟着写的 https://www.jianshu.com/p/923513a70d15
看完别人的效果图,第一步不是赶紧往下看,而是自己脑子里想下,自己写的话该咋写。
首先画6个小球,旋转, 然后往中心收缩,这都很常见,最后的扩散,我第一想到的是中心裁剪掉,也就是用clip方法了,也尝试自己写完看效果还行,然后跑去看作者的,发现作者很高明,通过修改paint的strokewidth来实现,又get到一个方法,不错。
裁剪代码如下,150就是中间那空心的半径,从0开始变大
path.reset();
path.addCircle(0, 0, 150, Direction.CW);;
canvas.clipPath(path, Region.Op.DIFFERENCE);
clip用的也不多,所以没研究过Region.Op的几种都啥效果,就看着名字蒙了一个是对的。完事赶紧百度下,找到下边的几个帖子,可以学习下
这里有clip那个属性的用法,看完这个,就赶紧画了个八卦图~~
http://blog.csdn.net/richiezhu/article/details/52956163
这里是介绍op的,感觉说的还不错,图文并茂
http://www.runoob.com/w3cnote/android-tutorial-canvas-api2.html
完整代码如下,简单说明下,因为不知道这种动画的具体要求,是不是小球一直转,等到加载完数据才手动调用方法来执行收缩和扩散动画,我就让它旋转一圈,监听动画结束完事开启收缩动画和扩散动画的,有需求再按照需求来吧,这里就随意了。
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path.Direction;
import android.graphics.Path;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.LinearInterpolator;
public class DotRotate extends View {
public DotRotate(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
int[] colors = { Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN, Color.GRAY, Color.parseColor("#215980") };
float startAngle = 0;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint paintBG = new Paint(Paint.ANTI_ALIAS_FLAG);
Path path=new Path();
private void initPaint() {
paint.setStyle(Style.FILL);
paintBG.setColor(Color.WHITE);
paintBG.setStyle(Style.STROKE);
}
int radius = 0;//原始几个小球的距离中心点的距离
float radiusFactor = 1;//小球距离中心点的比例,收缩用
int radiusMax;//扩散圆的最大值,用的宽高一半的
float radiusTrans;//透密空心大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width=getMeasuredWidth()/2;
int height=getMeasuredHeight()/2;
radius =Math.min(width, height) * 2 / 3;//留点边界,所以就取2/3大小好啦
radiusMax = (int) Math.sqrt(width*width+ height*height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (radius == 0) {
return;
}
float intervalAngle = (float) (2 * Math.PI / colors.length);
canvas.save();
canvas.translate(getWidth() / 2, getHeight()/2);
if (radiusTrans > 0) {//开始中心扩散的时候,就不用画小球拉
//通过裁剪实现
path.reset();
path.addCircle(0, 0, radiusTrans, Direction.CW);;
canvas.clipPath(path, Region.Op.DIFFERENCE);
canvas.drawColor(Color.WHITE);
//或者用下边的代码
// paintBG.setStrokeWidth(radiusMax-radiusTrans);//画笔的宽就是减去透明部分的其他部分
//举例说下,比如半径为40,画笔宽为10,那么最终线条是画在40-10/2,和40+10/2,就是说画笔宽度是平分在中心点的两边的,
//所以下边就是计算中心点的坐标的代码拉
// canvas.drawCircle(0, 0, radiusTrans+(radiusMax-radiusTrans)/2, paintBG);
} else {
canvas.drawColor(Color.WHITE);
for (int i = 0; i < colors.length; i++) {
double angle = startAngle + i * intervalAngle;
float x = (float) (radius * radiusFactor * Math.cos(angle));
float y = (float) (radius * radiusFactor * Math.sin(angle));
paint.setColor(colors[i]);
canvas.drawCircle(x, y, 10, paint);
}
}
canvas.restore();
}
ValueAnimator animator;
public void startAnimation() {
animator = ValueAnimator.ofFloat(0, (float) (2 * Math.PI));
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
startAngle = (Float) animation.getAnimatedValue();
postInvalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startShrink();
}
});
animator.setRepeatCount(0);
animator.setDuration(2000);
animator.setInterpolator(new LinearInterpolator());
animator.start();
}
private void startShrink() {
animator = ValueAnimator.ofFloat(1, 0.0f);//小球的半径缩放因子,从最大值到0
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radiusFactor = (Float) animation.getAnimatedValue();
postInvalidate();
}
});
animator.setDuration(1000);
animator.setInterpolator(new AnticipateInterpolator());//有一个向外弹的动作,就靠这个加速器的
animator.start();
startEnd();
}
public void startEnd() {
animator = ValueAnimator.ofFloat(10,radiusMax);
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radiusTrans = (Float) animation.getAnimatedValue();
postInvalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
cancelAnimation();
setVisibility(GONE);
}
});
animator.setDuration(1000);
animator.setInterpolator(new AccelerateInterpolator());//用的加速插值器
animator.setStartDelay(900);
animator.start();
}
public void cancelAnimation() {
if (animator != null) {
animator.cancel();
animator = null;
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimation();
}
@Override
protected void onDetachedFromWindow() {
if (animator != null) {
animator.cancel();
animator = null;
}
super.onDetachedFromWindow();
}
}
附上kotlin版本
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.graphics.Paint.ANTI_ALIAS_FLAG
import android.view.animation.AnticipateInterpolator
/**
* Created by charlie.song on 2018/6/4.
*/
class LoadingView:View{
constructor(context: Context?) : super(context){
initPaint()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){
initPaint()
}
var colors = intArrayOf(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN, Color.GRAY, Color.parseColor("#215980"))//6个小球的颜色
var startAngle = 0f //旋转的角度,弧度表示
var paint = Paint(ANTI_ALIAS_FLAG) //小球用
var paintBG = Paint(Paint.ANTI_ALIAS_FLAG)//画背景
var paintClear=Paint() //裁剪中心用
private fun initPaint() {
paint.setStyle(Paint.Style.FILL)
paintBG.setColor(Color.WHITE)
paintBG.style=Paint.Style.FILL
paintClear.setXfermode(PorterDuffXfermode(PorterDuff.Mode.CLEAR))//如果view设置了背景,那么clear的部分就会成灰黑色。
}
var radius = 0f//原始几个小球的距离中心点的距离
var radiusFactor = 1f//小球距离中心点的比例,收缩用
var clearRadius=0f;//中间透明的半径
var path=Path()
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if(changed){
radius=1f/4*Math.min(width,height)
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if(radius==0f){
return
}
canvas.save()
canvas.translate(width/2f,height/2f)
if(clearRadius>0){
//注释的方法,如果要用,则得确保没有设置background,否则clear的部分就成了黑色了。
// canvas.drawColor(Color.DKGRAY)
// canvas.drawCircle(0f,0f,clearRadius,paintClear)
canvas.clipPath(path.apply {
reset()
addCircle(0f,0f,clearRadius,Path.Direction.CW)
},Region.Op.DIFFERENCE)
canvas.drawColor(Color.DKGRAY) //需要注意,裁剪的时候先裁剪后绘制,否则无效
}else{
canvas.drawColor(Color.DKGRAY)
//draw 6 balls
(0 ..5).forEach {
val r=radius*radiusFactor;
val angle=Math.PI/180*60*it+startAngle
val x=r*Math.cos(angle)
val y=r*Math.sin(angle)
paint.color=colors[it]
canvas.drawCircle(x.toFloat(),y.toFloat(),10f,paint)
}
}
canvas.restore()
}
private fun resetDefault(){
startAngle=0f
radiusFactor=1f
clearRadius=0f;
}
var animaRotate:ValueAnimator?=null
fun startLoading(){
resetDefault()
animaRotate=ValueAnimator.ofFloat(0f,Math.PI.toFloat()*2).apply {
setDuration(2000L)
repeatCount=ValueAnimator.INFINITE
addUpdateListener(object : ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(animation: ValueAnimator) {
startAngle=animation.animatedValue as Float
postInvalidate()
}
})
interpolator=null//改为线性加速,默认是中间加速的
start()
}
}
fun stopLoading(){
animaRotate?.cancel()
ValueAnimator.ofFloat(1f,0f).apply {
setDuration(500L)
addUpdateListener( object :ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(animation: ValueAnimator) {
radiusFactor=animation.animatedValue as Float
postInvalidate()
}
})
addListener(object :AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
cancel()
startShowByCircle()
}
})
interpolator=AnticipateInterpolator()
start()
}
}
private fun startShowByCircle(){
ValueAnimator.ofFloat(0f, Math.sqrt(width*width/4.0+height*height/4.0).toFloat()).apply {
setDuration(700L)
addUpdateListener( object :ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(animation: ValueAnimator) {
clearRadius=animation.animatedValue as Float
postInvalidate()
}
})
start()
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
animaRotate?.cancel()
}
}