动画有多么重要,相信大家都清楚。它可以让一个枯燥乏味的静态界面变成一个充满动力的动画世界,提高用户体验。反正现在都是用户体验至上。android也是前端。苦逼的大前端。想想之前刚毕业的时候搞javaWeb,那个时候感觉前端好low。。现在不这么认为了。
废话不多少。直接上效果图
主要就是中间那部分的动画效果。
在理解Android中动画实现的本质之前,首先要理解动画实现的原理,估计这个大家都清楚。
如果要在Android中实现动画展示,那么就必须要有一个“动画驱动”每隔1/24秒去调用View的draw()方法,同时改变每一帧中View需要变化的元素,让这个View不断的绘制,这样一来,所有变化就是组合成一个流畅的动画。
上面就是“Android中动画实现的本质”,其关键就是要有一个“动画驱动”。回想下我们平时最常用的动画类Animation或者Animator,其实它们内部实现也是一个“动画驱动”,驱动View不断绘制。所以,我们完全可以不用Animation或者Animator去做动画,只要有一个“驱动”即可,例如Scroller是个不错的选择,甚至我们可以写一个我们自己实现的“动画驱动”。
View 本身
view本身的onDraw()马上会触发下一次绘制。
class MyView extends View { public void onDraw(Canvas canvas) { super.onDraw(canvas); invalidate(); }}View动画,属性动画(Animation/Animator)
上面的部分就是使用属性动画.Scroller
这个在刚开始的时候滑动主要就靠这个类。郭神的。医生的,还有爱哥的这些书中都有讲到。博客也有很多。它需要结合View的computeScroll()方法实现。
自己实现一个简易的“动画驱动”
既然有些需求用原有的方法难以实现或者实现起来不太合适,这个时候我们就需要自己动手了。因此,我也写了一个简易的“动画驱动”
其实就是自己把 path 的那些 moveTo ,lineTo 这些方法封装了下。勿喷。
PathPoint类
/** * des:具体的路径集合 * author: marc * date: 2017/2/8 11:31 * email:aliali_ha@yeah.net */public class PathPoint { //移动指令 public static final int MOVE = 0; //直线运动 public static final int LINE = 1; //贝塞尔曲线 public static final int CURVE = 2; //当前指令 int mOperation; float mX; float mY; float mControl0X, mControl1X;//2个拐点 float mControl0Y, mControl1Y; PRivate PathPoint(int operation, float x, float y) { mOperation = operation; mX = x; mY = y; } private PathPoint(float c0x, float c0y, float c1x, float c1y, float x, float y) { mOperation = CURVE; //终点 mX = x; mY = y; mControl0X = c0x; mControl0Y = c0y; mControl1X = c1x; mControl1Y = c1y; } /** * 移动 * * @param x * @param y * @return */ public static PathPoint moveTo(float x, float y) { return new PathPoint(MOVE, x, y); } /** * 直线 * * @param x * @param y * @return */ public static PathPoint lineTo(float x, float y) { return new PathPoint(LINE, x, y); } /** * 贝塞尔曲线 * * @return */ public static PathPoint curveTo(float c0x, float c0y, float c1x, float c1y, float x, float y) { return new PathPoint(c0x, c0y, c1x, c1y, x, y); }}AnimatorPath类
/** * des:动画路径 * author: marc * date: 2017/2/8 11:26 * email:aliali_ha@yeah.net */public class AnimatorPath { //存储路径集合 ArrayList<PathPoint> mPoints = new ArrayList<>(); /** * 移动到哪个位置 * * @param x * @param y */ public void moveTo(float x, float y) { mPoints.add(PathPoint.moveTo(x, y)); } //直线 public void lineTo(float x, float y) { mPoints.add(PathPoint.lineTo(x, y)); } //贝塞尔曲线 public void curveTo(float c0x, float c0y, float c1x, float c1y, float x, float y) { mPoints.add(PathPoint.curveTo(c0x, c0y, c1x, c1y, x, y)); } public Collection<PathPoint> getPoints() { return mPoints; } public void clear() { if (mPoints != null && mPoints.size() > 0) { mPoints.clear(); } }}PathEvaluator类 主要实现了move line 贝塞尔曲线这些方法
/** * des:自定义估值器 * author: marc * date: 2017/2/8 13:06 * email:aliali_ha@yeah.net */public class PathEvaluator implements TypeEvaluator<PathPoint> { /** * @param t 动画执行的百分比 ,其实就是时间 * @param startValue * @param endValue * @return */ @Override public PathPoint evaluate(float t, PathPoint startValue, PathPoint endValue) { //进行估值 float x, y; //判断进行哪种运动 if (endValue.mOperation == PathPoint.CURVE) { //贝塞尔曲线方式 float oneMinusT = 1 - t; //x的实时坐标 x = oneMinusT * oneMinusT * oneMinusT * startValue.mX + 3 * oneMinusT * oneMinusT * t * endValue.mControl0X + 3 * oneMinusT * t * t * endValue.mControl1X + t * t * t * endValue.mX; //y的实时坐标 y = oneMinusT * oneMinusT * oneMinusT * startValue.mY + 3 * oneMinusT * oneMinusT * t * endValue.mControl0Y + 3 * oneMinusT * t * t * endValue.mControl1Y + t * t * t * endValue.mY; } else if (endValue.mOperation == PathPoint.LINE) { //直线运动方式 //当前坐标点(x,y) = 起始点 +t*起始点和终点的距离 x = startValue.mX + t * (endValue.mX - startValue.mX); y = startValue.mY + t * (endValue.mY - startValue.mY); } else { //moveto方式 x = endValue.mX; y = endValue.mY; } //不断的把控制点 move到 x,y的位置 return PathPoint.moveTo(x, y); }}Activity类
/** * des: * author: marc * date: 2017/2/8 09:53 * email:aliali_ha@yeah.net */public class BezierActivity extends AppCompatActivity { private static final long ANIMATION_DUARTION = 400;//运动时间 private static final float MINIMUN_X_DISTANCE = 200;//x轴运动距离 private static final float SCALE_FACTOR_EXPAND = 13;//扩大倍数 private static final float SCALE_FACTOR_ORI = 1;//最初的倍数 ImageButton mFab; FrameLayout mFabContainer;//帧布局 LinearLayout mControlsContainer; private int mFabSize;//ImageButton的大小 private boolean mRevealFlag; private boolean mResetFlag; private float startX; private float startY; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_bezier); mFabSize = getResources().getDimensionPixelSize(R.dimen.fab_size); bindViews(); } private void bindViews() { mFab = (ImageButton) findViewById(R.id.fab); mFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onFabPressed(v); } }); mFabContainer = (FrameLayout) findViewById(R.id.fab_container); mControlsContainer = (LinearLayout) findViewById(R.id.media_controls_container); } public void onFabPressed(View view) { //还没运动前的X坐标 startX = mFab.getX(); startY = mFab.getY(); //开启动画 使用属性动画 属性动画控制对象身上的任何属性值 (必须有set方法) AnimatorPath mPath = new AnimatorPath(); mPath.moveTo(0, 0); //贝塞尔曲线 mPath.curveTo(-200, 200, -400, 100, -600, 0); //相对于原来移动的点 //填this是控制当前对象(当前是BezierActivity)的PathPoint p 这个属性 。 mPath.getPoints()是相当于从某个值到某个值 ObjectAnimator animator = ObjectAnimator.ofObject(this, "fabLocation", new PathEvaluator(), mPath.getPoints().toArray()); animator.setDuration(ANIMATION_DUARTION); //设置加速 animator.start(); //水波纹效果 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //X轴运动距离超过200 开始水波纹扩散 if (Math.abs(startX - mFab.getX()) > MINIMUN_X_DISTANCE) { if (!mRevealFlag) { //高版本 api 运动 //已经设置成透明的了。设置回来 mFab.setImageDrawable(new BitmapDrawable()); mFabContainer.setY(mFabContainer.getY() + mFabSize / 2); mFab.animate() .scaleX(SCALE_FACTOR_EXPAND) .scaleY(SCALE_FACTOR_EXPAND) .setListener(endListener) .setDuration(ANIMATION_DUARTION) .start(); mRevealFlag = true; mResetFlag = false; } } } }); } //反射 不用直接设置属性 PathPoint fabLocation; public void setFabLocation(PathPoint fabLocation) { //达到不断的控制view进行移动 mFab.setTranslationX(fabLocation.mX); mFab.setTranslationY(fabLocation.mY); } //开始动画。效果跟点击开始按钮一样 public void startAnimator(View view) { onFabPressed(view); } /** * fab做正向移动时的listerner */ private AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); } @Override public void onAnimationEnd(Animator animation) { mFab.setVisibility(View.INVISIBLE); mFabContainer.setBackgroundColor(getResources().getColor(R.color.brand_accent)); for (int i = 0; i < mFabContainer.getChildCount(); i++) { View v = mControlsContainer.getChildAt(i); ViewPropertyAnimator animator = v.animate().scaleX(1).scaleY(1).setDuration(ANIMATION_DUARTION); //依次显示 animator.setStartDelay(i * 50).start(); } } }; /** * 重置动画 1.水波纹 * 2. framelayout布局上移 * 3. fab做位移 * * @param view */ public void reset(View view) { //1.先隐藏 for (int i = 0; i < mFabContainer.getChildCount() + 1; i++) { View v = mControlsContainer.getChildAt(i); ViewPropertyAnimator animator = v.animate().scaleX(0).scaleY(0).setDuration(ANIMATION_DUARTION); //依次显示 animator.setStartDelay(i * 50); //最后一个动画的时候监听动画结束 ,开始显示fab。然后进行缩放 if (i == mControlsContainer.getChildCount() - 1) { animator.setListener(reverseListener); } animator.start(); } AnimatorPath mPath1 = new AnimatorPath(); mPath1.moveTo(-600, 0); mPath1.lineTo(0, 0); //相对于原来移动的点 //填this是控制当前对象(当前是BezierActivity)的PathPoint p 这个属性 。 mPath.getPoints()是相当于从某个值到某个值 ObjectAnimator fabLocation = ObjectAnimator.ofObject(this, "fabLocation", new PathEvaluator(), mPath1.getPoints().toArray()); fabLocation.setDuration(ANIMATION_DUARTION); //设置加速 fabLocation.start(); } /** * 2.结束的时候fab显示。然后进行缩放 */ private AnimatorListenerAdapter reverseListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { //结束的时候背景显示成透明 mFabContainer.setBackgroundColor(getResources().getColor(android.R.color.transparent)); //ImageButton设置成显示状态 mFab.setVisibility(View.VISIBLE); //2. ImageButton开始缩放 从13-1的缩放 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 13, 1); PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 13, 1); ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mFab, scaleX, scaleY); objectAnimator.setDuration(ANIMATION_DUARTION); objectAnimator.addListener(listenerAdapter); objectAnimator.start(); } }; private AnimatorListenerAdapter listenerAdapter = new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //ImageButton缩放动画结束的时候 if (!mResetFlag) { mFabContainer.setY(mFabContainer.getY() - mFabSize / 2); mFab.setImageBitmap(BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_media_pause)); mResetFlag = true; mRevealFlag = false; } } };}同时上传到github
地址: https://github.com/Xiemarc/DesignPatterns
新闻热点
疑难解答