前言:
前面介绍了瀑布流的基本实现,实际上瀑布流还有一些事件需要监听。比如点击事件,下拉和上拉事件。
这里接着上次的 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的使用