目录

1、效果展示

2、实现AnimationViewInterface接口

3、解析动画组成

4、翻转单元——RotateView

1)前景背景图

2)实现翻转

3)实现翻转动画

5、百叶窗——BlindsView

1)初始化图片矩阵

2)手动翻转百叶窗

3)自动翻转百叶窗

6、总结一下

源码:


本篇是基于AnimationListView框架的,这个框架在上一篇中详细的讲解了,建议阅读本篇前先熟悉Android魔法(第三弹)—— 一步步实现对折页面。

1、效果展示

在上一章中我们实现对折的效果同时实现了一个AnimationListView的框架,在这个框架下我们可以实现很多效果。
本篇文章我们就在这个框架下实现一个百叶窗的效果,效果如下:

2、实现AnimationViewInterface接口

如果想在AnimationListView中应用一种效果,那么就需要实现AnimationViewInterface接口,如下
public class BlindsView extends LinearLayout implements AnimationViewInterface{
BlindsView的具体实现我们稍后在讲解,先看看BlindsView继承LinearLayout,为什么呢?

3、解析动画组成

我们来看其中一帧的画面,如下
可以看到整个百叶窗效果其实是由一个个小的方形组成的,这些方块做水平翻转的动作,并且在不同列有一个效果的时差,就形成了百叶窗的效果。
所以我们BlindsView实际上包含许多这样的子view,真正的动画是这些子view翻转产生的,所以BlindsView要继承LinearLayout来实现这种宫格布局。

4、翻转单元——RotateView

上面提到的子view,我们定义为RotateView,继承ImageView以便来装载图片。如下:
public class RotateView extends ImageView {

1)前景背景图

观察下图中指示位置的方块,并对比上一张同一位置的方块。
可以发现当翻转过180度的时候,该方块显示了另外一张图片,实际上是下一页该位置的部分。所以每个RotateView需要前景和背景两张图片,代码如下:
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)实现翻转动画

对于RotateView其实只需要setRotate函数,动画部分在BlindsView中处理并调用setRotate即可。但是我们也希望这个类可以单独使用,所以我加入了它自身的动画处理,如下:
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

上面我们完成了翻转单元——RotateView,下面讲解如何用这些单元来组成百叶窗的效果。

1)初始化图片矩阵

将整个的前景和背景图片切割成小图片设置给RotateView,并将这些RotateView以矩阵形式布局到BlindsView中,代码如下:
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)手动翻转百叶窗

与上一篇对折效果一样,整个百叶窗效果的移动包括手动和自动两个部分。当用户touch屏幕并移动时,百叶窗跟随touch的move事件去移动;当用户touch up或end时,会通过一个animation自动完成剩余的部分。
手动移动阶段的需要实现AnimationViewInterface的setAnimationPrecent方法,如下:
@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)自动翻转百叶窗

自动阶段通过实现startAnimation函数,代码如下:
@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、总结一下

通过这两篇文章,大家应该对AnimationListView这个框架有了了解。通过这个框架我们还可以实现更多更酷的效果,代码大体上可以参考这两个效果。关于这个框架及实现我们暂时告一段落,接下来会分析一些其他的,以后有机会我们可以在这个框架上实现更多的效果,大家如果有什么好的想法或自己实现的效果可以留言。

源码:

关注公众号:BennuCTech,发送“FastWidget”获取完整源码

