具体使用方法在这:项目地址java
下面说下具体实现: 首先,既然作了,那就多作几种模式,左右两边均可以设置轮流进入或是轮流退出,因此先定义两个枚举类来讲明是哪一种模式:git
public enum ScrollDirection {
LEFT, // 从右到左
RIGHT, // 从左到又
BOTH // 都支持
}
public enum Mode {
IN, // 入场
OUT, // 出场
BOTH // 都支持
}
public void setSupportScrollDirection(ScrollDirection scrollDirection) {
mIsSupportLeft = scrollDirection == ScrollDirection.LEFT || scrollDirection == ScrollDirection.BOTH;
mIsSupportRight = scrollDirection == ScrollDirection.RIGHT || scrollDirection == ScrollDirection.BOTH;
}
public void setSupportMode(Mode mode) {
mIsSupportIn = mode == Mode.IN || mode == Mode.BOTH;
mIsSupportOut = mode == Mode.OUT || mode == Mode.BOTH;
}
复制代码
而后说下思路,这是个ViewPager
嵌套RecyclerView
,随着页面的滑动,item按照RecyclerView
进入屏幕的比例设置必定的偏移量,大体就是这样,其中的一些细节后面再讲。 按照上面的思路,首先咱们须要给ViewPager
添加滑动监听,在监听中经过获取RecyclerView
相对于整个屏幕的坐标位置,来判断它处于什么状态,正在进入屏幕仍是滑出屏幕,从左边进入仍是右边进入,该不应执行入场出场动画。添加监听就不说了,获取RecyclerView
的位置能够经过View#getGlobalVisibleRect(Rect r)
这个方法来实现,该方法会返回一个boolean
值说明目标View
是否在屏幕内,若是在屏幕内的话,会将在屏幕内的部分的坐标写入Rect
参数中,这里须要注意一下是屏幕内*的部分,以下图,黑色的框为屏幕,蓝色的是RecyclerView
,红色的是被赋值的Rect
。 github
因为咱们的动画须要在进入或者退出时执行,因此先要判断一下何时是进入,何时是退出,若是以前不在屏幕中那么说明是正在进入,若是以前彻底落在了屏幕中说明是正在退出,因此代码大体是这样的:布局
// 若是被回收了,那么根布局的parent为null,不然为ViewRootImpl
if (mRecyclerView.getRootView().getParent() == null) {
return;
}
boolean isInScreen = mRecyclerView.getGlobalVisibleRect(mBounds);
if (!isInScreen) {
mIsDoingAnimation = false;
mIsEntering = true;
return;
}
getLeftAndRight();
// RecyclerView彻底进入屏幕
if (mLeft >= 0 && mRight <= mWindowSize.x) {
mIsDoingAnimation = false;
mIsEntering = false;
return;
}
复制代码
首先判断了如下RecyclerView
是否被ViewPager
回收了,若是被回收了,也就是RecyclerView
没有父View
了,getGlobalVisibleRect
获取到的信息就不是咱们想要的,缘由这里就不说了,具体能够看下getGlobalVisibleRect
的源码。这里采用了一个我在Debug的时候发现的方法来判断是否被回收了,若是被回收了,那么根布局的parent
为null
,不然为ViewRootImpl
。 而后经过getGlobalVisibleRect
方法来判断RecyclerView
是否在屏幕中,若是不在则说明下次出如今屏幕中的时候就是正在进入,将mIsEntering
设置为true
,这里还有个变量mIsDoingAnimation
是用来标记当前是否在执行动画的,具体做用后面再讲。 接着经过getLeftAndRight
方法来获取RecyclerView
真实的左右坐标,有了真实的坐标才能够判断是否彻底进入屏幕。若是彻底进入屏幕则说明下次滑动时就是正在退出屏幕,将mIsEntering
设置为false
,同时将mIsDoingAnimation
也设置为false
。 getLeftAndRight
方法实现以下:优化
/** * 计算出左右的坐标,getGlobalVisibleRect获取的是进入屏幕内的左右坐标 */
private void getLeftAndRight() {
if (mBounds.left < mWindowSize.x && mBounds.right >= mWindowSize.x) {
mLeft = mBounds.left;
mRight = mLeft + mRecyclerViewWidth;
} else if (mBounds.left <= 0 && mBounds.right > 0) {
mRight = mBounds.right;
mLeft = mRight - mRecyclerViewWidth;
}
}
复制代码
判断完是正在进入屏幕仍是退出屏幕,接下来就能够根据这个来执行相应的动画了,而这个动画实际上就是在ViewPager
横向滑动的时候来设置RecyclerView
每一个item的水平偏移量,那么咱们就须要经过LayoutManager
来获取在屏幕中展现的每一个item的对象:动画
private void getChildList() {
mChildList.clear();
mChildCount = mLayoutManager.getChildCount();
for (int i = 0; i < mChildCount; i++) {
mChildList.add(mLayoutManager.getChildAt(i));
}
}
复制代码
获取到了这些对象以后,咱们须要对他们进行从新布局,这时还须要item的左右margin
值才能布局到正确的位置,这里我只获取了第一item的margin
,正常状况下不会给每一个item设置不一样的margin
:spa
private void getMargin() {
ViewGroup.LayoutParams params = mChildList.get(0).getLayoutParams();
if (params instanceof ViewGroup.MarginLayoutParams) {
mLeftMargin = ((ViewGroup.MarginLayoutParams) params).leftMargin;
mRightMargin = ((ViewGroup.MarginLayoutParams) params).rightMargin;
} else {
mLeftMargin = 0;
mRightMargin = 0;
}
}
复制代码
有了上面的东西就能够根据进入屏幕的比例来计算出每一个item应该偏移的距离了。以入场为例,在RecyclerView
刚进入屏幕的一瞬间每一个item从上到下都比前一个多偏移一个单位距离,假设单位距离为x,那么从上到下依次偏移x,2x,3x,·····,(n-1)x,nx随着进入屏幕的部分愈来愈多,每一个item的偏移量逐渐减少直到回到原来的位置。单位距离采用RecyclerView
减去左右margin
再除以当前展现的item个数来计算,以下:code
mUnitOffset = (mRecyclerViewWidth - mLeftMargin - mRightMargin) / mChildCount;
复制代码
这样随着RecyclerView
逐渐进入屏幕,只需将最初的偏移量减去进入屏幕部分的宽度就能够获得想要的偏移量。 下面是设置偏移量的代码:cdn
private void relayoutChild(int dx, int direction) {
float offset;
// 让index为0的子View也有偏移量
for (int i = 1; i <= mChildCount; i++) {
offset = (mUnitOffset * i - dx) * 1.25f;
if (offset < 0) {
offset = 0;
}
// 左为1 右为-1 在右边要反向偏移
mChildList.get(i - 1).setX(offset * direction + mLeftMargin);
}
mRecyclerView.requestLayout();
}
复制代码
在for
循环中,从1开始循环是为了让第一个item也有偏移量,还将计算出的偏移量稍微放大了一点,否则感受偏移的有点少了,很差看。其中direction
传入1
表示右侧,-1
表示左侧。右侧的偏移量只须要将算出来的偏移量取反就好了,同时都要加上marginLeft
确保能布局到正确的位置。 有了上面这个方法后就能够去布局了,只须要再判断一下是在左边仍是右边就好了:对象
// 状态改变,从新获取子view
if (!mIsDoingAnimation) {
getChildList();
if (mChildCount == 0) {
return;
}
getMargin();
mUnitOffset = (mRecyclerViewWidth - mLeftMargin - mRightMargin) / mChildCount;
mIsDoingAnimation = true;
}
// 这里判断的是去除左右margin后 item进入或退出屏幕才会执行动画
if (mLeft + mLeftMargin <= mWindowSize.x && mRight >= mWindowSize.x && mIsSupportRight) {
relayoutChild(mWindowSize.x - mLeft - mLeftMargin, RIGHT);
} else if (mLeft <= 0 && mRight + mRightMargin >= 0 && mIsSupportLeft) {
relayoutChild(mRight - mRightMargin, LEFT);
}
复制代码
这里若是以前不在执行动画,那么须要从新获取子View
,这样RecyclerView
竖向无论滑动到哪里均可以获取到对应的一个子View
列表。后面判断在左侧仍是右侧须要算上左右margin值,也就是当item滑动到屏幕边缘时才会执行动画,而不是RecyclerView
滑动到屏幕边缘就执行动画,否则可能部分item还没进入屏幕就已经回到原来的位置或是还没开始偏移就已经到屏幕外面去了,具体效果能够本身试试。 最终在ViewPager
滑动时的监听以下:
if (mRecyclerView == null) {
return;
}
// 若是被回收了,那么根布局的parent为null,不然为ViewRootImpl
if (mRecyclerView.getRootView().getParent() == null) {
return;
}
boolean isInScreen = mRecyclerView.getGlobalVisibleRect(mBounds);
if (!isInScreen) {
// 仅支持out动画时,彻底移出屏幕须要重置位置
if (mIsSupportOut && !mIsSupportIn && !mIsEntering) {
resetChild();
}
mIsDoingAnimation = false;
mIsEntering = true;
return;
}
getLeftAndRight();
// RecyclerView彻底进入屏幕
if (mLeft >= 0 && mRight <= mWindowSize.x) {
mIsDoingAnimation = false;
mIsEntering = false;
return;
}
if (!shouldDoAnimation()) {
return;
}
// 状态改变,从新获取子view
if (!mIsDoingAnimation) {
getChildList();
if (mChildCount == 0) {
return;
}
getMargin();
mUnitOffset = (mRecyclerViewWidth - mLeftMargin - mRightMargin) / mChildCount;
mIsDoingAnimation = true;
}
// 这里判断的是去除左右margin后 item进入或退出屏幕才会执行动画
if (mLeft + mLeftMargin <= mWindowSize.x && mRight >= mWindowSize.x && mIsSupportRight) {
relayoutChild(mWindowSize.x - mLeft - mLeftMargin, RIGHT);
} else if (mLeft <= 0 && mRight + mRightMargin >= 0 && mIsSupportLeft) {
relayoutChild(mRight - mRightMargin, LEFT);
}
复制代码
原理大体就是这样了,最后还要注意释放资源,防止内存泄漏,不仅是在ViewPager
销毁时须要调用,在PagerAdapter
的destroyItem
中也要调用:
public void onDestroy() {
mParent.removeOnPageChangeListener(mViewPagerListener);
mParent = null;
mRecyclerView = null;
mLayoutManager = null;
mChildList.clear();
}
复制代码
总体来说比较简单,目前实现是一个TakeTurnHelper
对应一个RecyclerView
,能够优化一下,改为能够对应多个RecyclerView
,之后想起来再搞,如今懒得改了。