TRecyclerView-實現RecycleView的下拉更新,上滑載入
1. 前言
SwipeRefreshLayout是google出的一個支援下拉更新,上滑載入的控制元件,有如下兩個問題:
1)上滑載入資料需要我們自己實現;
2)loading動畫會隨著手勢向下移動,其實好多情況下我們可能需要固定頂部的loading動畫。
針對上面第一個問題, SuperSwipeRefreshLayout 是基於SwipeRefreshLayout,實現了上滑的資料載入部分,並對不同的控制元件,如ListView,RecyclerView等做了很好的封裝,專案github地址: ofollow,noindex">SuperSwipeRefreshLayout-Demo 。
SuperSwipeRefreshLayout裡面沒有設定loading動畫是否隨手勢移動的API,因此沒有解決第二個問題。 TRecyclerView 為解決上面兩個問題,處理了上滑資料載入的邏輯,同時固定loading動畫在頂部位置,下面主要講講TRecyclerView的具體實現方式。
2. TRecyclerView
1) TRecyclerView 的結構
TRecycleView是一個FrameLayout主要包括兩部分,Header View和RecycleView,而RecycleView的View型別大體分為兩部分:Normal View和Footer View。
TRecyclerView中有一個 TRecyclerAdapter ,是用來載入RecyclerView的Item View,是TRecyclerView中真正載入資料的Adapter,其中包括兩大類的資料型別,即正常的 Normal View和Header View ,Normal View是通過RecyclerView.Adapter來載入,就是我們需要寫的Adapter。