Android魔法(第四弹)—— 一步步实现百叶窗效果相关推荐

  1. Android魔法(第二弹)——一步步实现淹没、展开效果

    目录 1.效果展示 2.动画分析 3.整体布局 4.源码解析 5.知识点总结 ObjectAnimator ViewWrapper 源码: 本篇文章我们实现一个简单的动画效果,目的是熟悉和加深Andr ...

  2. Android 开发第四弹:围住神经猫(简单Demo)

    前言 如下图所示,这篇文章要完成的就是这个简单的示例,后续会继续添加上动画和声音.这里主要包含了游戏的一些简单元素和逻辑. 在我的多次尝试后发现想赢它还是挺难的--毕竟它的走法不是简简单单的Rando ...

  3. Android EditText.setError() - 会弹出一个popupwindow,效果很好

    SetError提供了以下两个方法: 1.显示自定义文字 public void setError (CharSequence error) 2.显示自定义文字和图标 public void setE ...

  4. Android第二十四期 - 游戏公告跑马灯效果

    代码已经整理好,效果如下: 地址:http://down.51cto.com/data/1887395 本文转自 吴雨声 51CTO博客,原文链接:http://blog.51cto.com/lian ...

  5. Android魔法(第三弹)—— 一步步实现对折页面

    本篇文章主要去实现一个对折页面的效果,主要来学习Android中的截屏.Bitmap处理及canvas绘制这些知识. 1.效果展示 实现后的效果如下 2.AnimationListView框架解读 1 ...

  6. android 绘制心率曲线图,Android 特效View第四弹之折线图 心率图

    Android 特效View第四弹之折线图 心率图 android:layout_width="match_parent" android:layout_height=" ...

  7. Android基础知识~入门进阶,一步步走到高手

    Android基础知识~入门进阶,一步步走到高手 2011年09月01日 [b]希望新入手ANDROID设备的朋友认真阅读本帖,一些简单的问题就可以自己解决了!!! 一:基础知识[/b] [b]1.什 ...

  8. android RecyclerView一步步打造分组效果、类似QQ分组、折叠菜单、分组效果(二)

    第一篇链接: android RecyclerView一步步打造分组效果.类似QQ分组.折叠菜单.分组效果(一) 注!已更新代码! 上一篇写了分组效果的初步实现: 这一篇就继续增加分组折叠效果和基类的 ...

  9. 【转】android电池(四):电池 电量计(MAX17040)驱动分析篇

    关键词:android 电池  电量计  MAX17040 任务初始化宏 power_supply 平台信息: 内核:linux2.6/linux3.0 系统:android/android4.0  ...

最新文章

  1. mcDropdown使用方法
  2. java 1000个线程_java,一个程序建立1000个线程,每一个线程加1到一个变量sum。
  3. 吴恩达的视频课程做成了文字版 ~~~
  4. python和perl哪个好_做为脚本语言来说perl和python那个更有优势?
  5. 区块链软件公司:区块链使用程序如何成为战胜商场应战的垫脚石
  6. 4.3.6无分类编址CIDR(构成超网)
  7. ftp搜索文件_Windows10下搭建FTP服务器详解(多图预警)
  8. 十年后,你在元宇宙中的一天是什么样?
  9. Codeforces Round #403 (Div. 2, based on Technocup 2017 Finals
  10. Codeforces Round #131 (Div. 2) B. Hometask dp
  11. 我眼中的《APUE》
  12. php转换ofd文件格式,一种OFD格式文档支持脚本的方法与流程
  13. java 微信公众号微信支付
  14. 北京内推 | 微软亚洲研究院机器学习组招聘NLP/语音合成等方向全职研究员
  15. EasyDarwin开源流媒体云平台支持EasyCamera摄像机、EasyCamera手机直播监控、EasyNVR等多终端接入
  16. 【盘点】2017杭州云栖大会迁云实战Workshop
  17. 计算一个整数,转换成二进制,里面有多少个1
  18. IOS 读二进制数据文件
  19. YOLOv3论文中英文对照翻译
  20. 浮点数与字节数据转换详解

热门文章

  1. 你还在问android横竖屏切换的生命周期?
  2. Java Web学习(五)session、cookie、token
  3. 利用AutoHotkey实现Vim和Excel的数据传递
  4. Entity Framework数据库初始化四种策略
  5. 再也不用担心面试官问你HashCode和equals了
  6. ashx 绝对路径得到物理路径
  7. 从github克隆内容到本地时权限问题
  8. 各种排序总结(六)归并排序
  9. java版spring cloud+spring boot+redis多租户社交电子商务平台 (十一)docker部署spring cloud项目...
  10. Binlog同步工具Canal部署使用