以下內容為原創,轉載請注明:
來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4268097.html
這次來使用RecyclerView實現PinnedListView的效果,效果很常見:
開發的代碼建立在上一篇([Android]使用RecyclerView替代ListView(二):http://www.cnblogs.com/tiantianbyconan/p/4242541.html)基礎之上。
修改布局如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 4 android:orientation="vertical" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent"> 7 8 <android.support.v7.widget.Toolbar 9 android:id="@+id/recycler_view_pinned_toolbar" 10 android:layout_height="wrap_content" 11 android:layout_width="match_parent" 12 android:background="?attr/colorPrimary" 13 /> 14 <android.support.v4.widget.SwipeRefreshLayout 15 android:id="@+id/recycler_view_pinned_srl" 16 android:layout_width="match_parent" 17 android:layout_height="wrap_content" 18 > 19 20 <com.wangjie.androidbucket.support.recyclerview.pinnedlayout.PinnedRecyclerViewLayout 21 android:id="@+id/recycler_view_pinned_layout" 22 android:layout_width="match_parent" android:layout_height="match_parent"> 23 <android.support.v7.widget.RecyclerView 24 android:id="@+id/recycler_view_pinned_rv" 25 android:scrollbars="vertical" 26 android:layout_width="match_parent" 27 android:layout_height="match_parent" 28 android:background="#bbccaa" 29 /> 30 <Button 31 android:id="@+id/recycler_view_pinned_add_btn" 32 android:layout_width="wrap_content" android:layout_height="wrap_content" 33 android:layout_centerVertical="true" 34 android:background="#abcabc" 35 android:text="add" 36 /> 37 38 </com.wangjie.androidbucket.support.recyclerview.pinnedlayout.PinnedRecyclerViewLayout> 39 40 </android.support.v4.widget.SwipeRefreshLayout> 41 42 </LinearLayout>
可以看到RecyclerView是被一個PinnedRecyclerViewLayout(https://github.com/wangjiegulu/AndroidBucket/blob/master/src/com/wangjie/androidbucket/support/recyclerview/pinnedlayout/PinnedRecyclerViewLayout.java)包含在里面的。這個在項目AndroidBucket(https://github.com/wangjiegulu/AndroidBucket)中。先看看代碼中怎么使用吧,具體實現待會說。
1 pinnedLayout.initRecyclerPinned(recyclerView, layoutManager, LayoutInflater.from(context).inflate(R.layout.recycler_view_item_float, null)); 2 pinnedLayout.setOnRecyclerViewPinnedViewListener(this);
如上,使用方式很簡單:
Line1:初始化綁定PinnedRecyclerViewLayout和RecyclerView,並設置需要被頂上去的pinnedView
Line2:設置OnRecyclerViewPinnedViewListener,作用是在頂部被頂上去替換掉的時候,會回調重新渲染數據,傳入的OnRecyclerViewPinnedViewListener是this,顯然,此Activity實現了這個接口,實現代碼如下:
1 // 渲染pinnedView數據 2 @Override 3 public void onPinnedViewRender(PinnedRecyclerViewLayout pinnedRecyclerViewLayout, View pinnedView, int position) { 4 switch (pinnedRecyclerViewLayout.getId()) { 5 case R.id.recycler_view_pinned_layout: 6 TextView nameTv = (TextView) pinnedView.findViewById(R.id.recycler_view_item_float_name_tv); 7 nameTv.setText(personList.get(position).getName()); 8 TextView ageTv = (TextView) pinnedView.findViewById(R.id.recycler_view_item_float_age_tv); 9 ageTv.setText(personList.get(position).getAge() + "歲"); 10 break; 11 } 12 }
然后,我們來看看PinnedRecyclerViewLayout是怎么實現的。
1 /** 2 * Author: wangjie 3 * Email: tiantian.china.2@gmail.com 4 * Date: 2/2/15. 5 */ 6 public class PinnedRecyclerViewLayout extends RelativeLayout { 7 8 private static final String TAG = PinnedRecyclerViewLayout.class.getSimpleName(); 9 10 public static interface OnRecyclerViewPinnedViewListener { 11 void onPinnedViewRender(PinnedRecyclerViewLayout pinnedRecyclerViewLayout, View pinnedView, int position); 12 } 13 14 private OnRecyclerViewPinnedViewListener onRecyclerViewPinnedViewListener; 15 16 public void setOnRecyclerViewPinnedViewListener(OnRecyclerViewPinnedViewListener onRecyclerViewPinnedViewListener) { 17 this.onRecyclerViewPinnedViewListener = onRecyclerViewPinnedViewListener; 18 } 19 20 public PinnedRecyclerViewLayout(Context context) { 21 super(context); 22 init(context); 23 } 24 25 public PinnedRecyclerViewLayout(Context context, AttributeSet attrs) { 26 super(context, attrs); 27 init(context); 28 } 29 30 public PinnedRecyclerViewLayout(Context context, AttributeSet attrs, int defStyleAttr) { 31 super(context, attrs, defStyleAttr); 32 init(context); 33 } 34 35 private void init(Context context) { 36 } 37 38 private View pinnedView; 39 private ABaseLinearLayoutManager layoutManager; 40 41 public void initRecyclerPinned(RecyclerView recyclerView, ABaseLinearLayoutManager layoutManager, View pinnedView) { 42 this.pinnedView = pinnedView; 43 this.layoutManager = layoutManager; 44 this.addView(this.pinnedView); 45 RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 46 this.pinnedView.setLayoutParams(lp); 47 layoutManager.getRecyclerViewScrollManager().addScrollListener(recyclerView, new OnRecyclerViewScrollListener() { 48 @Override 49 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 50 } 51 52 @Override 53 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 54 refreshPinnedView(); 55 } 56 }); 57 pinnedView.setVisibility(GONE); 58 } 59 60 // 保存上次的position 61 private int lastPosition = RecyclerView.NO_POSITION; 62 63 public void refreshPinnedView() { 64 if (null == pinnedView || null == layoutManager) { 65 Logger.e(TAG, "Please init pinnedView and layoutManager with initRecyclerPinned method first!"); 66 return; 67 } 68 if (VISIBLE != pinnedView.getVisibility()) { 69 pinnedView.setVisibility(VISIBLE); 70 } 71 int curPosition = layoutManager.findFirstVisibleItemPosition(); 72 if (RecyclerView.NO_POSITION == curPosition) { 73 return; 74 } 75 View curItemView = layoutManager.findViewByPosition(curPosition); 76 if (null == curItemView) { 77 return; 78 } 79 // 如果當前的curPosition和上次的lastPosition不一樣,則說明需要重新刷新數據,避免curPosition一樣的情況下重復刷新相同數據 80 if (curPosition != lastPosition) { 81 if (null != onRecyclerViewPinnedViewListener) { 82 onRecyclerViewPinnedViewListener.onPinnedViewRender(this, pinnedView, curPosition); 83 } 84 lastPosition = curPosition; 85 } 86 87 int displayTop; 88 int itemHeight = curItemView.getHeight(); 89 int curTop = curItemView.getTop(); 90 int floatHeight = pinnedView.getHeight(); 91 if (curTop < floatHeight - itemHeight) { 92 displayTop = itemHeight + curTop - floatHeight; 93 } else { 94 displayTop = 0; 95 } 96 RelativeLayout.LayoutParams lp = (LayoutParams) pinnedView.getLayoutParams(); 97 lp.topMargin = displayTop; 98 pinnedView.setLayoutParams(lp); 99 pinnedView.invalidate(); 100 } 101 102 103 }
這個PinnedRecyclerViewLayout 是繼承RelativeLayout的,因為我們需要在里面添加一個被頂上去的pinnedView,需要覆蓋在RecyclerView上面。
Line44:把傳進來的pinnedView增加到PinnedRecyclerViewLayout 里面
Line47~56:在ABaseLinearLayoutManager中增加一個滾動的監聽器,因為我們需要在滾動的時候動態的改變pinnedView的位置,這樣才能模擬頂上去的效果。並滾動時調用refreshPinnedView來刷新pinnedView的位置。
Line57:因為在調用initRecyclerPinned方法時,RecyclerView可能還沒有數據源,所以不需要顯示這個pinnedView,等到真正滾動的時候再顯示就可以了。
refreshPinnedView()方法的作用是在滾動的同時用來刷新pinnedView的位置和顯示的數據:
Line71~78:通過layoutManager獲取當前第一個顯示的數據position,然后根據position獲取當前第一個顯示的View。
Line79~85:如果當前的curPosition和上次的lastPosition不一樣,則說明需要重新刷新數據,避免curPosition一樣的情況下重復刷新相同數據。
Line87~95:根據當前第一個顯示的View,根據它的top、它的高度和pinnedView的高度計算出pinnedView需要往上移動的距離(畫個幾何圖一目了然了)。
Line96~99:刷新pinnedView的位置
示例代碼:
https://github.com/wangjiegulu/RecyclerViewSample
[Android]使用RecyclerView替代ListView(一):
http://www.cnblogs.com/tiantianbyconan/p/4232560.html