(二十三)Animator 实例 —— 开场动画
来源:互联网 发布:导航升级软件下载 编辑:程序博客网 时间:2024/05/29 08:49
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、效果
这个是国外的一个 App 开场动画,曾获得设计大奖 。
二、分析
看效果是有两个界面,一个是 Splash 小球旋转的加载界面,一个是主界面,很多时候习惯在这边做成两个 Activity 进行跳转。如果条件允许的话,尽量做成两个 View 同时存在一个 Activity 里面,这边采用这种模式。
1.预加载时候显示 SplashView —- 小圆旋转的动画时间是不确定。
2.SplashView 盖在了主界面上面。
3.动画:动画分为三步。小球的旋转动画;小球逃逸和聚合动画(平移);水波纹的扩散动画。
三、搭建
加载的界面,弄成一个自定义控件,这样就可以与主界面放在一个 Activity 里面了。SplashView 一开始就执行小球旋转的动画,当数据加载完的时候,调用 splashDisappear,则开始调用小球的聚合与水波纹扩散的动画。
SplashView:
public class SplashView extends View { // 整体的背景颜色 private int mSplashBgColor = Color.WHITE; public SplashView(Context context) { this(context, null); } public SplashView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public SplashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public SplashView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context); } private void init(Context context) { } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(mSplashBgColor); } /** * 数据加载完成之后调用 * 开始后面的动画 */ public void splashDisappear() { }}
使用 ContentView 模拟主界面,虽然这边是继承 AppCompatImageView,只是为了方便,不是说这一定要是个 Image,这是一个 View。
ContentView:
public class ContentView extends android.support.v7.widget.AppCompatImageView { public ContentView(Context context) { this(context, null); } public ContentView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ContentView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { setImageResource(R.drawable.content); }}
布局文件,使用 FrameLayout,让 SplashView 盖在 ContentView 上面。
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.xiaoyue.animatorsplashview.MainActivity"> <com.xiaoyue.animatorsplashview.ContentView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY"/> <com.xiaoyue.animatorsplashview.SplashView android:id="@+id/splash_view" android:layout_width="match_parent" android:layout_height="match_parent"/></FrameLayout>
MainActivity:
public class MainActivity extends AppCompatActivity { private SplashView splashView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //AppCompatActivity 的设置全屏 supportRequestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); splashView = (SplashView) findViewById(R.id.splash_view); startLoad(); } Handler handler = new Handler(); private void startLoad() { //handler 模拟加载数据 handler.postDelayed(new Runnable() { @Override public void run() { //数据加载完毕,进入主界面,调用动画 splashView.splashDisappear(); } }, 5000); }}
这时候运行项目是一个全白的界面,加载界面 SplashView 把主界面 ContentView 盖住了。
四、策略模式
这个开场效果可以分为 3 个动画效果,小球的旋转动画、小球逃逸和聚合动画(平移)和水波纹的扩散动画。每次动画执行的时候都要进行绘制,为了代码优雅,这边采用策略模式进行搭建。
在 SplashView.java 添加几个内部类。
1.动画抽象类
//动画 private ValueAnimator mAnimator; private SplashState mState = null; //策略模式:State---三种动画状态 private abstract class SplashState{ //绘制各个状态的界面 public abstract void drawState(Canvas canvas); //取消动画 public void cancel(){ mAnimator.cancel(); } }
2.旋转动画
private class RotateState extends SplashState{ public RotateState() { } @Override } }
3.聚合动画
private class MergingState extends SplashState{ public MergingState() { } @Override public void drawState(Canvas canvas) { } }
4.水波纹扩散动画
private class ExpandState extends SplashState{ public ExpandState() { } @Override public void drawState(Canvas canvas) { } }
这边动画效果差异不是很大,逻辑也没有很复杂,使用策略模式可能显得有点麻烦。当动画差异较大的时候,策略模式写出来的代码就比较好看一些。
五、旋转动画
1.小球颜色
在初始化的时候获取小球的颜色,这样的话可以自己定义小球个数和颜色,比较灵活。
//小球颜色数组 private int[] mCircleColors; // 绘制小球的画笔 private Paint mPaint = new Paint(); private void init(Context context) { mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors); //画笔初始化 //消除锯齿 mPaint.setAntiAlias(true); }
color.xml
<resources> <color name="splash_bg">#F8F6EC</color> <color name="orange">#FF9600</color> <color name="aqua">#02D1AC</color> <color name="yellow">#FFD200</color> <color name="blue">#00C6FF</color> <color name="green">#00E099</color> <color name="pink">#FF3892</color> <array name="splash_circle_colors"> <item>@color/blue</item> <item>@color/green</item> <item>@color/pink</item> <item>@color/orange</item> <item>@color/aqua</item> <item>@color/yellow</item> </array></resources>
2.onDraw
重写 onDraw 方法,当第一次进来的时候,没有任何动画,直接初始化一个旋转动画,然后调用各个动画的绘制方法 drawState。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mState == null) { mState = new RotateState(); } mState.drawState(canvas); }
3.旋转动画
实现旋转动画,在构造函数中新建新建属性动画,改变旋转的角度,添加属性动画监听,每次执行动画时候进行重新绘制。
// 大圆和小球旋转一圈的时间 private final long mRotationDuration = 1200; //ms //当前圆旋转角度(弧度) private float mCurrentRotationAngle = 0F; /** * 1.旋转动画 * 控制各个小球的坐标---控制小球的角度变化----属性动画 ValueAnimator */ private class RotateState extends SplashState { public RotateState() { //计算某个时刻当前的角度是多少? 0~2π mAnimator = ValueAnimator.ofFloat(0f, 2 * (float)Math.PI); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentRotationAngle = (float) animation.getAnimatedValue(); //刷新,重新绘制 invalidate(); } }); //动画执行时间设置为 1200ms, mAnimator.setDuration(mRotationDuration); //设置无限循环 mAnimator.setRepeatCount(ValueAnimator.INFINITE); //启动动画 mAnimator.start(); } @Override public void drawState(Canvas canvas) { drawBackground(canvas); drawCircles(canvas); } }
4.绘制背景
直接绘制白色原先是在 onDraw 方法中,移到这边。
/** * 绘制背景(白色区域) * @param canvas */ private void drawBackground(Canvas canvas) { canvas.drawColor(mSplashBgColor); }
5.屏幕中心
很明显,大圆是在屏幕中心(更准确应该是说是在该 View 中心),所以需要计算屏幕中心的坐标。
// 屏幕正中心点坐标 private float mCenterX; private float mCenterY; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mCenterX = w/2f; mCenterY = h/2f; }
6.绘制小球
根据小球个数先算出相邻小球间的间隔角度,从而可以计算每个小球距离 0 角度的间隔角度,再加上旋转的角度,则为小球当前的角度。
// 大圆(里面包含很多小球的)的半径 private final float mRotationRadius = 90; // 每一个小球的半径 private final float mCircleRadius = 18; /** * 绘制小球 * @param canvas */ private void drawCircles(Canvas canvas) { //每个小球之间的间隔角度 = 2π/小圆的个数 float rotationAngle = (float) (2 * Math.PI / mCircleColors.length); for (int i=0; i < mCircleColors.length; i++){ /** * x = r * cos(a) +centerX * y= r * sin(a) + centerY * 每个小球 i * 间隔角度 + 旋转的角度 = 当前小球的真正角度 */ double angle = i*rotationAngle + mCurrentRotationAngle; float cx = (float) (mRotationRadius * Math.cos(angle) + mCenterX); float cy = (float) (mRotationRadius * Math.sin(angle) + mCenterY); mPaint.setColor(mCircleColors[i]); canvas.drawCircle(cx, cy ,mCircleRadius, mPaint); } }
7.效果
这时候运行代码:
小球开始不停的绕着大圈旋转,但是认真看会发现,旋转动画旋转一回后,会卡顿一下。这是由于我们旋转动画设置为重复执行,动画执行过程中,默认是线性的,但是,动画的衔接不会是线性的。这时候需要为动画添加线性插值器,这样可以使衔接过程也是线性。
mAnimator.setInterpolator(new LinearInterpolator());
六、聚合动画
1.切换动画
在开头搭建的时候已经预留了切换的方法,在数据加载完毕之后(用 Handler 延迟 5s 模拟)调用切换动画。
直接把要执行的动画 mState 指向聚合动画即可。
/** * 数据加载完成之后执行的动画 * 小球聚合和水波纹扩散 */ public void splashDisappear() { if (mState != null && mState instanceof RotateState) { mState.cancel(); post(new Runnable() { @Override public void run() { mState = new MergingState(); } }); } }
2.聚合动画
在旋转动画的时候,计算小球的坐标的时候,是根据大圆的半径进行计算,这边直接改变大圆的半径,从而达到聚合的效果。为了保持初始值,从新定义一个当前大圆的半径,用这个来进行计算,初始值为大圆默认半径。(在绘制小球 drawCircles 方法里也要同步改过来)
小球聚合前有一个向外扩散的效果,这里使用一个回荡秋千插值器 AnticipateInterpolator。
//当前大圆的半径 private float mCurrentRotationRadius = mRotationRadius; /** * 2.聚合动画 * 要素:大圆的半径不断地变大--变小----》小球的坐标 */ private class MergingState extends SplashState { public MergingState() { mAnimator = ValueAnimator.ofFloat(mRotationRadius, 0f); mAnimator.setDuration(mRotationDuration); //插值器实现先扩散再聚合效果 mAnimator.setInterpolator(new AnticipateInterpolator(6f)); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentRotationRadius = (float) animation.getAnimatedValue(); invalidate(); } }); mAnimator.start(); } @Override public void drawState(Canvas canvas) { drawBackground(canvas); drawCircles(canvas); } }
3.效果
七、扩散动画
1.切换动画
扩散动画是在聚合动画结束之后才开始执行,所以为聚合动画添加一个监听,当执行完毕之后,切换动画为扩散动画。
mAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mState = new ExpandState(); } });
2.分析
扩散动画是由中间一个小圆慢慢扩大,可以实现的方法很多,这边介绍个比较好的想法。
图中米黄色表示手机屏幕,中间彩色小圆为主界面,这是后加载界面是最外面一个大圆,被挖去中间一个小圆(圆环)。在这边把两个黑色圆圈组成的圆环理解为边沿很粗很粗的圆,则实际圆是圆环中间的那个圆(即黄色的圆)。 通过不断的缩小圆环的边沿,保持圆环的最大一个圆不变,从而使中间的主界面不断扩大。
3.屏幕对角线
需要计算出屏幕对角线一半的长度,即圆环的大圆半径。在 onSizeChanged 时候通过勾股定理计算出来。
//屏幕对角线一半 private float mDiagonalDist; @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mCenterX = w/2f; mCenterY = h/2f; mDiagonalDist = (float) Math.sqrt((w*w+h*h))/2f;//勾股定律 }
4.扩散动画
扩散动画变化的是中心要显示的主界面的圆半径大小,为了不显得变换的很突兀,扩散动画的初始显示大小设置为小球的大小。
//空心圆初始半径 private float mHoleRadius = 0F; /** * 3.水波纹扩散动画 * 画一个空心圆----画一个圆,让它的画笔的粗细变成很大---不断地减小画笔的粗细。 * 空心圆变化的范围:小球半径 ~ 对角线/2 */ private class ExpandState extends SplashState { public ExpandState() { //花1200ms,计算某个时刻当前的空心圆的半径是多少 mAnimator = ValueAnimator.ofFloat(mCircleRadius, mDiagonalDist); mAnimator.setDuration(mRotationDuration); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { //当前的空心圆的半径是多少? mHoleRadius = (float) valueAnimator.getAnimatedValue(); invalidate(); } }); mAnimator.start(); } @Override public void drawState(Canvas canvas) { drawBackground(canvas); } }
5.绘制背景
在扩散效果中,背景不能直接画为全白了,要画一个边沿很粗的圆。为绘制背景添加一个新的画笔,在 init 中进行初始化。
// 绘制背景的画笔 private Paint mPaintBackground = new Paint(); private void init(Context context) { mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors); //每个小球之间的间隔角度 = 2π/小圆的个数 rotationAngle = (float) (2 * Math.PI / mCircleColors.length); //画笔初始化 //消除锯齿 mPaint.setAntiAlias(true); mPaintBackground.setAntiAlias(true); //设置样式---边框样式--描边 mPaintBackground.setStyle(Paint.Style.STROKE); mPaintBackground.setColor(mSplashBgColor); } /** * 绘制背景(白色区域) * @param canvas */ private void drawBackground(Canvas canvas) { if(mHoleRadius>0f){ //得到画笔的宽度 = 对角线/2 - 空心圆的半径 float strokeWidth = mDiagonalDist - mHoleRadius; mPaintBackground.setStrokeWidth(strokeWidth); //画圆的半径 = 空心圆的半径 + 画笔的宽度/2 float radius = mHoleRadius + strokeWidth/2; canvas.drawCircle(mCenterX,mCenterY,radius,mPaintBackground); }else { canvas.drawColor(mSplashBgColor); } }
6.效果
到这里就结束了。
可以把动画执行时间,小球半径等属性提取出来作为自定义属性。
八、附
代码链接:http://download.csdn.net/download/qq_18983205/10006709
- (二十三)Animator 实例 —— 开场动画
- (二十一)Animator 实例 —— 刷鲜花效果
- Unity_2D游戏实例从零讲起(2)——手游开场动画的实现
- Android动画机制学习---animator(二)
- 【Unity】Unity中开场动画设置(二)
- Animator 动画器——参数解释
- Android——Animator笔记:属性动画
- Unity-动画(Animator)
- 项目实战①—高仿知乎日报(1)逼真的开场动画
- Android 动画之三 Property Animation—— 属性(Property)动画 【Animator提供基类】
- Android Animator(Android动画)
- Android Animator(Android动画)
- Animator参考(属性动画)
- Android最新动画框架完全解析(一)—— Animator(Property Animation)
- Unity基础——动画编辑Animations、Animator
- Android0919<二十三>(ViewGroup、Animation、Animator、Layout Animations)
- animator动画
- Animator动画
- codevs 1183 泥泞的道路 图论,spfa判环,二分答案
- C++bitset的基本使用方法
- plist 文件详解
- 解密回声消除技术之一(理论篇)
- 天天和不可描述
- (二十三)Animator 实例 —— 开场动画
- 微信公众号支付开发流程与避坑手册-Java篇
- 51nod 1055 最长等差数列
- 嵌入式多路温湿度监控系统(三主控程序之串口采集线程)
- <学习笔记>jar包置放在WEB-INF/lib下和通过build path导入的区别是什么
- Modbus协议学习(三)
- angular如何实现不同模块的变量共享
- 使用IOCP需要注意的一些问题~~(不断补充)
- 操作系统之计算机系统概述(1.1)一张图图解计算机系统基本构成