Android工具箱(一):3D ViewPager
来源:互联网 发布:房地产行业数据网站 编辑:程序博客网 时间:2024/05/16 14:11
好久没有更新博客了,积攒了很多UI效果,准备整理一下写一个系列。
作为android前端程序员,最重要的就是在工作过程中不断丰富自己的类库,等要用的时候直接拿出来改改就行了,我把它命名为Android工具箱。
今天要实现的一个效果是一个有科技感的3D ViewPager,看图:
实现这个效果主要分两个部分:
一. 无限循环ViewPager
这个网上一搜一大把,无非就是下面两种实现方式:
- 使得adapter的getCount()返回Integer.MAX_VALUE,然后在初始化的时候通过setCurrentItem()把当前页设置成一个中间值,这样左右就都有page了。然后在instantiateItem()的时候通过取模还原成真实的index。
数据集前后各补一个(比如123补成31231),然后监听viewpager的滑动,在onPageScrollStateChanged() 里通过setCurrentItem()设置真正的页面index。
第一种方法比较简单一些,而且逻辑清晰,github上有现成的封装:
https://github.com/antonyt/InfiniteViewPager
但是实际使用的时候发现会出现ANR,重现步骤如下:
点击下方的viewpager页面,弹出上方的viewpager。左右滑动几次,按back回到下方的viewpager的时候,需要调用setCurrentItem()使得上下选择的页面一致,此时会出现ANR。究其原因,主要跟viewpager的代码实现相关:
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { ... ... if (mFirstLayout) { mCurItem = item; if (dispatchSelected) { dispatchOnPageSelected(item); } requestLayout(); } else { populate(item); scrollToItem(item, smoothScroll, velocity, dispatchSelected); }}
可以看到,第一次layout的时候会进上面那个代码块,但是我们back回来的时候会进下面那个代码块调用populate()方法,问题就出在这个populate()方法上:
void populate(int newCurrentItem) { ... ... final int N = mAdapter.getCount(); ... ... for (int pos = mCurItem + 1; pos < N; pos++) { ... ... } ... ...}
明白了吧,这里边有个循环,循环次数跟getCount()相关,而我们的getCount()返回的是Integer.MAX_VALUE。。。
修复这个问题有两种方法:
一种是每次都通过反射把上面那个mFirstLayout设置成true,不让它走进populate()那个代码块。不过这终究是一种hack。
另外一种就是改小getCount()的返回值啦,一般返回item数量的100~200倍就可以了,很少有无聊的人一直滑到这么多页的。当然,逻辑上这只是一种“伪无限循环”。
这里采用了第二种解决方式,只需要改动一行:
@Override public int getCount() { if (getRealCount() == 0) { return 0; } // warning: scrolling to very high values (1,000,000+) results in // strange drawing behaviour // warning: return Integer.MAX_VALUE may cause ANR when calling setCurrentItem(), // since there's a loop in ViewPager.popluate() referring to getCount(). // return Integer.MAX_VALUE; return getRealCount() * 200; }
二. 滑动过程中的转换动画
Viewpager提供了PagerTransformer用于实现转换动画,详情参见:
https://developer.android.com/training/animation/screen-slide.html
简而言之,滑动过程中每个page都会有一个不断更新position值:正中显示的page的位置为0,左边为负值,右边为正值。滑动完成后,左边不可见page的位置依次为-1,-2,到负无穷;右边不可见page的位置依次为+1,+2,到正无穷。我们需要做的就是重写transform()方法,根据里面的position值对相应的view进行处理。代码如下:
public class RotateYTransformer implements ViewPager.PageTransformer { private float mMaxRotate = 25.0f; private OnTransformListener mOnTransformListener; public interface OnTransformListener { void onTransform(View page, float position); } ... ... @Override public void transformPage(View page, float position) { page.setPivotY(page.getHeight() / 2); if (position < -1) { // [-Infinity, -1) // This page is way off-screen to the left. page.setPivotX(page.getWidth()); page.setRotationY(-1 * mMaxRotate); } else if (position <= 1) { // [-1,1] if (position < 0) { // [0, -1] page.setPivotX(page.getWidth()); page.setRotationY(position * mMaxRotate); } else { // [1, 0] page.setPivotX(0); } page.setRotationY(position * mMaxRotate); } else { // (1, +Infinity] // This page is way off-screen to the right. page.setPivotX(0); page.setRotationY(1 * mMaxRotate); } if (mOnTransformListener != null) { mOnTransformListener.onTransform(page, position); } } }
代码很简单,就是右边的page以0为原点沿Y轴旋转,左边的page以page width为原点沿Y轴旋转。
另外还定义了一个OnTransformListener,主要是用来高亮当前页的。之前试过用OnPageChangeListener来高亮当前页,但是效果很不自然,在手松开的一刹那突然高亮,用户体验不好,因此就改用了这种方式,在MainActivity里进行设置:
RotateYTransformer transformer = new RotateYTransformer(); transformer.setOnTransformListener(new RotateYTransformer.OnTransformListener() { @Override public void onTransform(View page, float position) { if (position < 0.5f && position >-0.5f) { page.setBackground( getResources().getDrawable(R.drawable.viewpager_item_bg_highlight)); } else { page.setBackground( getResources().getDrawable(R.drawable.viewpager_item_bg)); } } }); mViewPager.setPageTransformer(false, transformer);
最后还有个问题,怎么让左右两边的page露出来一点让用户可以看到?首先我们需要给viewpager设置一个margin,留出绘制的空间。然后我们需要给viewpager以及它的的父容器设置一个android:clipChildren=”false”的属性,告诉父容器超出view返回的内容不要裁剪掉,继续绘制,这样就可以看到左右两边的page了。实际过程中有可能发现设置了clipChildren属性还是不起作用,这时候需要检查是不是父容器上面还有父容器,所有的容器都需要设置上这个属性。
三. 一些其他问题
基本效果已经实现了,当然还不完美。你可能会发现,只有中间的page可以接收touch事件,两边的page虽然露出来一点,但是你是没办法滑动它们的。要解决这个问题,最直接的思路是把margin换成padding,这样就可以接收touch事件了,当然还需要给父容器加上android:clipToPadding=”false”这个属性来告诉父容器不要对padding部分进行裁剪。不过如果你尝试过的话就会发现改完后transform()里获得的position会有问题,所有的页面都偏掉了。。。要修复这个问题,需要对position进行修正:
int padding = viewpager.getPaddingLeft(); int pageWidth = viewpager.getWidth() - padding * 2; float posCorrection = -(float)padding / width;
然后在transform()方法里加上这个偏移量对position进行修正就可以了。由于测试没有提这个问题,所以这部分代码没有提交上去。
源码下载地址:https://github.com/qianxin2016/ViewPager3D
- Android工具箱(一):3D ViewPager
- Toque 3D文档:介绍(工具箱)
- Android Viewpager实现3D画廊效果
- android viewpager实现3d画廊效果
- 射手Android ViewPager打造3D画廊
- Android中的ViewPager(一)
- ViewPager 3D翻转
- ViewPager 3D翻转
- Android 3D游戏开发(一)
- android常用控件ViewPager(一) ViewPager基本应用
- Android---自定义ViewPager指示器(一)
- Android之ViewPager总结(一)
- Android之ViewPager笔记(一)
- android自定义ViewPager之——3D效果应用
- 3D分析之Functional Surface工具箱
- Android:ViewPager 随记一
- 预测工具箱-----图形(一)
- Matlab机器人工具箱(一)
- cocos2dx 自定义事件
- X-Forwarded-For 和 X-Real-IP 的区别?
- darwin之枚举值得组合使用(Task)
- Could not resolve all files for configuration ':classpath'.
- ubuntu16.04下Navicat for mysql 破解方法
- Android工具箱(一):3D ViewPager
- 【IOS 邮件】发信人为“xxxx”被服务器拒绝 的解决方法
- 2-9·Linux基本权限
- Android C++中 sp<> wp<>知识
- PHP 获取各种固定时间的方法
- 使用 sqoop从MySQL增量导出数据到hive
- 第一章 统计学习方法概论
- python 列表、字典、元组、字符串之间的转换
- JS判断是否是微信页面,判断手机操作系统(ios或android)并跳转到不同下载页面