介绍一个过渡动画,一般用于启动页、加载页面。基于Matriel Design设计风格,效果不错。
序:涉及知识点
- 自定义视图View
- Paint 画笔工具
- Java基础之抽象类与抽象方法
一、首先我们来看看效果图
二、下面我们来进行实际开发:
1. 首先我们分析一下这个Demo例子的基本逻辑。由效果图上看到,我们可以分析出页面可能分为两个布局,首先第一个布局即为旋转的小圆圈动画布局,第二个布局即动画效果结束后的“单身狗”图片背景布局。
那么由上述分析,我们先创建一个Activity:SpalshActivity.class
/**
* 启动页面
*/
public class SpalshActivity extends AppCompatActivity {
private Handler handler=new Handler();
private SplashView mSplashView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//动画结束后展示的页面视图View
View v=View.inflate(this, R.layout.activity_spalsh, null);
//显示动画的页面视图View
mSplashView=new SplashView(this);
FrameLayout fl=new FrameLayout(this);
fl.addView(v);
fl.addView(mSplashView);
setContentView(fl);
handler.postDelayed(new Runnable() {
@Override
public void run() {
mSplashView.stopRotate();
}
}, 2000);
}
}
上面的代码表示先将页面视图放入FrameLayout,再创建一个显示动画的视图View(SplashView)放入 FrameLayout,保证显示动画的视图View(SplashView)在视图层的最上面;
2.接下来我们再继续分析。既然动画的视图View用来展示一系列动画,那么它到底有哪些内容呢?同样重新回看效果图,我们发现动画基本可细分为3部分。
第一部分:旋转动画——绘制出几个小圆圈,围绕屏幕中心进行顺时针旋转。
第二部分:弹动动画——小圆圈在旋转一定时间结束后,会向中间进行一个缩放的弹性动作动画。
第三部分:水波纹动画——最后一部分即呈现出从中心向四周扩散的圆形水波纹动画。
别急,下面我们来完成这个View:SplashView.class
/**
* 渐变视图
* Created by WangJie on 2017/1/11.
*/
public class SplashView extends View {
private Paint cPaint;//画小圆的画笔
private Paint bPaint;//画空心圆的画笔(用来制作水波纹)
private int width;
private int height;
private int[] colorArray;//小圆颜色的数组
private static final int MAX_DOT_CONUNT=6;//最大小圆的个数
private static int BIG_CIRCLE_RADIUS=80;//小圆距离屏幕中心的距离
private static final int S_CIRCLE_RADIUS=10;//小圆的半径
private float singleAngle;//每个小圆之间的夹角
private float rotateAngle=0;//旋转动画转动的角度
private int currentRadius=BIG_CIRCLE_RADIUS;//当前小圆距离屏幕中心的距离
private MatrielDesign design;
private ValueAnimator mAnimator;
public SplashView(Context context) {
super(context);
initSplashView();
}
/**
* 初始化渐变视图
*/
private void initSplashView() {
//设置第一个动画画小圆的画笔
cPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 构建Paint时直接加上去锯齿属性(Paint.ANTI_ALIAS_FLAG)
cPaint.setStyle(Paint.Style.FILL);//设置填充
//设置第三个动画画空心圆的画笔
bPaint = new Paint(Paint.ANTI_ALIAS_FLAG);// 构建Paint时直接加上去锯齿属性(Paint.ANTI_ALIAS_FLAG)
bPaint.setStyle(Paint.Style.STROKE);//设置描边
bPaint.setColor(0xFF4B88B2);//设置画笔颜色,为了看出区别,设置为蓝色
//获取小球颜色ID数组
colorArray = getContext().getResources().getIntArray(R.array.circleColor);
//求出每个小圆之间的夹角并复制
singleAngle = (float) (2*Math.PI/MAX_DOT_CONUNT);
}
/**
* 重写绘制方法
* 1.开始创建时,重新绘制小圆圈
* 2.开启小圆圈动画
* 3.调用startAnim方法对画布canvas进行操作
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
if(design==null){
design=new RotateAnim();
}
design.startAnim(canvas);
}
/**
* 提供给外加调用的,闪屏页检查版本更新后可以调用停止第一阶段动画并且开启第二阶段
*/
public void stopRotate(){
mAnimator.cancel();
design=new ScaleAnim();
invalidate();
}
/**
* 第一部分:旋转动画
* 改变的rotateAngle大小,让drawCircle()的时候可以每次及时计算出每个小圆的坐标
*/
private class RotateAnim extends MatrielDesign{
public RotateAnim() {
mAnimator=ValueAnimator.ofFloat(0,1.0f);
//用于设置循环模式,设置为ValueAnimator.RESTART时,表示正序重新开始,当取值为ValueAnimator.REVERSE表示倒序重新开始
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
//用于设置动画循环次数,设置为0表示不循环,当取值为ValueAnimator.INFINITE表示无限循环
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
//持续时间
mAnimator.setDuration(1300);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction=(Float) animation.getAnimatedValue();
rotateAngle=(float) (Math.PI*fraction);
invalidate();
}
});
mAnimator.start();
}
@Override
public void startAnim(Canvas canvas) {
drawBg(canvas);
drawCircle(canvas);
}
}
/**
* 第二部分:比例缩放动画
* 改变小圆与屏幕中心距离,定义ValueAnimator.ofFloat(1.0f,0.2f,1.2f)让动画有一个回弹效果
*/
private class ScaleAnim extends MatrielDesign{
public ScaleAnim() {
mAnimator=ValueAnimator.ofFloat(1.0f,0.2f,1.2f);
mAnimator.setDuration(1000);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction=(Float) animation.getAnimatedValue();
currentRadius=(int) (BIG_CIRCLE_RADIUS*fraction);
invalidate();
}
});
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
design=new CrinkleAnim();
invalidate();
}
});
mAnimator.start();
}
@Override
public void startAnim(Canvas canvas) {
drawBg(canvas);
drawCircle(canvas);
}
}
private int crinkleCircle;
private double diagonal;
private int strokeWidth;
/**
* 水波纹动画
* 画背景的时候画一个圆,然后调整画笔的描线宽度,这个思路也可以做一个圆形头像的效果
*/
private class CrinkleAnim extends MatrielDesign{
public CrinkleAnim(){
mAnimator=ValueAnimator.ofFloat(0,1.0f);
mAnimator.setDuration(1000);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction=(Float) animation.getAnimatedValue();
crinkleCircle = (int) (diagonal*fraction);
strokeWidth=(int) (diagonal-crinkleCircle);
bPaint.setStrokeWidth(strokeWidth);
invalidate();
}
});
mAnimator.start();
}
//第三阶段的动画只需要画背景即可
@Override
public void startAnim(Canvas canvas) {
isCrinkle=true;
drawBg(canvas);
}
}
private boolean isCrinkle;
private void drawBg(Canvas canvas) {
if(!isCrinkle){
//设置画布背景色
canvas.drawColor(0xffEB9F95);
}else{
//绘制空心圆形
canvas.drawCircle(width/2, height/2, crinkleCircle+strokeWidth/2, bPaint);
}
}
//计算每个小圆的坐标,画小圆
private void drawCircle(Canvas canvas) {
for(int i=0;i<colorArray.length;i++){
float angle=singleAngle*i+rotateAngle;
float cx=(float) (currentRadius*Math.cos(angle))+width/2;
float cy=(float) (currentRadius*Math.sin(angle))+height/2;
cPaint.setColor(colorArray[i]);
canvas.drawCircle(cx, cy, S_CIRCLE_RADIUS, cPaint);
}
}
/**
*获取View宽高,方便空心圆画笔的空心线宽计算
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width=w;
height=h;
diagonal = Math.sqrt(w*w+h*h)/2;
}
}
为了方便复习,我针对上述代码进行一些解释。首先SplahsView.class继承于View.class,在实例化方法中,调用initSplashView()方法两个画笔的初始化和小圆圈的相关参数初始化(具体解析在代码中已备注)。利用View绘制的生命周期onDraw()方法,创建第一部分动画RotateAnim.class旋转动画,并利用自定义的抽象类父类(MatrielDesign.class)中的抽象方法(startAnim())开启动画。此过程结束后,便是第一部分动画小圆圈不停的旋转。
还没有结束!
回到SpalshActivity.class中查看代码,其中有一段利用handler.postDelayed方法延时2000毫秒调用了SplahsView.class中的stopRotate()方法,这段代码模拟了加载过程结束后,进入下一个布局的过程,即开启第二部分动画ScaleAnim.class弹动动画的过程。在SplahsView.class代码中,查阅可发现ScaleAnim.class动画类中,对动画进行了监听,意图动画播放结束后,执行第三部分动画——水波纹动画。至此此部分代码解释完毕。
下面放出抽象类(MatrielDesign.class)的代码
public abstract class MatrielDesign{
public abstract void startAnim(Canvas canvas);
}
总结:
整个Demo例子其实很简单,但是如果对文章前所说的知识点不熟悉的开发者来说,解读仍然需要花点功夫,在此写清楚是为了方便我自己以后复习。同时备注一下我也是根据其他大神的博客内容进行学习才完成本文内容的编写,为表示尊重,下面告知相关参考博客。