Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> RecycleView從顯示到下拉刷新和加載更多

RecycleView從顯示到下拉刷新和加載更多

編輯:關於Android編程

RecycleView出來已經有一兩個年頭了最近在項目中完全替換掉了ListView很有必要的寫一篇記錄一下使用過程,以便以後溫故而知新。

RecycleView的使用場景開始到初始化

RecycleView可以用於展示列表式、網格式、瀑布流式風格的界面而且使用方便,可以這麼來講任何ListView能做到的功能RecycleView它也能做到而且能輕松駕馭比ListView更加強大的功能,從網上找到這麼一句話來形容RecycleView再適合不過了。

由於尺寸限制,用戶的設備不能一次性展現所有條目,用戶需要上下滾動以查看更多條目。滾出可見區域的條目將被回收,並在下一個條目可見的時候被復用。

看一段初始化代碼

mRecycleView =(RecyclerView)findViewById(R.id.id_recycleview);
LinearLayoutManager manager = new LinearLayoutManager(this);
mRecycleView.setLayoutManager(manager);

輕松定義了一個RecycleView然後我們傳入一個RecyclerView.Adapter後那麼一個列表式的界面就出來了。

public class AppRecycleAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private List beans;

    public AppRecycleAdapter(Context mContext, List beans) {
        this.mContext = mContext;
        this.beans = beans;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new AppViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder _holder, int position) {
        AppViewHolder holder = (AppViewHolder) _holder;
        holder.imageView.setImageDrawable(beans.get(position).appIcon);
        holder.textView.setText(beans.get(position).appName);
    }

    @Override
    public int getItemCount() {
        return beans.size();
    }

    public class AppViewHolder extends RecyclerView.ViewHolder {

        ImageView imageView;
        TextView textView;

        public AppViewHolder(View itemView) {
            super(itemView);
            imageView = (ImageView) itemView.findViewById(R.id.app_icon);
            textView = (TextView) itemView.findViewById(R.id.app_name);
        }
    }
}

在RecyclerView.Adapter中我們需要去重寫三個函數並且還需要頂一個ViewHolder,這個ListView中采用ViewHolder實現View復用在本質上是一個道理的。

AppRecycleAdapter adapter = new AppRecycleAdapter(this, beans);
mRecycleView.setAdapter(adapter);

跟ListView設置BaseAdapter一樣,來不及想看下效果了:

這裡寫圖片描述

如果你按照上面的寫法你會發現那個淡藍色的線條並不能顯示,我當時就直接在RecycleView的聲明xml文件中按照ListView那樣聲明了一個divider,當然結果並不是我們想象的那樣因為RecycleView的divider並不能在xml文件中聲明就可以的。

使用RecyclerView.ItemDecZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcmF0aW9uyrXP1mRpdmlkZXK5psTcPC9zdHJvbmc+PC9wPg0KPHA+v7TPws/Cw+bV4rj2t723qKOsw7u07VJlY3ljbGVWaWV3vs3Kx82ouf1JdGVtRGVjb3JhdGlvbsC0u61kaXZpZGVyo6zDu7Ttyse7rbP2wLS1xLK7ysfJ+cP3s/bAtLXEoaM8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> mRecycleView.addItemDecoration(decoration);

這個類中我們通常可以去實現以下三個方法來實現各種divider效果:

// 通常用於畫divider可以在itemView的左上右下隨意畫
onDraw(Canvas c, RecyclerView parent, State state)
// 通常用於畫需要懸浮在itemView上的蒙層
onDrawOver(Canvas c, RecyclerView parent, State state)
// 類似padding可以理解為通過outRect來設置itemView的padding來標示我們divider或者蒙層畫在哪、畫多大
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

上一段demo裡面的代碼可以更加深刻的理解這些意思。

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        drawVertical(c, parent);
    }

    private void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();
        final int itemCount = parent.getChildCount();
        for (int i = 0; i < itemCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
            final int top = child.getBottom() + lp.bottomMargin;
            final int bottom = top + dividerHeight;
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.set(0, 0, 0, dividerHeight);// 設置這個ItemView的哪個方向的padding值。
    }

onDraw這個方法會在item繪制時候回調,onDrawOver會在item繪制結束的時候回調,所以onDraw適合用於話divider而onDrawOver適合於畫蒙層的效果。經過這麼一步之後我們重新運行一下項目就會跟上面的圖的效果一樣出現了藍色效果。

兩種方式實現RecycleView的點擊事件

第一種,采用OnItemTouchListener來實現點擊事件。

public void addOnItemTouchListener(OnItemTouchListener listener) {
    mOnItemTouchListeners.add(listener);
}