TRecyclerView的結構圖
2)TRecyclerView的初始化
下面結合TRecyclerView的結構圖,我們看看具體的程式碼實現,首先是TRecycleView的構造方法:
public TRecyclerView(Context context) { super(context); init(context); } private void init(Context ctx) { mCtx = ctx; mTouchSlop = ViewConfiguration.get(mCtx).getScaledTouchSlop(); initView(); } private void initView() { //建立一個Header View createHeaderView(); addTargetView(); linearLayoutManager = new LinearLayoutManager(mCtx); mRecyclerView.setLayoutManager(linearLayoutManager); mRecyclerView.setVerticalScrollBarEnabled(true); initListener(); } //Header View是一個RelativeLayout,同時往Header View裡面新增一個自己的Header,我們可以自定義樣式 private void createHeaderView() { mHeaderContainer = new RelativeLayout(mCtx); mHeaderHeight = (int)mCtx.getResources().getDimension(R.dimen.header_height); mTipHeight =(int)mCtx.getResources().getDimension(R.dimen.header_tip_height); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderHeight); params.gravity = Gravity.TOP; addView(mHeaderContainer, params); setHeaderView(); } //設定自己的Header private void setHeaderView(){ mHeaderHolder = new HeaderHolder(mCtx); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, mHeaderHeight); mHeaderContainer.addView(mHeaderHolder.getHeaderView(), params); mHeaderHolder.setAnimListener(mAnimListener); } //新增一個RecycleView,因為TRecycleView是一個FrameLayout,因此,RecycleView是處在在Header View的上面 privatevoid addTargetView(){ mRecyclerView = new RecyclerView(mCtx); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); addView(mRecyclerView, params); }
3)TRecyclerAdapter的實現
我們知道TRecyclerView中真正載入資料的Adapter是TRecyclerAdapter,我們看看TRecyclerView設定RecyclerView.Adapter的API,程式碼如下:
public void setAdapter(RecyclerView.Adapter adapter){ adapter.registerAdapterDataObserver(mDataObserver); mTAdapter = new TRecyclerAdapter(mCtx, adapter); mRecyclerView.setAdapter(mTAdapter); }
我們給RecyclerView.Adapter註冊了一個觀察者,呼叫RecyclerView.Adapter的資料更新方法時,會通知TRecyclerAdapter去更新資料資料,程式碼如下:
private RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { mTAdapter.notifyDataSetChanged(); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { mTAdapter.notifyItemRangeChanged(positionStart, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { mTAdapter.notifyItemRangeChanged(positionStart , itemCount, payload); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { mTAdapter.notifyItemRangeInserted(positionStart , itemCount); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { mTAdapter.notifyItemRangeRemoved(positionStart , itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { mTAdapter.notifyItemMoved(fromPosition, toPosition ); } };
再看看TRecyclerAdapter的 onCreateViewHolder 和 onBindViewHolder 方法的實現。
onCreateViewHolder方法:
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return buildHolder(parent, viewType); } private RecyclerView.ViewHolder buildHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder holder = null; switch (viewType) { case ITEM_TYPE_FOOTER: //Footer View的型別 holder = new BaseViewHolder(mFooterHolder.getFooterView()); break; default: //Normal View 的型別 holder = mAdapter.onCreateViewHolder(parent, viewType); break; } return holder; } @Override public int getItemViewType(int position) { if (isFooter(position)) { //底部View return ITEM_TYPE_FOOTER; } else { return mAdapter.getItemViewType(position); } }
onBindViewHolder方法:
//如果是Footer View型別,則直接返回,否則呼叫mAdapter的onBindViewHolder方法 @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { if (isFooter(position)) { return; } initData(holder, position); } private void initData(RecyclerView.ViewHolder holder, final int position) { final int type = getItemViewType(position); if (type != ITEM_TYPE_FOOTER) { mAdapter.onBindViewHolder(holder, position); } }
4)TRecyclerView下拉更新資料
TRecycleView下拉超過一定的高度,這個高度是Header View的高度,鬆開手後開始載入資料,下面看看TRecyclerView的 onInterceptTouchEvent 方法和 onTouchEvent 方法。
onInterceptTouchEvent(MotionEvent ev) 方法:
ublic boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); //如果RecyclerView能夠向下或者向上滾動,則不攔截事件,事件交給RecyclerView處理 if(isUnIntercept()){ returnfalse; } switch (action) { case MotionEvent.ACTION_DOWN: mIsDrag = false; mInitY = ev.getY(); break; case MotionEvent.ACTION_MOVE: float y = ev.getY(); //如果TRecyclerView下拉超過一定距離,則認為TRecyclerView是在被拖拽,同時設定mIsDrag標誌為true if(y-mInitY >= mTouchSlop && !mIsDrag){ mIsDrag = true; } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mIsDrag = false; break; default: break; } return mIsDrag; }
onTouchEvent(MotionEvent event) 方法:
public boolean onTouchEvent(MotionEvent event) { if(isUnIntercept()){ return false; } float dist = 0f; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mIsDrag = false; break; case MotionEvent.ACTION_MOVE: float y = event.getY(); dist = (y - mInitY)* TRecycleViewConst.PULL_DRAG_RATE; if(dist > 0){ //下拉移動TRecyclerView mRecyclerView.setTranslationY(getY() + dist); } if(mIsDrag){ //下拉距離超過Header View的高度,則鬆手後可以重新整理資料 if(mPullRefresh != null){ mPullRefresh.pullRefreshEnable(dist >= mHeaderHeight); } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: dist = (event.getY() - mInitY) * TRecycleViewConst.PULL_DRAG_RATE; if(mIsDrag){ //鬆手後,如果下拉距離超過Header View的高度,則執行回彈到HeaderView位置的動畫,動畫結束後,呼叫重新整理資料的方法;否則執行回彈到初始位置的動畫 if(dist >= mHeaderHeight){ animToHeader(); }else{ animToStart(); } } mIsDrag = false; break; } return true; }
animToHeader方法:
private void animToHeader(){ ObjectAnimator animator = ObjectAnimator.ofFloat(mRecyclerView,"translationY",mHeaderHeight); animator.addListener(mToHeaderListener); animator.setDuration(AnimDurConst.ANIM_TO_HEADER_DUR); animator.start(); } private Animator.AnimatorListener mToHeaderListener = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //回彈動畫結束後,開始呼叫pullRefresh的回撥,在這裡面可以載入網路資料 if(mPullRefresh != null){ mRefresh = true; mPullRefresh.pullRefresh(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } };
5)TRecyclerView上滑載入資料
上面講了TRecyclerView下拉更新資料的過程,下面講講TRecyclerView上滑載入資料的實現。
看上面的結構圖,我們知道Footer View並不是直接作為TRecyclerView的一個View,而是RecyclerView的一個Item View,因此,當RecyclerView上滑到最後一個Item View,即Footer View可見時,我們可以處理上滑載入資料的邏輯,程式碼的實現如下:
private void initListener(){ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); //如果RecyclerView的Scroll State是IDLE,我們判斷下RecyclerView是否已經滑動到底部,如果是則執行loadMore方法回撥 if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (targetInBottom()) { if(mPushRefresh != null){ mLoadMore = true; mPushRefresh.loadMore(); } } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); } }); } //滑動到底部,且最後一個元素可見,則認為到達底部 private boolean targetInBottom() { if (targetInTop()) { return false; } RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); int count = mRecyclerView.getAdapter().getItemCount(); if (layoutManager instanceof LinearLayoutManager && count > 0) { LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; if (linearLayoutManager.findLastVisibleItemPosition() == count - 1) { return true; } } return false; }
3. TRecyclerView例項
我們自個寫了一個NewsRecyclerAdapter,通過TRecyclerView實行了資料的下拉更新,上滑載入的邏輯。
下拉更新資料:

pull_down_refresh_data
上滑載入資料:

slide_up_to_refresh_data
TRecyclerView專案地址: TRecyclerView 。
4. 總結
TRecyclerView還存如下一些問題,後續會優化。
1)TRecyclerView中的RecyclerView沒有滾動條,這是因為我們直接new RecyclerView,RecyclerView的一些初始化方法沒有執行到。
解法方法:RecyclerView通過inflate的方式去載入一個xml檔案。
2)TRecyclerView在下拉載入資料的時候,可以再次下拉重新整理資料,這個後續會優化。
3)TRecyclerView有一些業務邏輯的耦合,如tips的顯示動畫。
TRecyclerView專案地址: TRecyclerView 。