(二十四)自定义动画框架
2017-10-04 10:25
253 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
这边代码很简单,主要学习这个动画框架开发过程的思想,开发出来的动画框架便于使用,利于扩展。
ScrollView 下,里面的每个 Item 可以有一些动画效果,支持的动画有四种:
1.透明度变化
2.X 或 Y 方向缩放
3.颜色渐变
4.平移进场
通过监听 ScrollView 的滑动,调用对应 Item 的设置属性方法。Item 执行动画的程度跟这个 Item 从底部滑出来的高度有关,需要先计算 Item 滑出来多少。
每个 Item 执行的动画不一致,这边把 Item 要执行什么动画作为自定义属性配置在各自的 Item 上面,这些 Item 是系统自带的 View,如 TextView、ImageView 等。动画由 MyFrameLayout 去控制。
这时候布局文件样式为:
上面为了方便使用,扩展了 LinearLayout,让他自动为子 View 添加一个 MyFrameLayout,这时候自定义属性只能写在各个 Item 上,但是 Item 是系统的 View,自身无法识别到这些属性,所以是让 MyLinearLayout 去识别子 View 身上的属性。即重写 generateLayoutParams 方法。
同时,重写 MyLinearLayout 的 generateLayoutParams 方法。
在 MyLinearLayout 的 addView 方法中,把自定义属性保存到 MyFrameLayout 中。添加了一个判断是否有要执行的自定义动画,没有的话则不进行包裹。性能上的一点优化。
为了便于扩展,这边采用接口。
接口 DiscrollInterface:
MyFrameLayout 实现 DiscrollInterface:
这里比较复杂的就是计算最后一个 Item 滑出来的距离,从而确认执行动画的百分比 ratio。ratio = child 浮现的高度/ child 的高度,浮现的高度没有办法直接获取,只能用 ScrollView 的高度减去 Child 离 ScrollView 的顶部距离(红色箭头距离)再减去 ScrollView 滑出去的距离(绿色部分)。
注:在 onScrollChanged 中循环遍历对每一个子 View 都进行操作,实际上只需要对最后一个 Item 进行动画属性的设置,这边对所有的 Item 都进行了设置,在性能上实际是由一点影响的,正常情况下,这里的 Item 不会很多,如果说 Item 比较多的话,可以考虑像 ListView 记录最后一个 Item,在滑动的时候根据计算进行重新获取,每次绘制的时候只需要调用这个 Item 执行动画即可。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、效果
这边代码很简单,主要学习这个动画框架开发过程的思想,开发出来的动画框架便于使用,利于扩展。
二、分析
实现滚动是使用了 ScrollView,如果说要使用 ListView 的话,理论上也是可以的,但是 Item 类型比较多的时候,估计会比较复杂。ScrollView 下,里面的每个 Item 可以有一些动画效果,支持的动画有四种:
1.透明度变化
2.X 或 Y 方向缩放
3.颜色渐变
4.平移进场
通过监听 ScrollView 的滑动,调用对应 Item 的设置属性方法。Item 执行动画的程度跟这个 Item 从底部滑出来的高度有关,需要先计算 Item 滑出来多少。
每个 Item 执行的动画不一致,这边把 Item 要执行什么动画作为自定义属性配置在各自的 Item 上面,这些 Item 是系统自带的 View,如 TextView、ImageView 等。动画由 MyFrameLayout 去控制。
<LinearLayout> <MyFrameLayout discrollve:discrollve_scaleY="true" discrollve:discrollve_translation="fromLeft" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="20dp" android:layout_gravity="center" android:src="@drawable/camera" /> </MyFrameLayout> </LinearLayout>
三、包裹 Item
1.MyFrameLayout
先定义个 MyFrameLayout 包裹类,这个类包裹每一个 Item,并控制动画的执行。public class MyFrameLayout extends FrameLayout { public MyFrameLayout(@NonNull Context context) { super(context); } }
2.MyLinearLayout
考虑到再 XML 布局文件中, LinearLayout 下每一个 Item 都需要在外添加一个 MyFrameLayout 才可以,这样使用起来较为麻烦,所以 自定义 MyLinearLayout 扩展自 LinearLayout,默认为子 View 添加一个 MyFrameLayout。public class MyLinearLayout extends LinearLayout { public MyLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); //设置排版为竖着 setOrientation(VERTICAL); } @Override public void addView(View child, ViewGroup.LayoutParams params) { MyFrameLayout mf = new MyFrameLayout(getContext()); mf.addView(child); super.addView(mf, params); } }
这时候布局文件样式为:
<MyLinearLayout> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="20dp" android:layout_gravity="center" android:src="@drawable/camera" discrollve:discrollve_scaleY="true" discrollve:discrollve_translation="fromLeft" /> </MyLinearLayout>
四、自定义属性
自定义属性 attrs.xml<?xml version="1.0" encoding="UTF-8"?> <resources> <declare-styleable name="DiscrollView_LayoutParams"> <attr name="discrollve_alpha" format="boolean"/> <attr name="discrollve_scaleX" format="boolean"/> <attr name="discrollve_scaleY" format="boolean"/> <attr name="discrollve_fromBgColor" format="color"/> <attr name="discrollve_toBgColor" format="color"/> <attr name="discrollve_translation"/> </declare-styleable> <attr name="discrollve_translation"> <flag name="fromTop" value="0x01" /> <flag name="fromBottom" value="0x02" /> <flag name="fromLeft" value="0x04" /> <flag name="fromRight" value="0x08" /> </attr> </resources>
上面为了方便使用,扩展了 LinearLayout,让他自动为子 View 添加一个 MyFrameLayout,这时候自定义属性只能写在各个 Item 上,但是 Item 是系统的 View,自身无法识别到这些属性,所以是让 MyLinearLayout 去识别子 View 身上的属性。即重写 generateLayoutParams 方法。
1.自定义 LayoutParams
public class MyLayoutParams extends LinearLayout.LayoutParams{ public int mDiscrollveFromBgColor;//背景颜色变化开始值 public int mDiscrollveToBgColor;//背景颜色变化结束值 public boolean mDiscrollveAlpha;//是否需要透明度动画 public int mDisCrollveTranslation;//平移值 public boolean mDiscrollveScaleX;//是否需要x轴方向缩放 public boolean mDiscrollveScaleY;//是否需要y轴方向缩放 public MyLayoutParams(Context c, AttributeSet attrs) { super(c, attrs); //解析attrs得到自定义的属性,保存 TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.DiscrollView_LayoutParams); mDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false); mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false); mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false); mDisCrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1); mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1); mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1); a.recycle(); } }
同时,重写 MyLinearLayout 的 generateLayoutParams 方法。
@Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MyLayoutParams(getContext(),attrs); }
2.MyFrameLayout 保存自定义属性
为 MyFrameLayout 添加自定义属性,生成 set 方法。同时,重写 onSizeChanged 方法,记录宽高。private static final int TRANSLATION_FROM_TOP = 0x01; private static final int TRANSLATION_FROM_BOTTOM = 0x02; private static final int TRANSLATION_FROM_LEFT = 0x04; private static final int TRANSLATION_FROM_RIGHT = 0x08; //颜色估值器 private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator(); /** * 自定义属性的一些接收的变量 */ private int mDiscrollveFromBgColor;//背景颜色变化开始值 private int mDiscrollveToBgColor;//背景颜色变化结束值 private boolean mDiscrollveAlpha;//是否需要透明度动画 private int mDisCrollveTranslation;//平移值 private boolean mDiscrollveScaleX;//是否需要x轴方向缩放 private boolean mDiscrollveScaleY;//是否需要y轴方向缩放 private int mHeight;//本view的高度 private int mWidth;//宽度 public void setmDiscrollveFromBgColor(int mDiscrollveFromBgColor) { this.mDiscrollveFromBgColor = mDiscrollveFromBgColor; } public void setmDiscrollveToBgColor(int mDiscrollveToBgColor) { this.mDiscrollveToBgColor = mDiscrollveToBgColor; } public void setmDiscrollveAlpha(boolean mDiscrollveAlpha) { this.mDiscrollveAlpha = mDiscrollveAlpha; } public void setmDisCrollveTranslation(int mDisCrollveTranslation) { this.mDisCrollveTranslation = mDisCrollveTranslation; } public void setmDiscrollveScaleX(boolean mDiscrollveScaleX) { this.mDiscrollveScaleX = mDiscrollveScaleX; } public void setmDiscrollveScaleY(boolean mDiscrollveScaleY) { this.mDiscrollveScaleY = mDiscrollveScaleY; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; }
在 MyLinearLayout 的 addView 方法中,把自定义属性保存到 MyFrameLayout 中。添加了一个判断是否有要执行的自定义动画,没有的话则不进行包裹。性能上的一点优化。
@Override public void addView(View child, ViewGroup.LayoutParams params) { MyLayoutParams p = (MyLayoutParams) params; if(!isDiscrollvable(p)){//判断是否有自定义属性,没有则不包裹一层容器 super.addView(child,params); }else { //偷天换日 MyFrameLayout mf = new MyFrameLayout(getContext()); mf.addView(child); mf.setmDiscrollveAlpha(p.mDiscrollveAlpha); mf.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor); mf.setmDiscrollveToBgColor(p.mDiscrollveToBgColor); mf.setmDiscrollveScaleX(p.mDiscrollveScaleX); mf.setmDisCrollveTranslation(p.mDisCrollveTranslation); super.addView(mf, params); } } /** * 是否要执行自定义动画 * @param p * @return */ private boolean isDiscrollvable(MyLayoutParams p){ return p.mDiscrollveAlpha|| p.mDiscrollveScaleX|| p.mDiscrollveScaleY|| p.mDisCrollveTranslation!=-1|| (p.mDiscrollveFromBgColor!=-1&& p.mDiscrollveToBgColor!=-1); }
五、动画
1.封装动画方法
在上面已经把要执行的动画属性传给了 MyFrameLayout,为 MyFrameLayout 实现两个方法,设置属性值和初始化。为了便于扩展,这边采用接口。
接口 DiscrollInterface:
public interface DiscrollInterface { /** * 当滑动的时候调用该方法,用来控制里面的控件执行相应的动画 * @param ratio 动画执行的百分比(child view画出来的距离百分比) */ void onDiscroll(float ratio); /** * 重置动画--让view所有的属性都恢复到原来的样子 */ void onResetDiscroll(); }
MyFrameLayout 实现 DiscrollInterface:
public class MyFrameLayout extends FrameLayout implements DiscrollInterface{ ... @Override public void onDiscroll(float ratio) { //执行动画ratio:0~1 if(mDiscrollveAlpha){ setAlpha(ratio); } if(mDiscrollveScaleX){ setScaleX(ratio); } if(mDiscrollveScaleY){ setScaleY(ratio); } //平移动画 int值:left,right,top,bottom left|bottom if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//是否包含bottom setTranslationY(mHeight*(1-ratio));//height--->0(0代表恢复到原来的位置) } if(isTranslationFrom(TRANSLATION_FROM_TOP)){//是否包含bottom setTranslationY(-mHeight*(1-ratio));//-height--->0(0代表恢复到原来的位置) } if(isTranslationFrom(TRANSLATION_FROM_LEFT)){ setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0代表恢复到本来原来的位置) } if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){ setTranslationX(mWidth*(1-ratio));//-mWidth--->0(0代表恢复到本来原来的位置) } //判断从什么颜色到什么颜色 if(mDiscrollveFromBgColor!=-1&&mDiscrollveToBgColor!=-1){ setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor)); } } private boolean isTranslationFrom(int translationMask){ if(mDisCrollveTranslation ==-1){ return false; } //fromLeft|fromeBottom & fromBottom = fromBottom return (mDisCrollveTranslation & translationMask) == translationMask; } @Override public void onResetDiscroll() { if(mDiscrollveAlpha){ setAlpha(1); } if(mDiscrollveScaleX){ setScaleX(1); } if(mDiscrollveScaleY){ setScaleY(1); } //平移动画 int值:left,right,top,bottom left|bottom if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//是否包含bottom setTranslationY(0);//height--->0(0代表恢复到原来的位置) } if(isTranslationFrom(TRANSLATION_FROM_TOP)){//是否包含bottom setTranslationY(0);//-height--->0(0代表恢复到原来的位置) } if(isTranslationFrom(TRANSLATION_FROM_LEFT)){ setTranslationX(0);//mWidth--->0(0代表恢复到本来原来的位置) } if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){ setTranslationX(0);//-mWidth--->0(0代表恢复到本来原来的位置) } } }
六、ScrollView 监听
到这里,就缺一个滑动时候对执行动画的监听,为了方便使用,扩展 ScrollView ,实现滑动监听执行动画效果。这里比较复杂的就是计算最后一个 Item 滑出来的距离,从而确认执行动画的百分比 ratio。ratio = child 浮现的高度/ child 的高度,浮现的高度没有办法直接获取,只能用 ScrollView 的高度减去 Child 离 ScrollView 的顶部距离(红色箭头距离)再减去 ScrollView 滑出去的距离(绿色部分)。
public class MyScrollView extends ScrollView { private MyLinearLayout mContent; public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); mContent = (MyLinearLayout) getChildAt(0); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //为了好看,让第一个子 View 占满 View first = mContent.getChildAt(0); first.getLayoutParams().height = getHeight(); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); int scrollViewHeight = getHeight(); for (int i=0;i<mContent.getChildCount();i++){ View child = mContent.getChildAt(i); int childHeight = child.getHeight(); if(!(child instanceof DiscrollInterface)){ continue; } DiscrollInterface discrollInterface = (DiscrollInterface) child; //child离parent顶部的高度 int childTop = child.getTop(); //滑出去的这一截高度:t (t 为负的) //child离屏幕顶部的高度 int absoluteTop = childTop - t; if(absoluteTop <= scrollViewHeight) { //child浮现的高度 = ScrollView 的高度 - child 离屏幕顶部的高度 int visibleGap = scrollViewHeight - absoluteTop; //float ratio = child浮现的高度/child的高度 float ratio = visibleGap / (float) childHeight; //确保ratio是在0~1的范围 discrollInterface.onDiscroll(clamp(ratio, 1f, 0f)); }else{ discrollInterface.onResetDiscroll(); } } } /** * 求三个数的中间大小的一个数 * @param value 输入的值 * @param max 最大值限制 * @param min 最小值限制 */ public static float clamp(float value, float max, float min){ return Math.max(Math.min(value, max), min); } }
注:在 onScrollChanged 中循环遍历对每一个子 View 都进行操作,实际上只需要对最后一个 Item 进行动画属性的设置,这边对所有的 Item 都进行了设置,在性能上实际是由一点影响的,正常情况下,这里的 Item 不会很多,如果说 Item 比较多的话,可以考虑像 ListView 记录最后一个 Item,在滑动的时候根据计算进行重新获取,每次绘制的时候只需要调用这个 Item 执行动画即可。
七、附
代码链接:http://download.csdn.net/download/qq_18983205/10008170相关文章推荐
- 自定义present动画从左到右 ios推送详解、常用的十种xcode开源框架
- Android自定义曲线路径动画框架
- 【Android进阶】如何写一个很屌的动画(1)---先实现一个简易的自定义动画框架
- andbase框架实现上拉加载,下拉刷新和自定义旋转动画的方式
- qt动画框架(三) 自定义tab滑动导航栏
- Android之自定义动画框架实现ScrollView滚动动画总结(雷惊风)
- Android自定义动画框架让View实现Path动画
- 关于RecyclerView的下拉刷新,自定义帧动画,第三方框架PtrFrameLayout使用手册
- Glide图片框架使用详细介绍(四)自定义动画
- 自定义动画框架
- 最新 21 款Android 自定义View及炫酷动画开源框架,总有一款适合你!
- 前端框架Aurelia - 自定义组件
- Android基于Facebook Rebound的动画效果框架Backboard demo (非常炫酷)
- 用jQuery实现自定义动画
- Swift中设置自定义tabBar按钮的动画效果
- Android自定义View-仿华为手机管家病毒查杀类似于雷达扫描动画效果
- c#网络通信框架networkcomms内核解析之十 支持优先级的自定义线程池
- android 自定义动画 圆呼吸效果 ValueAnimator
- 【Android 基础】利用Theme自定义Activity间的切换动画
- 浅析 Android 动画:自定义 Interpolator 与 TypeEvaluator