天天看點

“京東金融”首頁效果 RecyclerView關聯

先上效果圖吧:

第一個想到的實作方式是上面使用horizontalScrollview,下面使用Viewpager,經過嘗試之後發現二者API有限,不能達到理想效果。幾經折騰,最後上下都使用了自定義的RecyclerView。效果圖如下:

現在來分析技術點,首先是上下關聯,思路是在Recycleview的onScrolled回調方法中操作另一個Recycleview的滑動。

1 @Override
2 public void onScrolled(int dx, int dy) {
3     super.onScrolled(dx, dy);
4     sx = sx +dx;
5     if (scrollViewListener != null && isMark) {
6         scrollViewListener.onScrollChanged(this, sx, 0);
7     }
8 }      

其中onScrollChanged方法在首頁面中實作

1 @Override
 2 public void onScrollChanged(Object scrollView, int x, int y) {
 3     int width1 = CommonUtil.getScreenWidth(this) - DensityUtils.dip2px(this, 60);
 4     int width2 = CommonUtil.getScreenWidth(this);
 5     if (scrollView == rvHead) {
 6         rvFoot.setmark(false);
 7         rvFoot.scrollTo(x * width2 / width1, y);
 8     } else if (scrollView == rvFoot) {
 9         rvHead.setmark(false);
10         rvHead.scrollTo(x * width1 / width2, y);
11     }
12     rvHead.setmark(true);
13     rvFoot.setmark(true);
14 }      

上下View的滑動速率差即為上下RecyclerView中item的寬度差,上面view中item的寬度為螢幕寬度-60dp,詳見對應的adapter。

由于RecyclerView中scrollTo方法沒有實作,是以直接想到的是用scroolBy代替,但由于滑動回調傳回的是Int值,經過速率差處理後精度丢失,得不到準确值,導緻關聯效果達不到,痛定思痛,最後還是自己來重寫scrollTo方法:

1 @Override
2 public void scrollTo(int x, int y) {
3     scrollBy(x-sx,0);
4 }      

sx為自己在onScrolled方法中記錄,具體見文末給出的源碼。

滑動之後,還要進行回調處理,以達到像viewPager那樣的回彈效果,具體邏輯在自定義的RecyclerView中的回調方法onScrollStateChanged中實作:

1 public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
 2         super.onScrollStateChanged(recyclerView, newState);
 3         if (newState == RecyclerView.SCROLL_STATE_IDLE) {
 4             int mmSelected;
 5             //當控件停止滾動時,擷取可視範圍第一個item的位置,滾動調整控件以使選中的item剛好處于正中間
 6             int firstVisiblePos = mLayoutManager.findFirstVisibleItemPosition();
 7             if (firstVisiblePos == RecyclerView.NO_POSITION) {
 8                 return;
 9             }
10             Rect rect = new Rect();
11             mLayoutManager.findViewByPosition(firstVisiblePos).getHitRect(rect);
12             if (Math.abs(rect.left) > mItemWidth / 2) {
13                 smoothScrollBy(rect.right, 0);
14                 mmSelected = firstVisiblePos + 1;
15             } else {
16                 smoothScrollBy(rect.left, 0);
17                 mmSelected = firstVisiblePos;
18             }
19             if (Math.abs(rect.left) == 0 && mOnSelectListener != null && mmSelected != mSelected) {
20                 mSelected = mmSelected;
21                 mOnSelectListener.onSelect(mSelected);
22             }
23         }
24     }
25 }      

為了讓滑動效果更為自然且支援fling效果,本項目還重寫了RecyclerView的fling方法,使得每次fling都恰好能滑動整數個item,大緻思路為調整fling初始速率,代碼如下:

1 @Override
 2 public boolean fling(int velocityX, int velocityY) {
 3     int v;
 4     int touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 5     if (Math.abs(velocityX) <= 3*touchSlop) {
 6         return false;
 7     }
 8     mPhysicalCoeff = SensorManager.GRAVITY_EARTH   // g (m/s^2)
 9             * 39.37f               // inch/meter
10             * getContext().getResources().getDisplayMetrics().density * 160.0f                 // pixels per inch
11             * 0.84f;
12     int firstVisiblePos = mLayoutManager.findFirstVisibleItemPosition();
13     if (firstVisiblePos == RecyclerView.NO_POSITION) {
14         return false;
15     }
16     Rect rect = new Rect();
17     mLayoutManager.findViewByPosition(firstVisiblePos).getHitRect(rect);
18     double n = getSplineFlingDistance(velocityX) / mItemWidth;
19     int num = Double.valueOf(n).intValue();
20     if (velocityX > 0)
21         v = Double.valueOf(getVelocityByDistance(num * mItemWidth + Math.abs(rect.right)- DensityUtils.dip2px(getContext(), 20))).intValue();
22     else
23         v = Double.valueOf(getVelocityByDistance(num * mItemWidth + Math.abs(rect.left)+ DensityUtils.dip2px(getContext(), 20))).intValue();
24     if (velocityX < 0) {
25         v = -v;
26     } 
27     return super.fling(v, velocityY);
28 }