先上效果圖吧:web
第一個想到的實現方式是上面使用horizontalScrollview,下面使用Viewpager,通過嘗試以後發現兩者API有限,不能達到理想效果。幾經折騰,最後上下都使用了自定義的RecyclerView。效果圖以下:app
如今來分析技術點,首先是上下聯動,思路是在Recycleview的onScrolled回調方法中操做另外一個Recycleview的滑動。ide
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方法在主頁面中實現this
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。spa
因爲RecyclerView中scrollTo方法沒有實現,因此直接想到的是用scroolBy代替,但因爲滑動回調返回的是Int值,通過速率差處理後精度丟失,得不到準確值,致使聯動效果達不到,痛定思痛,最後仍是本身來重寫scrollTo方法:.net
1 @Override 2 public void scrollTo(int x, int y) { 3 scrollBy(x-sx,0); 4 }
sx爲本身在onScrolled方法中記錄,具體見文末給出的源碼。code
滑動以後,還要進行回調處理,以達到像viewPager那樣的回彈效果,具體邏輯在自定義的RecyclerView中的回調方法onScrollStateChanged中實現:orm
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初始速率,代碼以下:blog
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 }