最近項目中遇到了這麼個需求,媽的竟然和高德地圖實現一模一樣的功能。因爲保密性原則,我就直接上高德地圖的截圖了。
首先這麼一步操作,輸入起始點,
之後呢,就進入這個界面
看到這裏,大家應該清楚我說的需求吧,好吧,不講邏輯,單單講一下這個界面 實現。因爲數據都是動態生成的,每次搜索的結果對應的list的大小和內容都是不一樣的,所以呢,我們做這個界面的時候,也需要遵循數據源定的遊戲規則,我們界面上也要做成動態的。並且是可複用的,這樣不論是在操作還是性能上,都會很好。
這個佈局主要分兩大部分,
1,頭部的水平recycle +指示器
2,頭部以下的分級列表
好,分析完大的結構,然後我們再來看一下他們的關係,頭部的滑動,會影響到下面列表內容的展示,實施更新對應的數據。
所以我們需要對recycle 的滑動做監聽,目的是有兩個:1,及時更新指示器 ; 2,及時更新分級列表內容。
下面分享一下我個人這個界面的實現過程。將Recycleview 設置成水平滑動模式,很簡單,就這麼一句話。
hLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(hLinearLayoutManager);恩,這就實現了水平滑動,但是單單實現水平滑動是不夠的,滑動的距離要是一個item 的寬度才行,這樣才能像高仿的viewpager。那麼很關鍵的東西出現了。Scroller ,處理recycle滑動分頁的工具類。我們自定義的Scorller 繼承於recycleview 的onscroollIstener。
最開始是看到這位兄臺的博客得到的靈感:一行代碼讓RecyclerView變身ViewPager
因爲他沒有實現 指示器的功能,所以我在他的基礎上,把指示器添加了進來。下面是我自己的Scroller:
public class PagingScrollHelper { RecyclerView mRecyclerView = null; private RouteTopAdapter myAdapter = null; private MyOnScrollListener mOnScrollListener = new MyOnScrollListener(); private MyOnFlingListener mOnFlingListener = new MyOnFlingListener(); private int offsetY = 0; private int offsetX = 0; int startY = 0; int startX = 0; private PageIndicatorView mIndicatorView = null; private int index = 0; private int totalPage = 0; enum ORIENTATION { HORIZONTAL, VERTICAL, NULL } ORIENTATION mOrientation = ORIENTATION.HORIZONTAL; public void setUpRecycleView(RecyclerView recycleView) { if (recycleView == null) { throw new IllegalArgumentException("recycleView must be not null"); } mRecyclerView = recycleView; //處理滑動 recycleView.setOnFlingListener(mOnFlingListener); //設置滾動監聽,記錄滾動的狀態,和總的偏移量 recycleView.setOnScrollListener(mOnScrollListener); //記錄滾動開始的位置 recycleView.setOnTouchListener(mOnTouchListener); //獲取滾動的方向 updateLayoutManger(); } public void updateLayoutManger() { RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager != null) { if (layoutManager.canScrollVertically()) { mOrientation = ORIENTATION.VERTICAL; } else if (layoutManager.canScrollHorizontally()) { mOrientation = ORIENTATION.HORIZONTAL; } else { mOrientation = ORIENTATION.NULL; } if (mAnimator != null) { mAnimator.cancel(); } startX = 0; startY = 0; offsetX = 0; offsetY = 0; } } ValueAnimator mAnimator = null; public class MyOnFlingListener extends RecyclerView.OnFlingListener { @Override public boolean onFling(int velocityX, int velocityY) { if (mOrientation == ORIENTATION.NULL) { return false; } //獲取開始滾動時所在頁面的index int p = getStartPageIndex(); //記錄滾動開始和結束的位置 int endPoint = 0; int startPoint = 0; //如果是垂直方向 if (mOrientation == ORIENTATION.VERTICAL) { startPoint = offsetY; if (velocityY < 0) { p--; } else if (velocityY > 0) { p++; } //更具不同的速度判斷需要滾動的方向 //注意,此處有一個技巧,就是當速度爲0的時候就滾動會開始的頁面,即實現頁面復位 endPoint = p * mRecyclerView.getHeight(); } else { startPoint = offsetX; if (velocityX < 0) { p--; } else if (velocityX > 0) { p++; } endPoint = p * mRecyclerView.getWidth(); } if (endPoint < 0) { endPoint = 0; } //使用動畫處理滾動 if (mAnimator == null) { mAnimator = new ValueAnimator().ofInt(startPoint, endPoint); mAnimator.setDuration(300); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int nowPoint = (int) animation.getAnimatedValue(); if (mOrientation == ORIENTATION.VERTICAL) { int dy = nowPoint - offsetY; //這裏通過RecyclerView的scrollBy方法實現滾動。 mRecyclerView.scrollBy(0, dy); } else { int dx = nowPoint - offsetX; mRecyclerView.scrollBy(dx, 0); } } }); mAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { //回調監聽 if (null != mOnPageChangeListener) { mOnPageChangeListener.onPageChange(getPageIndex()); } } }); } else { mAnimator.cancel(); mAnimator.setIntValues(startPoint, endPoint); } mAnimator.start(); return true; } } public class MyOnScrollListener extends RecyclerView.OnScrollListener { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { //newState==0表示滾動停止,此時需要處理回滾 if (newState == 0 && mOrientation != ORIENTATION.NULL) { boolean move; int vX = 0, vY = 0; if (mOrientation == ORIENTATION.VERTICAL) { int absY = Math.abs(offsetY - startY); //如果滑動的距離超過屏幕的一半表示需要滑動到下一頁 move = absY > recyclerView.getHeight() / 2; vY = 0; if (move) { vY = offsetY - startY < 0 ? -1000 : 1000; } } else { int absX = Math.abs(offsetX - startX); move = absX > recyclerView.getWidth() / 2; if (move) { vX = offsetX - startX < 0 ? -1000 : 1000; } } mOnFlingListener.onFling(vX, vY); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { //滾動結束記錄滾動的偏移量 offsetY += dy; offsetX += dx; } } private MyOnTouchListener mOnTouchListener = new MyOnTouchListener(); public class MyOnTouchListener implements View.OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { //手指按下的時候記錄開始滾動的座標 if (event.getAction() == MotionEvent.ACTION_DOWN) { startY = offsetY; startX = offsetX; } return false; } } private int getPageIndex() { int p = 0; if (mOrientation == ORIENTATION.VERTICAL) { p = offsetY / mRecyclerView.getHeight(); } else { p = offsetX / mRecyclerView.getWidth(); } mIndicatorView.setSelectedPage(p); return p; } private int getStartPageIndex() { int p = 0; if (mOrientation == ORIENTATION.VERTICAL) { p = startY / mRecyclerView.getHeight(); } else { p = startX / mRecyclerView.getWidth(); } return p; } onPageChangeListener mOnPageChangeListener; public void setOnPageChangeListener(onPageChangeListener listener) { mOnPageChangeListener = listener; } public interface onPageChangeListener { void onPageChange(int index); } public void setIndicator(PageIndicatorView indicatorView) { this.mIndicatorView = indicatorView; } public void setAdapter(RecyclerView.Adapter adapter) { this.myAdapter = (RouteTopAdapter) adapter; update(); } // 更新頁碼指示器和相關數據 private void update() { int temp = myAdapter.getItemCount(); if (temp != totalPage) { mIndicatorView.initIndicator(temp); if (temp < totalPage && index == totalPage) { index = temp; } mIndicatorView.setSelectedPage(index); totalPage = temp; } } }上面有詳細的註釋,這個類如果你想用的話,可以直接去copy。
這個是Indicator類的代碼
public class PageIndicatorView extends LinearLayout { private Context mContext = null; private int dotSize = 15; // 指示器的大小(dp) private int margins = 4; // 指示器間距(dp) private List<View> indicatorViews = null; // 存放指示器 public PageIndicatorView(Context context) { this(context, null); } public PageIndicatorView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PageIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { this.mContext = context; setGravity(Gravity.CENTER); setOrientation(HORIZONTAL); dotSize = dip2px(context, dotSize); margins = dip2px(context, margins); } // 初始化指示器,默認選中第一頁 public void initIndicator(int count) { if (indicatorViews == null) { indicatorViews = new ArrayList<>(); } else { indicatorViews.clear(); removeAllViews(); } View view; LayoutParams params = new LayoutParams(dotSize, dotSize); params.setMargins(margins, margins, margins, margins); for (int i = 0; i < count; i++) { view = new View(mContext); view.setBackgroundResource(android.R.drawable.presence_invisible); addView(view, params); indicatorViews.add(view); } if (indicatorViews.size() > 0) { indicatorViews.get(0).setBackgroundResource(android.R.drawable.presence_offline); } } //設置選中頁 public void setSelectedPage(int selected) { for (int i = 0; i < indicatorViews.size(); i++) { if (i == selected) { indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_offline); } else { indicatorViews.get(i).setBackgroundResource(android.R.drawable.presence_invisible); } } } }
這兩個關鍵類已經給出。其他的就是自己來實例化Adapter ,在XML佈局中添加 PagerIndicatorView控件。
綁定adapter 和 scroller。即可。
不明白的可以諮詢我。