RecycleView通過這個方法來實現點擊事件的,但是OnItemTouchListener需要實現的方法太多了,所以官方推薦使用SimpleOnItemTouchListener來實現。

public class AppClickListener extends RecyclerView.SimpleOnItemTouchListener {

    private GestureDetectorCompat gestureDetector;

    public interface ItemClickListener {
        void onItemClick(View view, int position);
    }

    public AppClickListener(final RecyclerView recyclerView, final ItemClickListener listener) {
        gestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                if (listener != null) {
                    listener.onItemClick(childView, recyclerView.getChildAdapterPosition(childView));
                }
                return true;
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        gestureDetector.onTouchEvent(e);
        return super.onInterceptTouchEvent(rv, e);
    }
}

我們通過實現GestureDetector.SimpleOnGestureListener中的onSingleTapUp方法拿到用戶點擊的MotionEvent的x、y軸的值然後通過recyclerView.findChildViewUnder來確定手指點中的是哪個view,有了view之後我們又通過recyclerView.getChildAdapterPosition就可以輕松知道點中的position位置了,最後是不是就可以想干嘛就干嘛了。

第二種,通過給itemView設置點擊事件來處理。

直接給出實現過程:

    public class AppViewHolder extends RecyclerView.ViewHolder {

        ImageView imageView;
        TextView textView;

        public AppViewHolder(View itemView) {
            super(itemView);
            imageView = (ImageView) itemView.findViewById(R.id.app_icon);
            textView = (TextView) itemView.findViewById(R.id.app_name);

//             另一種設置點擊事件..
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();
                    Toast.makeText(mContext, beans.get(position).appName, Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

沒錯我們就是直接寫在ViewHolder中,通過getAdapterPosition這個方法來獲取當前點中的position,只要我們拿到position後就什麼都好辦了。

其實很多覺得還有一種方式直接在RecycleView.Adapter的onBindViewHolder方法中給view設置點擊事件,但是這有一個不好的效果每次復用的時候都得重新設置一個OnClickListener那麼有上萬個item不就有上個點擊對象了,這樣肯定是不行的,所以我們推崇上面這兩種方式,第一種我們只需要new出一個對象通過x、y值來得到position,而第二種只需要new出一個屏幕item數量對象就好。相比之下肯定是設置OnItemTouchListener這種方式是為最佳的。

如何實現達到底部就加載更多

首先我們需要監聽什麼時候到達底部,沒錯RecycleView跟ListView一樣有一個RecyclerView.OnScrollListener滾動事件我們可以監聽到達底部了沒。通過獲取LayoutManager然後調用LayoutManager中的一系列方法進行比對,來判斷是否到達了底部。

findFirstVisibleItemPosition() 返回當前第一個可見Item的position
findFirstCompletelyVisibleItemPosition() 返回當前第一個完全可見Item的position
findLastVisibleItemPosition() 返回當前最後一個可見Item的position
findLastCompletelyVisibleItemPosition() 返回當前最後一個完全可見Item的position

這邊demo中采用的是一個優秀的開源庫很方便的實現這些功能:
https://github.com/cundong/HeaderAndFooterRecyclerView
這邊坐下這個庫的源碼解讀,首先看下如何監聽到達底部的實現,把主要代碼摳出來其他的代碼刪除。

public class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener implements OnListLoadNextPageListener {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

        if (layoutManagerType == null) {
            if (layoutManager instanceof LinearLayoutManager) {
                layoutManagerType = LayoutManagerType.LinearLayout;
            }
        }

        switch (layoutManagerType) {
            case LinearLayout:
                lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
                break;
        }
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        currentScrollState = newState;
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        int visibleItemCount = layoutManager.getChildCount();
        int totalItemCount = layoutManager.getItemCount();
        if ((visibleItemCount > 0 && currentScrollState == RecyclerView.SCROLL_STATE_IDLE && (lastVisibleItemPosition) >= totalItemCount - 1)) {
            onLoadNextPage(recyclerView);
        }
    }
}

在onScroll方法中通過得到的LayoutManager從而得到當前屏幕可見的最後一項的position,然後在onScrollStateChanged方法中進行比對是否滿足到達底部的要求,這個類看起來還是比較簡單和明了的,主要是運用上面列出來的LayoutManager的api方法。
除了EndlessRecyclerOnScrollListener這個類外還有一個HeaderAndFooterRecyclerViewAdapter也是主要的類,裡面主要涉及通知顯示的Adapter進行數據刷新,通過setAdapter這個方法把我們需要顯示的adapter跟HeaderAndFooterRecyclerViewAdapter這個類進行綁定起來。

    public void setAdapter(RecyclerView.Adapter adapter) {

        if (adapter != null) {
            if (!(adapter instanceof RecyclerView.Adapter))
                throw new RuntimeException("your adapter must be a RecyclerView.Adapter");
        }

        if (mInnerAdapter != null) {
            notifyItemRangeRemoved(getHeaderViewsCount(), mInnerAdapter.getItemCount());
            mInnerAdapter.unregisterAdapterDataObserver(mDataObserver);
        }

        this.mInnerAdapter = adapter;
        mInnerAdapter.registerAdapterDataObserver(mDataObserver);
        notifyItemRangeInserted(getHeaderViewsCount(), mInnerAdapter.getItemCount());
    }

然後通過getItemViewType來區分如果是底部的話就顯示加載更多布局。

    public int getItemViewType(int position) {
        int innerCount = mInnerAdapter.getItemCount();
        int headerViewsCountCount = getHeaderViewsCount();
        if (position < headerViewsCountCount) {
            return TYPE_HEADER_VIEW + position;
        } else if (headerViewsCountCount <= position && position < headerViewsCountCount + innerCount) {

            int innerItemViewType = mInnerAdapter.getItemViewType(position - headerViewsCountCount);
            if(innerItemViewType >= Integer.MAX_VALUE / 2) {
                throw new IllegalArgumentException("your adapter's return value of getViewTypeCount() must < Integer.MAX_VALUE / 2");
            }
            return innerItemViewType + Integer.MAX_VALUE / 2;
        } else {
            return TYPE_FOOTER_VIEW + position - headerViewsCountCount - innerCount;
        }
    }

這些還是比較好理解的,那麼如何控制顯示呢?可以從代碼調用處看起然後一行一行點進去查看源碼就能理解了。

        AppRecycleAdapter adapter = new AppRecycleAdapter(this, beans);
        mAdapter = new HeaderAndFooterRecyclerViewAdapter(adapter);
        mRecycleView.setAdapter(mAdapter);

注意setAdapter中寫入的不是我們自己寫的Adapter而是HeaderAndFooterRecyclerViewAdapter通過傳入我們自己定義的aapter後實現通過HeaderAndFooterRecyclerViewAdapter來控制adapter了。

RecyclerViewStateUtils.setFooterViewState(this, mRecycleView, INDEX, LoadingFooter.State.Normal, null);

通過這個類來寫入一個FootView,點進去看下這個方法的實現:

public static void setFooterViewState(Activity instance, RecyclerView recyclerView, int pageSize, LoadingFooter.State state, View.OnClickListener errorListener) {

        if(instance==null || instance.isFinishing()) {
            return;
        }

        RecyclerView.Adapter outerAdapter = recyclerView.getAdapter();

        if (outerAdapter == null || !(outerAdapter instanceof HeaderAndFooterRecyclerViewAdapter)) {
            return;
        }

        HeaderAndFooterRecyclerViewAdapter headerAndFooterAdapter = (HeaderAndFooterRecyclerViewAdapter) outerAdapter;

        //只有一頁的時候,就別加什麼FooterView了
        if (headerAndFooterAdapter.getInnerAdapter().getItemCount() < pageSize) {
            return;
        }

        LoadingFooter footerView;

        //已經有footerView了
        if (headerAndFooterAdapter.getFooterViewsCount() > 0) {
            footerView = (LoadingFooter) headerAndFooterAdapter.getFooterView();
            footerView.setState(state);

            if (state == LoadingFooter.State.NetWorkError) {
                footerView.setOnClickListener(errorListener);
            }
        } else {
            footerView = new LoadingFooter(instance);
            footerView.setState(state);

            if (state == LoadingFooter.State.NetWorkError) {
                footerView.setOnClickListener(errorListener);
            }
            headerAndFooterAdapter.addFooterView(footerView);
        }
    }

其實最後就是通過addFooterView這個方法給HeaderAndFooterRecyclerViewAdapter寫入一個FootView,其實這個FootView就是LoadingFooter控件,通過傳入State來控制是顯示加載中、加載完成、加載失敗等種種效果。
總的來說,通過給HeaderAndFooterRecyclerViewAdapter綁定我們的Apdater後寫入FootView,然後通過RecyclerViewStateUtils控制顯示底部。

下拉刷新

下拉刷新demo中采用的是SwipeRefreshLayout一個Android自帶的下拉刷新效果,網上也有很多優秀的開源庫例如:
https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh
就可以實現很多炫酷的下拉刷新效果。

項目地址:https://github.com/Neacy/EffectiveRecycleView

—–The End….

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved