Android基礎RecyclerView實現瀑布流(2)

前言:

前面介紹了瀑布流的基本實現,實際上瀑布流還有一些事件需要監聽。比如點擊事件,下拉和上拉事件。
這裏接着上次的 android—UI—RecyclerView實現瀑布流(1)


添加Item點擊事件:

參考文章:爲RecyclerView添加item的點擊事件
RecyclerView側重的是佈局的靈活性,雖說可以替代ListView但是連基本的點擊事件都沒有,這篇文章就來詳細講解如何爲RecyclerView的item添加點擊事件,順便複習一下觀察者模式

最終目的

所以我們的目的就是要模擬ListView的setOnItemClickListener()方法,調用者只須調用類似於setOnItemClickListener的東西就能獲得被點擊item的相關數據。

實現原理:

爲RecyclerView的每個子item設置setOnClickListener,然後在onClick中再調用一次對外封裝的接口,將這個事件傳遞給外面的調用者。而“爲RecyclerView的每個子item設置setOnClickListener”在Adapter中設置。其實直接在onClick中也能完全處理item的點擊事件,但是這樣會破壞代碼的邏輯。

實現步驟:

在這裏爲了不影響前面的數據,重寫一個適配器adapter:

MyAdapter.java

具體步驟如下:
1.繼承接口 OnClickListener

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener{ }

2.在MyAdapter中定義如下接口,模擬ListView的OnItemClickListener:

//定義接口
public static interface OnRecyclerViewItemClickListener {
        void onItemClick(View view , String data);
    }

3.聲明一個這個接口的變量:

private OnRecyclerViewItemClickListener mOnItemClickListener = null;

4.和前面的ViewHolder不同,這裏使用了自定義的ViewHolder
ViewHoider的作用就是一個儲存器,所以自定義的ViewHolder繼承於
RecyclerView.ViewHolder—RecyclerView強制使用ViewHolder,而在ListView裏面,ViewHolder只是作爲一個優化的選項。

//自定義的ViewHolder,持有每個Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;
        public ImageView mImgView;
        public ViewHolder(View view){
            super(view);
            mTextView = (TextView) view.findViewById(R.id.masonry_item_title);
            mImgView=(ImageView) view.findViewById(R.id.masonry_item_img);
        }
    }

5.在onCreateViewHolder()中爲每個item添加點擊事件

@Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup,  int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
        ViewHolder vh = new ViewHolder(view);
        //將創建的View註冊點擊事件
        view.setOnClickListener(this);
        return vh;
    }

6.將點擊事件轉移給外面的調用者:(這也是繼承接口OnClickListener必須實現的方法)

@Override
    public void onClick(View v) {
        if (mOnItemClickListener != null) {
            //注意這裏使用getTag方法獲取數據
            mOnItemClickListener.onItemClick(v,(String)v.getTag());
        }
    }

7.前面一步有個getTag方法獲取數據,那就有個setTag方法對應。在onBindViewHolder()方法裏面。

@Override
    public void onBindViewHolder(ViewHolder viewHolder,  int position) {
        viewHolder.mTextView.setText(products.get(position).getTitle());
        viewHolder.mImgView.setImageResource(products.get(position).getImg());
        //將數據保存在itemView的Tag中,以便點擊時進行獲取
        viewHolder.itemView.setTag(products.get(position).getTitle());
    }

8.最後暴露給外面的調用者,定義一個設置Listener的方法():

public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
        this.mOnItemClickListener = listener;
    }

總的MyAdapter代碼:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener{
    private List<Product> products;
    public MyAdapter(List<Product> list) {
        products = list;
    }

    private OnRecyclerViewItemClickListener mOnItemClickListener = null;

    //define interface
    public static interface OnRecyclerViewItemClickListener {
        void onItemClick(View view , String data);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.masonry_item, viewGroup, false);
        ViewHolder vh = new ViewHolder(view);
        //將創建的View註冊點擊事件
        view.setOnClickListener(this);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder,  int position) {
        viewHolder.mTextView.setText(products.get(position).getTitle());
        viewHolder.mImgView.setImageResource(products.get(position).getImg());
        //將數據保存在itemView的Tag中,以便點擊時進行獲取
        viewHolder.itemView.setTag(products.get(position).getTitle());
    }

    @Override
    public void onClick(View v) {
        if (mOnItemClickListener != null) {
            //注意這裏使用getTag方法獲取數據
            mOnItemClickListener.onItemClick(v,(String)v.getTag());
        }
    }

    public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
        this.mOnItemClickListener = listener;
    }


    //獲取數據的數量
    @Override
    public int getItemCount() {
        return products.size();
    }
    //自定義的ViewHolder,持有每個Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;
        public ImageView mImgView;
        public ViewHolder(View view){
            super(view);
            mTextView = (TextView) view.findViewById(R.id.masonry_item_title);
            mImgView=(ImageView) view.findViewById(R.id.masonry_item_img);
        }
    }
}

然後在Activity中調用即可

adapter.setOnItemClickListener(new MyAdapter.OnRecyclerViewItemClickListener(){
            @Override
            public void onItemClick(View view , String data){
                Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
            }
        });

測試:

這裏寫圖片描述

當然還可以添加點擊樣式:drawable添加顏色選擇器:
item_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorAccent" android:state_pressed="true"></item>
    <item android:drawable="@color/white"></item>
</selector>

設置item的背景色爲item_bg即可
這裏寫圖片描述

總結:

以上所有步驟都發生在自定義的adapter中,典型的觀察者模式,有點繞的地方在於,這裏涉及到兩個觀察者模式的使用,view的setOnClickListener本來就是觀察者模式,我們將這個觀察者模式的事件監聽傳遞給了我們自己的觀察者模式。

