利用属性动画实现一个不一样的SplashView
2017-07-06 15:56
369 查看
直接上效果
![](https://img-blog.csdn.net/20170706180326748?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl1amlhbl8zMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这个效果是在学属性动画时做的一个效果,现在就当复习了,顺便记录下来。
自定义View重写onDraw方法,用属性动画控制旋转和缩放效果。
动画还是比较简单的可以分为三个过程:
state1.六个小球的旋转
state2.小球向中间收缩
state3.展开显示出下面的内容
我们可以想象成这种场景:我们从服务器请求数据时,开始让小球执行旋转动画,当数据请求成功加载完成后执行收缩动画和展开动画显示内容。
这里我们小小用了一下设计模式——策略模式,我们把每个state定义成一个类,让每个类去实现它自己的动画。
首先定一个抽象类,里面定一个drawState方法。
然后我们有三个状态,定义三个类,分别实现我们的SplashState
重写drawState方法;
旋转状态 ⬇️
收缩状态⬇️
>
展开状态⬇️
这个效果是在学属性动画时做的一个效果,现在就当复习了,顺便记录下来。
自定义View重写onDraw方法,用属性动画控制旋转和缩放效果。
动画还是比较简单的可以分为三个过程:
state1.六个小球的旋转
state2.小球向中间收缩
state3.展开显示出下面的内容
我们可以想象成这种场景:我们从服务器请求数据时,开始让小球执行旋转动画,当数据请求成功加载完成后执行收缩动画和展开动画显示内容。
这里我们小小用了一下设计模式——策略模式,我们把每个state定义成一个类,让每个类去实现它自己的动画。
首先定一个抽象类,里面定一个drawState方法。
private SplashState mState; private abstract class SplashState{ public abstract void drawState(Canvas canvas); }
然后我们有三个状态,定义三个类,分别实现我们的SplashState
重写drawState方法;
旋转状态 ⬇️
public class RotateState extends SplashState{ @Override public void drawState(Canvas canvas) { } }
收缩状态⬇️
public class MergeState extends SplashState{ @Override public void drawState(Canvas canvas) { } }4000
>
展开状态⬇️
public class ExpandState extends SplashState{ @Override public void drawState(Canvas canvas) { } }
这样一来每种模式的动画都有它们自己去实现了,在我们的onDraw方法里就不用if else去判断三种模式了,可以这样写:@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(mState==null){ mState = new RotateState(); } mState.drawState(canvas); }
接下来就挨个分析每种状态了,首先旋转状态:public class RotateState extends SplashState{ @Override public void drawState(Canvas canvas) { drawBackground(canvas); drawCircles(canvas); } }
drawState里就是画背景和画圆
drawBackground画背景private void drawBackground(Canvas canvas) { canvas.drawColor(mBackgroundColor); }
drawCircles画圆private void drawCircles(Canvas canvas) { float spaceAngle = (float) (Math.PI * 2 / mCircleColors.length); for (int i=0;i<mCircleColors.length;i++){ double angle = spaceAngle * i + mRotateAngle; float cx = (float) (mCenterX + mCurrentRotateRadius * Math.cos(angle)); float cy = (float) (mCenterY + mCurrentRotateRadius * Math.sin(angle)); mCirclePaint.setColor(mCircleColors[i]); canvas.drawCircle(cx,cy,mCircleRadius,mCirclePaint); } }
六个圆按一周平均分布,spaceAngle是每个圆之间间隔角度,mCenterX,mCenterY是中心点的坐标,cx,cy是最终要确定的每个小圆的圆心,很简单的计算得到cx,cy的值,mCircleRadius为小圆的半径,通过canvas的drawCircle方法来依次画圆。mRotateAngle是用来控制小圆旋转用的,用ValueAnimaor使它在0到Math.PI*2之间变化,从而改变cx,cy的坐标,以达到旋转的效果。
现在mCurrentRotateRadius的值是小圆围绕着旋转的大圆的半径。
看一下控制mRotateAngle角度变化的代码,可以在RotateState的构造方法里执行。public RotateState(){ animator = ValueAnimator.ofFloat(0,(float) Math.PI * 2f); animator.setDuration(mRoatationDuration); animator.setInterpolator(new LinearInterpolator()); animator.setRepeatCount(ValueAnimator.INFINITE); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mRotateAngle = (float) valueAnimator.getAnimatedValue(); invalidate(); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); setVisibility(View.VISIBLE); } }); animator.start(); }
这里加了一个线线性插值器,如果不加的话,动画旋转一圈后会停一下,然后再旋转,效果不流畅。而且还设置了RepeatCount值是INFINITE,无限循环。因为停止旋转动画并开启后面的收缩和展开动画需要通过外部设置。提供一个给外部调用的接口,停止旋转动画,开启收缩动画。public void splashDismiss(){ animator.cancel(); if(mState instanceof RotateState){ mState = new MergeState(); } }
我们在MainActivity中用Handler模拟加载数据,在3秒后调用splashDismiss开启后面的动画。public class MainActivity extends AppCompatActivity { private SplashView splash; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); splash = (SplashView) findViewById(R.id.splash); handler.postDelayed(new Runnable() { @Override public void run() { splash.splashDismiss(); } },3000); } }
MainActivity的布局的代码很简单,<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" tools:context="com.example.splashview.MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/content"> </LinearLayout> <com.example.splashview.SplashView android:id="@+id/splash" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
这时就要开始执行收缩动画MergeState了,MergeState的drawState方法。@Override public void drawState(Canvas canvas) { drawBackground(canvas); drawCircles(canvas); }
这里跟RotateState的drawState是一样的也调用了drawCircles方法,因为收缩只需要用ValueAnimator改变大圆的半径mCurrentRotateRadius就好了,在MergeState的构造方法里public MergeState(){ animator = ValueAnimator.ofFloat(0,mRotationRadius); animator.setDuration(mMergeDuration); animator.setInterpolator(new OvershootInterpolator(10)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mCurrentRotateRadius = (float) valueAnimator.getAnimatedValue(); invalidate(); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mState = new ExpandState(); } }); animator.reverse(); }
这里的收缩有一个overshoot的效果,先向外弹射一段距离,再收缩回来,用了OvershootInterpolator插值器。在动画结束后要开启展开动画ExpandState。drawState方法@Override public void drawState(Canvas canvas) { drawBackground(canvas); }
展开动画对背景进行了操作,当然我们肯定要对drawBackground方法进行修改了。private void drawBackground(Canvas canvas) { if(mHoleRadius>0){ float mBgPaintStroke = mDiagnoalDist - mHoleRadius; float radius = mHoleRadius + mBgPaintStroke / 2; mBackgroundPaint.setStrokeWidth(mBgPaintStroke); canvas.drawCircle(mCenterX,mCenterY,radius,mBackgroundPaint); }else { canvas.drawColor(mBackgroundColor); } }
这里很巧妙的利用改变Paint的StrokeWidth的方法实现这种效果,画背景我们也可以当成是画了一个很大的圆,把屏幕都覆盖了,我们把画笔的宽度设置成了屏幕对角线距离的一半,然后来让画笔的宽度满满减小,对应的也就是中间空心圆的半径mHoleRadius慢慢变大,计算出画圆的半径radius,
我们用ValueAnimator来控制mHoleRadius使其慢慢变大,同样也是在ExpandState的构造方法里。public ExpandState(){ animator = ValueAnimator.ofFloat(0,mDiagnoalDist); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mHoleRadius = (float) animation.getAnimatedValue(); invalidate(); } }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); setVisibility(View.GONE); } }); animator.setDuration(800); animator.start(); }
除了上面那种通过改变Paint的StrokeWidth来实现效果,还有一种就是通过设置Paint的Xfermode来达到展开效果,用CLEAR模式就可以轻松实现,这时的drawBackground方法修改如下private void drawBackground(Canvas canvas) { canvas.drawColor(mBackgroundColor); if(mHoleRadius>0){ mHolePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); canvas.drawCircle(mCenterX,mCenterY,mHoleRadius,mHolePaint); mHolePaint.setXfermode(null); } }
用xfermode实现看来
a975
稍简单点,不用计算,不了解xfermode的可以去了解下,CLEAR模式是最好理解的一种。
最后再贴一下定义的变量和一些初始化的代码private ValueAnimator animator; //小圆的半径 private float mCircleRadius = 20; //大圆的半径 (小圆围绕大圆旋转形成的) private float mRotationRadius = 100; //小圆旋转收缩时的半径 private float mCurrentRotateRadius = mRotationRadius; //小圆的颜色 private int[] mCircleColors; //小圆围绕旋转的时间 private long mRoatationDuration = 1200; //收缩的时间 private long mMergeDuration = 400; //旋转角度 private float mRotateAngle = 0f; //画小圆用的画笔 private Paint mCirclePaint; //画背景的画笔 private Paint mBackgroundPaint; //背景的颜色 private int mBackgroundColor = Color.WHITE; //中心点的坐标 private float mCenterX; private float mCenterY; //对角线的距离的一半 private float mDiagnoalDist; //展开动画时中间空心圆的半径 private float mHoleRadius = 0f; //用xfermode CLEAR模式时 画空心圆的画笔 private Paint mHolePaint; public SplashView(Context context) { this(context,null); } public SplashView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE); mCircleColors = context.getResources().getIntArray(R.array.splash_circle_colors); mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBackgroundPaint.setColor(Color.WHITE); mBackgroundPaint.setStyle(Paint.Style.STROKE); mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mHolePaint.setStyle(Paint.Style.FILL); mHolePaint.setColor(mBackgroundColor); setLayerType(LAYER_TYPE_SOFTWARE,null); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mCenterX = w/2f; mCenterY = h/2f; mDiagnoalDist = (float) (Math.sqrt(w*w+h*h) / 2); }
好了。效果实现了。总体来说不难。
相关文章推荐
- 利用属性动画Animator实现的一个小demo,
- 利用<div>层 和<img>标签,实现一个图片两个动画效果
- 利用POPAnimatableProperty属性来实现动画倒计时
- Android中属性动画3----ObjectAnimator实现一个动画菜单
- 利用属性动画轻松实现 Android TV 游标动画,你缺的是几行代码
- android 利用属性动画实现酷炫的圆形菜单
- 利用JAVA的动态属性之反射原理实现一个简单AOP容器 - AOP的实现原理分析
- 利用JavaFX实现一个弹球动画
- 利用Android属性动画实现Banner的原理与实践
- 利用属性动画实现动态菜单的效果
- 利用Android属性动画实现Banner控件
- 利用transition属性实现一个简单小巧的hover效果
- 利用MovieClip对象的rotationY属性实现翻转动画效果
- 利用Android属性动画实现Banner的原理与实践
- 利用layer的mask属性实现逐渐揭示的动画效果,layermask
- 利用RecyclerView实现的一个动画给变item的位置和左右滑动删除该RcyclervView的Item条目
- 利用属性动画实现优酷菜单
- 利用KVC写的一个分类实现自动生成模型属性代码
- css3 实现一个改变元素属性实现的动画效果
- 使用属性动画实现一个简单的加载动画