内容概括:
- 给recyclerView添加分割线
- 给recyclerView添加HeaderView和FootView
- 给recyclerView添加刷新功能,这里使用的是Android系统的SwipeRefreshLayout
- recyclerView子项的点击事件【三种实现方式】
RecyclerView出来很长时间,但一直没有用在项目中,这个项目打算全部使用RecyclerView代替ListView,所以学习了一波,把看到的整理出来
RecyclerView比ListView更灵活,可以动态设置布局方向,也可以动态切换成ListView gridview以及瀑布流形式
- -
基础的使用
添加依赖
compile 'com.android.support:recyclerview-v7:24.0.0-beta1'
xml布局中像使用正常控件一样添加
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
初始化视图
mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
linear = new LinearLayoutManager(this);
//设置布局管理器,这里设置为线性布局
mRecyclerView.setLayoutManager(linear);
//设置布局方向
linear.setOrientation(LinearLayoutManager.VERTICAL);
//设置适配器
adpt = new MyAdapter(this, mList);
//设置适配器
mRecyclerView.setAdapter(adpt);
初始化数据
private void initDatas() {
mList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
mList.add("item" + i);
}
}
RecyclerView与ListView的适配器不同,需要继承自RecyclerView的Adapter
代码
package com.cd.recyclerviewdemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
/**
* Author Chendan
* Create Date 2016/9/9.
* Description:
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyVeiwHold> {
Context mContext;
List<String> mList;
//构造方法,初始化数据
public MyAdapter(Context context, List<String> mlist) {
mContext = context;
mList = mlist;
}
//初始化控件
@Override
public MyVeiwHold onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);
return new MyVeiwHold(layout);
}
//绑定控件
@Override
public void onBindViewHolder(MyVeiwHold holder, final int position) {
((MyVeiwHold)holder).mTextView.setText(mList.get(position ));
/**
* @return
*/
@Override
public int getItemCount() {
return mList.size();
}
//
class MyVeiwHold extends RecyclerView.ViewHolder {
TextView mTextView;
public MyVeiwHold(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.item_tv);
}
}
}
通过以上代码的编写,就能实现普通ListView展示的功能了
但是会发现子项之间没有分割线,于是需要特别为它写一个类去绘制分割线
- -
添加分割线
代码
package com.cd.recyclerviewdemo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import org.w3c.dom.Attr;
/**
* Author Chendan
* Create Date 2016/9/9.
* Description:
*/
public class Decretion extends RecyclerView.ItemDecoration {
/**
* 成员变量
*/
//上下文
Context mContext;
//线
private Drawable mdevider;
//方向
private int moritation;
//水平
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
//垂直
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
//获取系统属性
public static final int[] Attr = new int[]{android.R.attr.listDivider};
public Decretion(Context context, int oritation) {
mContext = context;
//通过上下文对象获取属性数组
TypedArray ta = context.obtainStyledAttributes(Attr);
//通过属性数组。获取对应属性获取线
this.mdevider = ta.getDrawable(0);
//回收属性
ta.recycle();
//初始化方向
setOritation(oritation);
}
private void setOritation(int oritation) {
//如果方向不为水平也不为垂直,则跑出非法异常
if (oritation != HORIZONTAL_LIST && oritation != VERTICAL_LIST) {
new IllegalArgumentException("invalid orientation");
} else {
moritation = oritation;
}
}
//
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (moritation == HORIZONTAL_LIST) {
drawVerticalLine(c, parent, state);
} else {
drawHorizontalLine(c, parent, state);
}
}
//绘制水平线
private void drawHorizontalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {
//左边
int left = parent.getPaddingLeft();
//右边
int right = parent.getWidth() - parent.getPaddingRight();
//子视图数量
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams param = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + param.bottomMargin;
final int bottom = top + mdevider.getIntrinsicHeight();
mdevider.setBounds(left, top, right, bottom);
mdevider.draw(c);
}
}
//绘制垂直线
private void drawVerticalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {
int top = parent.getPaddingTop();
int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
//获得child的布局信息
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mdevider.getIntrinsicWidth();
mdevider.setBounds(left, top, right, bottom);
mdevider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (moritation == HORIZONTAL_LIST) {
//画横线,就是往下偏移一个分割线的高度
outRect.set(0, 0, 0, mdevider.getIntrinsicHeight());
} else {
//画竖线,就是往右偏移一个分割线的宽度
outRect.set(0, 0, mdevider.getIntrinsicWidth(), 0);
}
}
}
获取到系统属性然后通过方向判断绘制横向的还是垂直的
在视图初始化中添加这样一行代码
//添加下划线
mRecyclerView.addItemDecoration(new Decretion(this, Decretion.VERTICAL_LIST));
Decretion类中使用到的attr和drawable代码如下
在AppTheme中添加
<item name="android:listDivider">@drawable/divider</item>
在drawable文件夹下定义divider
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#7b7a7a"/>
<size android:height="1dp"/>
</shape>
添加完以上代码运行就会有下划线的效果了,这样比较灵活可以自定义线的颜色和粗细
- -
RecyclerView添加头部视图和足部视图
分别定义好头部和足部布局
header
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="我是Header"
android:textSize="30sp"
android:textColor="#fde70b0b"
android:background="#f9777979"
android:gravity="center"/>
</LinearLayout>
footer与header同样,只是显示的文本不同
然后在activity中添加两个方法把布局添加进来
//设置HeardView
private void setHeaderView(RecyclerView view) {
View header = LayoutInflater.from(this).inflate(R.layout.header, view, false);
adpt.setHeaderView(header);
}
//设置FooterView
private void setFooterView(RecyclerView view) {
View footer = LayoutInflater.from(this).inflate(R.layout.footer, view, false);
adpt.setFooterView(footer);
}
这个adpt就是我的适配器,适配器中定义了setFooterView和setHeaderView方法
代码:
//获取HeaderView
public View getHeaderView()
{
return mHeaderView;
}
//设置HeaderView
public void setHeaderView(View headerView) {
mHeaderView = headerView;
//更新第一条
notifyItemInserted(0);
}
//获取FootView
public View getFooterView() {
return mFooterView;
}
//设置FooterView
public void setFooterView(View footerView) {
mFooterView = footerView;
//更新最后一条
notifyItemInserted(getItemCount() - 1);
}
其他方法代码也要相应改变,适配器所有代码
package com.cd.recyclerviewdemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
/**
* Author Chendan
* Create Date 2016/9/9.
* Description:
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyVeiwHold> {
Context mContext;
List<String> mList;
public static final int TYPE_HEADER = 0; //带有Header的类型
public static final int TYPE_FOOTER = 1; //是带有Footer的类型
public static final int TYPE_NORMAL = 2; //不带有header和footer的类型,正常类型
private View mHeaderView;//头部视图
private View mFooterView;//足部视图
public onRecyclerViewItemOnClick mItemOnClick;
/**
* 定义recyclerView的点击事件接口
*/
public interface onRecyclerViewItemOnClick {
//点击事件
void onItemClick(View view, int position);
//长按事件
void onItemLongClick(View view, int position);
}
/**
* 设置set方法
*/
public void setItemClick(onRecyclerViewItemOnClick itemOnClick) {
mItemOnClick = itemOnClick;
}
//构造方法,初始化数据
public MyAdapter(Context context, List<String> mlist) {
mContext = context;
mList = mlist;
}
//获取HeaderView
public View getHeaderView()
{
return mHeaderView;
}
//设置HeaderView
public void setHeaderView(View headerView) {
mHeaderView = headerView;
//更新第一条
notifyItemInserted(0);
}
//获取FootView
public View getFooterView() {
return mFooterView;
}
//设置FooterView
public void setFooterView(View footerView) {
mFooterView = footerView;
//更新最后一条
notifyItemInserted(getItemCount() - 1);
}
//初始化控件
@Override
public MyVeiwHold onCreateViewHolder(ViewGroup parent, int viewType) {
//如果有头部视图
if (mHeaderView != null && viewType == TYPE_HEADER) {
//返回头部视图的Hold
return new MyVeiwHold(mHeaderView);
}
//如果有足部视图
if (mFooterView != null && viewType == TYPE_FOOTER) {
//返回足部视图的Hold
return new MyVeiwHold(mFooterView);
}
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);
return new MyVeiwHold(layout);
}
//绑定控件
@Override
public void onBindViewHolder(MyVeiwHold holder, final int position) {
/**
* 对子项视图的类型进行判断,1,正常无头部和足部视图的情况下2,有头部视图的情况下3,有足部视图的情况下以及头足部都有的情况下
*/
if (getItemViewType(position) == TYPE_NORMAL) {
if (holder instanceof MyVeiwHold) {
//这里加载数据的时候要注意,是从position-1开始,因为position==0已经被header占用了
((MyVeiwHold) holder).mTextView.setText(mList.get(position - 1));
if (mItemOnClick!=null){
holder.mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mItemOnClick.onItemClick(v,position);
}
});
holder.mTextView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mItemOnClick.onItemLongClick(v,position);
return false;
}
});
}
return;
}
return;
} else if (getItemViewType(position) == TYPE_HEADER) {
return;
} else {
return;
}
}
/**
* @return
*/
@Override
public int getItemCount() {
if (mHeaderView == null && mFooterView == null) {
return mList.size();
} else if (mHeaderView == null && mFooterView != null) {
return mList.size() + 1;
} else if (mHeaderView != null && mFooterView == null) {
return mList.size() + 1;
} else {
return mList.size() + 2;
}
}
//
class MyVeiwHold extends RecyclerView.ViewHolder {
TextView mTextView;
public MyVeiwHold(View itemView) {
super(itemView);
//如果是headerview或者是footerview,直接返回
if (itemView == mHeaderView) {
return;
}
if (itemView == mFooterView) {
return;
}
mTextView = (TextView) itemView.findViewById(R.id.item_tv);
}
}
/**
* 重写这个方法,很重要,是加入Header和Footer的关键,我们通过判断item的类型,从而绑定不同的view *
*/
@Override
public int getItemViewType(int position) {
if (mHeaderView == null && mFooterView == null) {
return TYPE_NORMAL;
}
if (position == 0) {
//第一个item应该加载Header
return TYPE_HEADER;
}
if (position == getItemCount() - 1) {
//最后一个,应该加载Footer
return TYPE_FOOTER;
}
return TYPE_NORMAL;
}
}
在activity中调用
//为RecyclerView添加HeaderView和FooterView
setHeaderView(mRecyclerView);
setFooterView(mRecyclerView);
这样就实现了添加头部视图和足部视图
-
RecyclerView刷新
在布局的最外层包裹一个控件SwipeRefreshLayout
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swip"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.cd.recyclerviewdemo.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v4.widget.SwipeRefreshLayout>
在初始化中添加
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swip);
//设置卷内的颜色
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light, android.R.color.holo_orange_light, android.R.color.holo_red_light);
刷新
//刷新
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//往集合中添加数据
mList.add(0, "嘿,我是下拉刷新的数据");
//更新适配器中的数据
adpt.notifyDataSetChanged();
//关闭刷新
mSwipeRefreshLayout.setRefreshing(false);
}
});
加载
//加载
mRecyclerView.addOnScrollListener(new EndLessOnScrollListener(linear) {
@Override
public void onLoadMore(int currentPage) {
//加载更多,这里是测试数据,所以直接加载更多,
// 如果是真是开发中,需要传入currentPage这个参数来实现分页效果,
// 否则一次性加载太多影响性能
loadMoreData();
}
});
private void loadMoreData() {
for (int i = 0; i < 10; i++) {
mList.add("哈哈,拉出第" + i + "条数据");
adpt.notifyDataSetChanged();
}
}
这里的EndLessOnScrollListener是定义的一个接口,继承自RecyclerView.OnScrollListener
package com.cd.recyclerviewdemo;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
/**
* Author Chendan
* Create Date 2016/9/9.
* Description:
*/
public abstract class EndLessOnScrollListener extends RecyclerView.OnScrollListener {
//声明一个LinearLayoutManager
private LinearLayoutManager mLinearLayoutManager;
// 当前页,从0开始
private int currentPage = 0;
//已经加载出来的Item的数量
private int totalItemCount;
//主要用来存储上一个totalItemCount
private int previousTotal = 0;
//在屏幕上可见的item数量
private int visibleItemCount;
//在屏幕可见的Item中的第一个
private int firstVisibleItem;
//是否正在上拉数据
private boolean loading = true;
public EndLessOnScrollListener(LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//显示在屏幕中的数量
visibleItemCount = recyclerView.getChildCount();
//总数量
totalItemCount = mLinearLayoutManager.getItemCount();
//第一项可见数量
firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
//如果正在上拉数据,并且数据当前总条数大于上一次总条数
if (loading) {
if (totalItemCount > previousTotal) {
//说明数据已经加载结束
loading = false;
//保存这次的总条数
previousTotal = totalItemCount;
}
}
//这里需要好好理解
if (!loading && totalItemCount - visibleItemCount <= firstVisibleItem) {
currentPage++;
onLoadMore(currentPage);
loading = true;
}
}
/**
* 提供一个抽闲方法,在Activity中监听到这个EndLessOnScrollListener
* 并且实现这个方法
*/
public abstract void onLoadMore(int currentPage);
}
RecyclerView的点击事件
有三种实现方式,但是我只选择一种我最熟悉也最适合我们项目的记下来,要具体了解可以看看这篇文章
http://www.tuicool.com/articles/au6ZvmB
1,通过 RecyclerView 已有的方法 addOnItemTouchListener() 实现
2,在绑定 ItemView 时添加点击监听
3,当 ItemView attach RecyclerView 时实现
我使用的是第二种
在适配器中定义设置监听的接口
/**
* 定义recyclerView的点击事件接口
*/
public interface onRecyclerViewItemOnClick {
//点击事件
void onItemClick(View view, int position);
//长按事件
void onItemLongClick(View view, int position);
}
给接口设置set方法,方便调用
/**
* 设置set方法
*/
public onRecyclerViewItemOnClick mItemOnClick;
public void setItemClick(onRecyclerViewItemOnClick itemOnClick) {
mItemOnClick = itemOnClick;
}
在绑定视图的时候给item每一个控件设置点击事件,这是一种item点击事件的假象,但是可以实现功能
//绑定控件
@Override
public void onBindViewHolder(MyVeiwHold holder, final int position) {
/**
* 对子项视图的类型进行判断,1,正常无头部和足部视图的情况下2,有头部视图的情况下3,有足部视图的情况下以及头足部都有的情况下
*/
if (getItemViewType(position) == TYPE_NORMAL) {
if (holder instanceof MyVeiwHold) {
//这里加载数据的时候要注意,是从position-1开始,因为position==0已经被header占用了
((MyVeiwHold) holder).mTextView.setText(mList.get(position - 1));
if (mItemOnClick!=null){
holder.mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mItemOnClick.onItemClick(v,position);
}
});
holder.mTextView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mItemOnClick.onItemLongClick(v,position);
return false;
}
});
}
return;
}
return;
} else if (getItemViewType(position) == TYPE_HEADER) {
return;
} else {
return;
}
}
在activity中调用
adpt.setItemClick(new MyAdapter.onRecyclerViewItemOnClick() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "点击view.getId()+position:" + (view.getId() + position), Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this, "长按view.getId()+position:" + (view.getId() + position), Toast.LENGTH_SHORT).show();
}
});
参考文章
http://www.jianshu.com/p/3bf125b4917d
http://www.jianshu.com/p/4eff036360da
http://www.jianshu.com/p/991062d964cf