上拉和下拉事件的監聽:

在下拉刷新,上拉加載的事件中,第一步就是要監聽上啦和下拉事件。
這個很簡單,由於自帶了OnScrollListener方法

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                Log.d("ScrollListener",dx+","+dy+"");
            }
        });

位置變化:
這裏寫圖片描述

可以看出上拉爲負下拉爲正。

上拉加載:

首先判斷是否已經到底部:
判斷方法1:使用StaggeredGridLayoutManager獲取最後一個的位置,判斷是否在屏幕底部。

StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
               if (lastPositions == null) {
                   lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
               }
               staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
               lastVisibleItemPosition = findMax(lastPositions);

判斷方法2: RecyclerView每加載一個item都會調用一次onBindViewHolder方法,並且只在item由不可見變爲可見的時候纔會調用此方法。我們可以通過onBindViewHolder方法來判斷是否已經到達列表的底部。–>RecyclerView滑動到底部自動加載

public void onBindViewHolder(CollectionViewHolder holder, int position) {
        holder.fillData(mData.get(position));
        if(position == getItemCount()-1){//已經到達列表的底部
            loadMoreData();
        }
    }

這裏採用第一種方法:
具體如下:
設置兩個變量:

      //最後一個的位置
    
    private int[] lastPositions;

    
     //最後一個可見的item的位置
    
    private int lastVisibleItemPosition;

滑動事件監聽:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                int visibleItemCount = layoutManager.getChildCount();
                int totalItemCount = layoutManager.getItemCount();
                Log.i("onScrollStateChanged", "visibleItemCount" + visibleItemCount);
                Log.i("onScrollStateChanged", "lastVisibleItemPosition" + lastVisibleItemPosition);
                Log.i("onScrollStateChanged", "totalItemCount" + totalItemCount);
                Log.i("newstate","newstate"+newState);
                if (visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == totalItemCount - 2) {
                    Log.d("----------","到底了");

                }

            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //Log.d("ScrollListener",dx+","+dy+"");
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
                if (lastPositions == null) {
                    lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
                }
                staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
                lastVisibleItemPosition = findMax(lastPositions);

            }
        });

首先在onScrolled方法裏監聽滾動事件的最後一個Item的位置。
1.獲取View的layoutManager。

    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    taggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;

2.getSpanCount()返回所包含的 Item 總個數—爲什麼是個數組?

經過測試,lastPositions的長度和設置的列有關,設置3列,數組長度也爲三。但是測試時候,在findLastVisibleItemPositions 方法之前,值都爲0.經過findLastVisibleItemPositions 方法之後就能找到每一列的最大位置。—這裏是27,28,29。然後比較,輸出最大的位置。

if (lastPositions == null) {
                    lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
                }

3.通過位置判斷哪個纔是真正的最後一個

staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
lastVisibleItemPosition = findMax(lastPositions);

自定義findMax方法

private int findMax(int[] lastPositions) {
        int max = lastPositions[0];
        for (int value : lastPositions) {
            if (value > max) {
                max = value;
            }
        }
        return max;
    }

繼續onScrollStateChanged()方法
1.同樣先

                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();

2.通過佈局管理器獲取:總Item數和可見item數

int visibleItemCount = layoutManager.getChildCount();
                int totalItemCount = layoutManager.getItemCount();

3.判斷是否到底:lastVisibleItemPosition == totalItemCount - 1 因爲是位置是從0開始的,所以比總數小1.

if (visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == totalItemCount - 1) {
                    Log.d("----------","到底了");

                }

測試–

這裏寫圖片描述

數據加載:
在適配器MyAdapter中添加兩個方法:

//增加item
    public void addData(int position,String text,int Bitmap) {
        products.add(new Product(Bitmap, text));
        notifyItemInserted(position);
    }
   //刪除item
    public void removeData(int position) {
        products.remove(position);
        notifyItemInserted(position);
    }

在拉到底部的時候調用:

if (visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == totalItemCount - 1) {
                    Log.d("----------","到底了");
                    adapter.addData(totalItemCount,"添加",R.drawable.ic_9);

                }

測試:
這裏寫圖片描述
這裏寫圖片描述

下拉刷新:

下拉刷新要判斷是否在頂部:使用SwipeRefreshLayout控件不需要判斷是否在頂部,因爲SwipeRefreshLayout控件默認在頂部纔會刷新數據。
所以下面的方法可以作廢。

同樣的道理:findFirstVisibleItemPositions方法可以返回當前視圖每一列可見的第一個item的位置。判斷返回最小的是否等於0,等於就是到頂部了。

private int[] firstPositions;
private int firstVisibleItemPosition;
if (firstPositions == null) {
                    firstPositions = new int[staggeredGridLayoutManager.getSpanCount()];
                }
staggeredGridLayoutManager.findFirstVisibleItemPositions(firstPositions);
firstVisibleItemPosition = findMin(firstPositions);
private int findMin(int[] firstPositions) {
        int min = firstPositions[0];
        for (int value : firstPositions) {
            Log.d("xxxx",value+"");
            if (value < min) {
                min = value;
            }
        }
if(firstVisibleItemPosition==0){
                    Log.d("----------","到頂了");

                }

實現刷新功能:
SwipeRefreshLayout 是谷歌公司推出的用於下拉刷新的控件.
更改主佈局文件爲:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipe_refresh_widget"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
<android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>

關於SwipeRefreshLayout的使用:
SwipeRefreshLayout的使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章