Android魔法(第四弹)—— 一步步实现百叶窗效果
目录
1、效果展示
2、实现AnimationViewInterface接口
3、解析动画组成
4、翻转单元——RotateView
1)前景背景图
2)实现翻转
3)实现翻转动画
5、百叶窗——BlindsView
1)初始化图片矩阵
2)手动翻转百叶窗
3)自动翻转百叶窗
6、总结一下
源码:
本篇是基于AnimationListView框架的,这个框架在上一篇中详细的讲解了,建议阅读本篇前先熟悉Android魔法(第三弹)—— 一步步实现对折页面。
1、效果展示
2、实现AnimationViewInterface接口
public class BlindsView extends LinearLayout implements AnimationViewInterface{
3、解析动画组成
4、翻转单元——RotateView
public class RotateView extends ImageView {
1)前景背景图
public void setBitmap(Bitmap frontBitmap, Bitmap backBitmap){if(frontBitmap == null){return;}mFrontBitmap = frontBitmap;mBackBitmap = backBitmap;mRotatedBackBitmap = null;setImageBitmap(frontBitmap);setScaleType(ScaleType.FIT_XY);//初始化翻转角度setRotationX(0);setRotationY(0);
}
代码比较简单,默认显示前景图。
其中有个mRotateBackBitmap,我们后面会讲。
2)实现翻转
public void setRotation(float value, boolean isRotateX){//设置翻转角度if(isRotateX){setRotationX(value);}else {setRotationY(value);}//将角度转换为0-360之间,以便后面判断float rotate = value % 360;if (rotate < 0) {rotate += 360;}/*** 设置缩放:当向垂直翻转时缩小,反之恢复* 缩放的主要原因是在翻转时,图像会变形为梯形,这时图片中心轴保持原来的宽度,* 则向上翻转那边会变大,部分图像会超出无法显示。所以这里用缩放处理一下,* 至于缩放大小,根据实际需求改变。*/float scale = rotate > 180 ? Math.abs(rotate - 270) : Math.abs(rotate - 90);scale = scale / 90 * (1 - mScaleMin) + mScaleMin;if(isRotateX){setScaleX(scale);}else{setScaleY(scale);}//根据翻转的位置,设置前景/背景图片if(mBackBitmap != null) {if(mRotatedBackBitmap == null || this.isRotateX != isRotateX) {/*** 首先会根据翻转的方向,对背景图片进行一次翻转* 这样当翻转时背景图片不会左右上下颠倒*/Matrix matrix = new Matrix();if (isRotateX) {matrix.postScale(1, -1);} else {matrix.postScale(-1, 1);}mRotatedBackBitmap = Bitmap.createBitmap(mBackBitmap, 0, 0,mBackBitmap.getWidth(), mBackBitmap.getHeight(), matrix, true);}/*** 当翻转在2、3象限显示背景图,在1、4象限显示前景图*/if (rotate > 90 && rotate < 270) {setImageBitmap(mRotatedBackBitmap);} else {setImageBitmap(mFrontBitmap);}}this.isRotateX = isRotateX;
}
两个参数,第一个参数是翻转的角度,第二个参数是翻转的方向(水平还是垂直)。
翻转很简单,调用setRotationX或setRotationY函数即可,主要是前景图和背景图的切换。
注意第二部分代码,这里做了缩放的处理,是因为翻转时由于实现了近大远小的效果,所以翻转时处于外侧的一边会增大并超出区域,这样视觉上效果不好,所以做了缩放处理,保证整个翻转过程可以完整的呈现在区域内。大家可以试着将这部分代码去掉对比一下效果,这里就不展示了。
最后一步代码则是根据反转的角度不同设置不同的图片。重点关注背景图,由于背景图实际上应该是水平镜像的,所以使用要提前水平翻转一下,翻转后的就是mRotateBackBitmap。为了防止每次都做一次翻转操作,判断如果已经有mRotateBackBitmap并且翻转方向未变则不必再执行。所以如果改变了背景图,要重置mRotateBackBitmap为null,就是上面setBitmap函数提到的。
这样当我们调用setRotate方法设置不同的角度就能得到不同的翻转效果。
3)实现翻转动画
public void rotateXAnimation(float fromRotate, float toRotate, long duration){rotateAnimation(fromRotate, toRotate, duration, 0, true);
}/*** 翻转动画* @param fromRotate 开始角度* @param toRotate 结束角度* @param duration* @param delay 动画延时* @param isRotateX 是否以X为轴*/
private void rotateAnimation(float fromRotate, float toRotate, long duration, long delay, boolean isRotateX){if(mAnimator != null){mAnimator.cancel();}mAnimator = ValueAnimator.ofFloat(fromRotate, toRotate);mAnimator.setStartDelay(delay);mAnimator.setDuration(duration);mAnimator.start();mAnimator.addUpdateListener(new RotateListener(isRotateX));
}class RotateListener implements ValueAnimator.AnimatorUpdateListener{private boolean isRotateX;public RotateListener(boolean isRotateX){this.isRotateX = isRotateX;}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float value = (Float)(animation.getAnimatedValue());setRotation(value, isRotateX);}
}
其实也很简单,用属性动画来实现即可。这里直接用ValueAnimator,这样动画的值会从fromRotate逐渐改变至toRotate。为动画设置一个监听器,并调用setRotate函数就实现了翻转的动画。
5、百叶窗——BlindsView
1)初始化图片矩阵
public void setBitmap(Bitmap frontBitmap, Bitmap backBitmap){//处理图片List<Bitmap> subFrontBitmaps = getSubBitmaps(mRowCount, mColumnCount, frontBitmap);List<Bitmap> subBackBitmaps = getSubBitmaps(mRowCount, mColumnCount, backBitmap);setBitmaps(mRowCount, mColumnCount, subFrontBitmaps, subBackBitmaps);
}/*** 获取图片阵列* 将大图片分割为rowCount*columnCount阵列的小图片* @param rowCount* @param columnCount* @param bitmap* @return*/
private List<Bitmap> getSubBitmaps(int rowCount, int columnCount, Bitmap bitmap){List<Bitmap> subBitmaps = new ArrayList<Bitmap>();int subWidth = bitmap.getWidth() / columnCount;int subHeight = bitmap.getHeight() / rowCount;for(int i = 0; i < rowCount; i++){for(int j = 0; j < columnCount; j++){/*** 这里计算每个叶面图片的大小* 由于有余数,所以最后一张图片大小单独计算*/int height = i == rowCount - 1 ? bitmap.getHeight() - subHeight * i : subHeight;int width = j == columnCount - 1 ? bitmap.getWidth() - subWidth * j : subWidth;Bitmap subBitmap = Bitmap.createBitmap(bitmap, subWidth * j, subHeight * i, width, height);subBitmaps.add(subBitmap);}}return subBitmaps;
}/*** 设置图片阵列* 将前景和背景图片的阵列放入每个rotateview中* @param rowCount* @param columnCount* @param mFrontBitmaps* @param mBackBitmaps*/
private void setBitmaps(int rowCount, int columnCount, List<Bitmap> mFrontBitmaps, List<Bitmap> mBackBitmaps){/*** 为了复用,需要做些处理* 首先判断现有行/列是否多余,多余直接remove,不足补充*///最大行数,是取现有行数和目标行数的最大值。int maxRow = Math.max(getChildCount() , rowCount);LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 1);params.weight = 1;for(int i = 0; i < maxRow; i++){LinearLayout subView = null;if(i >= getChildCount() && i < rowCount){//如果现有行数不足,则补充。每一行都是水平的linearlayoutsubView = new LinearLayout(getContext());subView.setOrientation(HORIZONTAL);addView(subView, params);}else if(i < getChildCount() && i >= rowCount){//如果现有行数过多,则移除removeViewAt(i);i--;maxRow--;}else{subView = (LinearLayout)getChildAt(i);}//开始处理每一行中的每项if(subView != null){//最大列数,是取现有列数和目标列数的最大值。int maxColumn = Math.max(subView.getChildCount() , columnCount);LinearLayout.LayoutParams subParams = new LinearLayout.LayoutParams(1, LinearLayout.LayoutParams.MATCH_PARENT);subParams.weight = 1;for(int j = 0; j < maxColumn; j++){RotateView rotateView = null;if(j >= columnCount && j < subView.getChildCount()){//如果现有列过多,则移除subView.removeViewAt(j);j--;maxColumn--;}else if(j < columnCount && j >= subView.getChildCount()){//如果现有列不足,则补充。每个叶面是RotateViewrotateView = new RotateView(getContext());subView.addView(rotateView, subParams);}else{rotateView = (RotateView)subView.getChildAt(j);}//为重新整理好的矩阵填充图片if(rotateView != null){int index = i * columnCount + j;rotateView.setBitmap(mFrontBitmaps.get(index), mBackBitmaps.get(index));rotateView.setScaleMin(0.5f);}}}}
}
可以看到先调用getSubBitmaps函数分别将前景和背景图切割并返回一个list。
然后调用setBitmaps函数,根据指定的行和列循环新建RotateView,传入对应的图片并添加到布局中。
注意,这里复用之前已经存在的RotateView,如果不足则补充,多余的remove掉。
这部分虽然代码较多,但是实际上就是每行add一个水平的LinearLayout(BlindsView本身是垂直的),然后逐行一个个add或复用RotateView并为其setBitmap。
2)手动翻转百叶窗
@Override
public void setAnimationPercent(float percent, MotionEvent event, boolean isVertical){mAnimationPercent = percent;//获取总的转动的角度float value = mAnimationPercent * getTotalVaule(isVertical);/*** 遍历每一个小叶面设置当前的角度* 根据转动的方向不同,从不同的位置开始翻转*/for(int i = 0; i < mRowCount; i++){LinearLayout parent = (LinearLayout)getChildAt(i);for(int j = 0; j < mColumnCount; j++){RotateView view = (RotateView)parent.getChildAt(j);float subValue;if(value > 0){if(isVertical){//向下滑动。从第一行开始转动,每行转动角度依次递减subValue = value - mSpace * i;}else{//向右滑动。从第一列开始转动,每列转动角度依次递减subValue = value - mSpace * j;}//保证转动角度在0到180度内if(subValue < 0){subValue = 0;}else if(subValue > 180){subValue = 180;}}else{if(isVertical){//向下滑动。从最后一行开始转动,每行转动角度依次递减(注意由于value是负数,所以数值上是递增)subValue = value + mSpace * (mRowCount - i - 1);}else{//向左滑动。从最后一列开始转动,每列转动角度依次递减(注意由于value是负数,所以数值上是递增)subValue = value + mSpace * (mColumnCount - j - 1);}//保证转动角度在0到-180度内if(subValue < -180){subValue = -180;}else if(subValue > 0){subValue = 0;}}//注意,如果是上下翻动,角度需要转为负值,否则转动的方向有误view.setRotation(isVertical ? -subValue : subValue, isVertical);}}
}
可以看到,一开始我们就通过getTotalValue计算出一个总的转动角度,这个函数代码如下:
private float getTotalVaule(boolean isVertical){if(isVertical) {return mSpace * (mRowCount - 1) + 180;}else{return mSpace * (mColumnCount - 1) + 180;}
}
这块需要解释一下。从上面的图片可以看到,每一列旋转的角度时不同的,相邻列会差一个角度,就是mSpace。
那么getTotalValue函数计算的是一个什么值?
在一个完整翻转过程中,当第一列翻转完成,其他列还没有,所以过程并未结束。
这时假设第一列继续翻转,当第二列翻转完成,第一列已经翻转了mSpace * 1 + 180。那么继续直到最后一列也完全翻转过来,那么第一列实际翻转了mSpace * (columnCount - 1) + 180。
所以mAnimationPercent * getTotalVaule(isVertical)实际上就是第一列当前的翻转角度了,这样就可以计算出其他列的翻转角度。为每个RotateView设置rotation即可。
但是注意这并不是真正的翻转角度,当已经完全翻转180度后就不再需要翻转。
代码中处理了四个方向的翻转,所以计算上多少有些不同,思路是一样的。
3)自动翻转百叶窗
@Override
public void startAnimation(boolean isVertical, MotionEvent event, float toPercent){if(mAnimator != null && mAnimator.isRunning()){return;}mAnimator = ValueAnimator.ofFloat(mAnimationPercent, toPercent);//动画持续时间根据起始位置不同mAnimator.setDuration((long) (Math.abs(toPercent - mAnimationPercent) * mDuration));mAnimator.start();OnAnimationListener onAnimationListener = new OnAnimationListener(isVertical, toPercent);mAnimator.addUpdateListener(onAnimationListener);mAnimator.addListener(onAnimationListener);
}class OnAnimationListener implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener{private boolean isVertical;private float toPercent;public OnAnimationListener(boolean isVertical, float toPercent){this.isVertical = isVertical;this.toPercent = toPercent;}@Overridepublic void onAnimationUpdate(ValueAnimator animation) {setAnimationPercent((float)animation.getAnimatedValue(), null, isVertical);}@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {mAnimationPercent = 0;if(mOnAnimationViewListener == null){return;}if(toPercent == 1){mOnAnimationViewListener.pagePrevious();}else if(toPercent == -1){mOnAnimationViewListener.pageNext();}}@Overridepublic void onAnimationCancel(Animator animation) {mAnimationPercent = 0;}@Overridepublic void onAnimationRepeat(Animator animation) {}
}
通过代码可以看到就是通过监听一个float的属性动画,然后通过setAnimationPrecent改变翻转状态即可。
注意在动画结束时调用切页的回调。
这部分与上一篇对折效果类似,就不细说了。
6、总结一下
源码:
Android魔法(第四弹)—— 一步步实现百叶窗效果相关推荐
- Android魔法(第二弹)——一步步实现淹没、展开效果
目录 1.效果展示 2.动画分析 3.整体布局 4.源码解析 5.知识点总结 ObjectAnimator ViewWrapper 源码: 本篇文章我们实现一个简单的动画效果,目的是熟悉和加深Andr ...
- Android 开发第四弹:围住神经猫(简单Demo)
前言 如下图所示,这篇文章要完成的就是这个简单的示例,后续会继续添加上动画和声音.这里主要包含了游戏的一些简单元素和逻辑. 在我的多次尝试后发现想赢它还是挺难的--毕竟它的走法不是简简单单的Rando ...
- Android EditText.setError() - 会弹出一个popupwindow,效果很好
SetError提供了以下两个方法: 1.显示自定义文字 public void setError (CharSequence error) 2.显示自定义文字和图标 public void setE ...
- Android第二十四期 - 游戏公告跑马灯效果
代码已经整理好,效果如下: 地址:http://down.51cto.com/data/1887395 本文转自 吴雨声 51CTO博客,原文链接:http://blog.51cto.com/lian ...
- Android魔法(第三弹)—— 一步步实现对折页面
本篇文章主要去实现一个对折页面的效果,主要来学习Android中的截屏.Bitmap处理及canvas绘制这些知识. 1.效果展示 实现后的效果如下 2.AnimationListView框架解读 1 ...
- android 绘制心率曲线图,Android 特效View第四弹之折线图 心率图
Android 特效View第四弹之折线图 心率图 android:layout_width="match_parent" android:layout_height=" ...
- Android基础知识~入门进阶,一步步走到高手
Android基础知识~入门进阶,一步步走到高手 2011年09月01日 [b]希望新入手ANDROID设备的朋友认真阅读本帖,一些简单的问题就可以自己解决了!!! 一:基础知识[/b] [b]1.什 ...
- android RecyclerView一步步打造分组效果、类似QQ分组、折叠菜单、分组效果(二)
第一篇链接: android RecyclerView一步步打造分组效果.类似QQ分组.折叠菜单.分组效果(一) 注!已更新代码! 上一篇写了分组效果的初步实现: 这一篇就继续增加分组折叠效果和基类的 ...
- 【转】android电池(四):电池 电量计(MAX17040)驱动分析篇
关键词:android 电池 电量计 MAX17040 任务初始化宏 power_supply 平台信息: 内核:linux2.6/linux3.0 系统:android/android4.0 ...
最新文章
- mcDropdown使用方法
- java 1000个线程_java,一个程序建立1000个线程,每一个线程加1到一个变量sum。
- 吴恩达的视频课程做成了文字版 ~~~
- python和perl哪个好_做为脚本语言来说perl和python那个更有优势?
- 区块链软件公司:区块链使用程序如何成为战胜商场应战的垫脚石
- 4.3.6无分类编址CIDR(构成超网)
- ftp搜索文件_Windows10下搭建FTP服务器详解(多图预警)
- 十年后,你在元宇宙中的一天是什么样?
- Codeforces Round #403 (Div. 2, based on Technocup 2017 Finals
- Codeforces Round #131 (Div. 2) B. Hometask dp
- 我眼中的《APUE》
- php转换ofd文件格式,一种OFD格式文档支持脚本的方法与流程
- java 微信公众号微信支付
- 北京内推 | 微软亚洲研究院机器学习组招聘NLP/语音合成等方向全职研究员
- EasyDarwin开源流媒体云平台支持EasyCamera摄像机、EasyCamera手机直播监控、EasyNVR等多终端接入
- 【盘点】2017杭州云栖大会迁云实战Workshop
- 计算一个整数,转换成二进制,里面有多少个1
- IOS 读二进制数据文件
- YOLOv3论文中英文对照翻译
- 浮点数与字节数据转换详解
热门文章
- 你还在问android横竖屏切换的生命周期?
- Java Web学习(五)session、cookie、token
- 利用AutoHotkey实现Vim和Excel的数据传递
- Entity Framework数据库初始化四种策略
- 再也不用担心面试官问你HashCode和equals了
- ashx 绝对路径得到物理路径
- 从github克隆内容到本地时权限问题
- 各种排序总结(六)归并排序
- java版spring cloud+spring boot+redis多租户社交电子商务平台 (十一)docker部署spring cloud项目...
- Binlog同步工具Canal部署使用