主要是实现类似于抖音翻页的效果,但是有有点不同,需要在底部漏出后面的view,这样说可能不好理解,看下Demo,按页滑动,后面的View有放大缩放的动画,滑动速度过小时会有回到原位的效果,下滑也是按页滑动的效果。
有的小伙伴可能说这个用 SnapHelper
就可以了,没错,翻页是要结合这个,但是也不是纯粹靠这个,因为底部需要漏出来后面的view,所以LayoutManager
就不能简单的使用LinearLayoutManager
,需要去自定义LayoutManager
,然后再自定义SnapHelper
。
先看下自定义LayoutManager
。
1.自定义LayoutManager
Android系统给我们提供了常用的几个LayoutManager
,比如LinearLayoutManager
:用于水平或者竖直滑动GridLayoutManager
:用于表格布局,一行可以有多列StaggeredGridLayoutManager
:瀑布流布局
但是在我们上面那个界面就用不了,因为在第一页界面底部需要漏出后面的Item,所以我们就需要自定义。
一般自定义LayoutManager需要实现三个方法:
第一个方法是generateDefaultLayoutParams
,这个用来定义布局参数的,一般宽高都WRAP_CONTENT
就行。
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
RecyclerView.LayoutParams.WRAP_CONTENT);
}
第二个方法根据需要水平或者竖直滑动区分,我们这里是竖直滑动,重写canScrollVertically
@Override
public boolean canScrollVertically() {
return true;
}
聪明的你肯定已经知道如果水平滑动,就是重写canScrollHorizontally
.
前面两个方法都很简单,最麻烦的就是第三个方法,重写LayoutManager
就是需要自己去布局,所以需要重写
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.getItemCount() == 0 || state.isPreLayout()) {
return;
}
if (!hasChild) {
hasChild = true;
}
mItemCount = getItemCount();
// 滑动过的距离
mScrollOffset = Math.min(Math.max(0, mScrollOffset), (mItemCount - 1) * itemHeight);
layoutChild(recycler);
}
首先如果没有item或者第一次layout进来就直接返回,mScrollOffset
是滑动过的距离,初始值为0,滑动到最后一个就不能进行滑动,这里其实可以加个下拉刷新,以后有机会再加,不是今天的主题。
接下来大头就是layoutChild
.
private void layoutChild(RecyclerView.Recycler recycler) {
if (getItemCount() == 0) {
return;
}
int firstItemPosition = (int) Math.floor(mScrollOffset / itemHeight);
if (firstItemPosition > commonAdapter.getItemCount() - 1) {
return;
}
int firstItemScrolledHeight = mScrollOffset % itemHeight;
final float firstItemScrolledHeightPercent = firstItemScrolledHeight * 1.0f / itemHeight;
ArrayList<PageItemViewInfo> layoutInfos = new ArrayList<>();
// 计算view位置
int tmpCount = Math.min(VISIBLE_EMOTICON_COUNT, commonAdapter.getItemCount() - firstItemPosition - 1);
for (int i = 0; i <= tmpCount; i++) {
// 用于计算偏移量
int tmp = i + 1;
double maxOffset = (getVerticalSpace()
- itemHeight - firstItemScrolledHeightPercent) / 2 * Math.pow(0.65, tmp);
if (maxOffset <= 0) {
break;
}
int start;
if (i == 0) {
start = getPaddingTop() - firstItemScrolledHeight;
} else {
start = (int) (getPaddingTop() + i * maxOffset + i * ITEM_OFFSET);
}
float mScale = 0.95f;
float scaleXY = (float) (Math.pow(mScale, i) * (1 - firstItemScrolledHeightPercent * (1 - mScale)));
PageItemViewInfo info = new PageItemViewInfo(start, scaleXY);
layoutInfos.add(0, info);
}
// 回收View
int layoutCount = layoutInfos.size();
final int endPos = firstItemPosition + VISIBLE_EMOTICON_COUNT;
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
View childView = getChildAt(i);
if (childView == null) {