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的使用

发布了85 篇原创文章 · 获赞 40 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/lw_zhaoritian/article/details/52604030