
如上圖所示,要想實作此類效果,第一聯想到的就是viewpager了,因為它翻頁啊,然後處理它的關聯以及跟随翻頁時效果展示,但是我們今天研究的是用兩個Recyclerview去實作,有人就問了,這玩意不是清單麼,我咋翻頁啊,不會還要自己處理吧,别怕,了解的人都知道了谷歌有個PagerSnapHelper,完美貼合咱們的需求,完成翻頁效果,代碼大概如下:
PagerSnapHelper snapHelperContent=new PagerSnapHelper();
snapHelperContent.attachToRecyclerView(mRvContent);
ok,這樣就實作了翻頁效果,想要研究原理的,自行檢視源代碼,這裡不做重點講述。
接着我們簡單進行布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/rv_content"
android:translationY="-70dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
實作簡單的上下兩個清單的adapter資料填充後,大概效果如下:
要想實作理想中的效果,我們必須給清單添加分割線,這裡隻貼出下面清單的分割線代碼:
public class TopItemDecoration extends RecyclerView.ItemDecoration {
private int mLeftMargin;
public TopItemDecoration(Context context) {
mLeftMargin = (Globals.SCREEN_WIDTH - ScreenUtils.dp2px(context,100)) / 2;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int itemCount = parent.getAdapter().getItemCount();
int leftMargin;
int rightMargin;
if (position == 0) {
leftMargin = mLeftMargin;
} else {
leftMargin = 0;
}
if (position == itemCount - 1) {
rightMargin = mLeftMargin;
} else {
rightMargin = 0;
}
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.setMargins(leftMargin, 0, rightMargin, 0);
super.getItemOffsets(outRect, view, parent, state);
}
}
好了,我們再看下效果:
到這裡翻頁和大體布局已經完成了,下面就是核心的滑動過程處理了,以下舉例下面recyclerview的監聽:
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mRvContent.getChildCount() == 1) {
View view = mRvContent.getChildAt(0);
view.setScaleY(1);
view.setScaleX(1);
} else {
for (int i = 0; i < mRvContent.getChildCount(); i++) {
View view = mRvContent.getChildAt(i);
float rate;
int left = (view.getLeft() + view.getRight()) / 2;
if (left <= mGalleryMiddle) {
if (left < mGalleryMiddle - mGalleryMove) {
rate = 1f;
} else {
rate = (mGalleryMiddle - left) * 1f / mGalleryMove;
}
view.setScaleY(1 - rate * mGalleryScaleY);
view.setScaleX(1 - rate * mGalleryScaleY);
view.setTranslationX(rate * mGalleryTranslation);
} else {
if (left > mGalleryMiddle + mGalleryMove) {
rate = 0f;
} else {
rate = (mGalleryMiddle + mGalleryMove - left) * 1f / mGalleryMove;
}
view.setScaleY((1 - mGalleryScaleY) + rate * mGalleryScaleY);
view.setScaleX((1 - mGalleryScaleY) + rate * mGalleryScaleY);
view.setTranslationX((rate - 1) * mGalleryTranslation);
}
}
}
}
同理也可實作頂部清單的滾動變化,難度不大,詳見源碼。
重點是兩個清單如何進行關聯呢,肯定在下面清單滾動監聽過程中,去觸發上面清單的滾動,但是上下布局不一樣大,是以我們要等比例進行滾動:
乍一看沒啥問題,我們跑下代碼試試,恩,感覺有那個意思了,不對啊,咋感覺怪怪的,為啥越往後滑,錯位越大,不在正中央,這就尴尬了,而且當你緩慢滑動下面清單時,上面清單壓根就不叼你。
這是因為public void scrollBy(int x, int y) 這個方法的參數隻能傳int值
這就導緻了精度的丢失,那我們換scrooTo吧,直接一步到位,不就沒有什麼誤差了麼,
然而請看recyclerview中:
@Override
public void scrollTo(int x, int y) {
Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
+ "Use scrollToPosition instead");
}
這tm~,不好意思,沒忍住,這還咋玩啊,既然recyclerview沒有給我們實作,那我們就換個思路,自己去完成“一步到位”的滾動吧。
我們回看
mRvTop.scrollBy(dx * 100 / 327, dy);
這段代碼,此處dx是下面清單每次滑動的距離,然後換算比例得出上面清單需要疊加滑動距離,然後調用scrollBy方法,但是這樣誤差會一直疊加,是以我們換種思路,下面的清單我們每次拿到總滑動距離,然後換算比例得出上面清單需要滑動的總距離,然後再減去上面清單目前滑動距離,得出來的資料不就是沒有誤差的滑動差麼,這樣我們就可以正大光明調用scrollBy方法了:
mTotalContentX += dx;
mRvTop.scrollBy(mTotalContentX * 100 / 327 - mTotalTopX, dy);
大功告成,我們看下最終實作效果:

源碼下載下傳