概述
RecyclerView,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它的不同的LayoutManager,ItemDecoration,ItemAnimator实现各种效果
- 想要控制其显示方法,请通过布局管理器LayoutManager
- 想要控制Item间的间隔(可绘制),请通过ItemDecoration
- 想要控制Item增删的动画,请通过ItemAnimator
- 想要控制点击,长按事件,自己写去
基本使用
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
MyAdapter adapter;
List<String> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
recyclerView = (RecyclerView)findViewById(R.id.recycler);
adapter = new MyAdapter(data,this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//设置布局管理器
recyclerView.setAdapter(adapter);
//设置适配器
}
private void init(){
data = new ArrayList<>();
for (int i = 'A'; i < 'z'; i++)
{
data.add("" + (char) i);
}
}
}
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.myHolder> {
List<String> data ;
Context context;
public MyAdapter(List<String> data, Context context) {
this.data =data;
this.context = context;
}
@Override
public void onBindViewHolder(myHolder holder, int position) {
holder.textView.setText(data.get(position));
Log.d("onBind","aaaaaaaaaaa");
}
@Override
public int getItemCount() {
return data.size();
}
@Override
public myHolder onCreateViewHolder(ViewGroup parent, int viewType) {
myHolder holder = new myHolder(LayoutInflater.from(context).
inflate(R.layout.recycleritem,parent,false));
Log.d("onCreate","bbbbbbbbb");
return holder;
}
class myHolder extends RecyclerView.ViewHolder{
TextView textView;
public myHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text);
}
}
}
在第一次创建item的时候,是先调用onCreateViewHolder再调用onBindViewHolder的,在滑动中,只要不是再创建新的,一直调用的是onBingViewHolder。
关于其他:
mRecyclerView = findView(R.id.id_recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
看上面例子的效果:
这样好丑,感觉item之间应该有分割线,可是RecyclerView并没有支持divider这样的属性
我们可以通过添加mRecyclerView.addItemDecoration()
来添加分割线
该方法的参数为RecyclerView.ItemDecoration,该类为抽象类,目前官方没有提供默认的实现类
该类源码:
public static abstract class ItemDecoration {
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
当我们调用recyclerview.addItemDecoration()方法添加decoration的时候,Recycler在绘制的时候,会去绘制decorator,即调用该类的onDraw和onDrawOver方法:
- onDraw方法先于drawChildern
- onDrawOver在drawChildern之后,一般我们选择复写一个即可
- getItemOffset可以通过outRect.set()为每一个Item设置一定的偏移量,主要用户绘制Decorator。
下面是一个例子:
public class DividerItemDecaration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable divider;
private int orientation;
public DividerItemDecaration(Context context,int orientation) {
//取默认的listdivider的属性
final TypedArray a = context.obtainStyledAttributes(ATTRS);
//得到这个属性
divider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation){
if(orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST){
throw new IllegalArgumentException("invalid orientation");
}
this.orientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if(orientation == VERTICAL_LIST){
drawVertical(c,parent);
}else {
drawHorizontal(c,parent);
}
}
public void drawVertical(Canvas c,RecyclerView parent){
final int left = parent.getPaddingLeft();
final int riht = parent.getWidth()-parent.getPaddingRight();
final int childCount = parent.getChildCount();
for(int i = 0;i<childCount;i++){
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
child.getLayoutParams();
final int top = child.getBottom()+params.bottomMargin;
final int bottom = top +divider.getIntrinsicHeight();
divider.setBounds(left,top,riht,bottom);
divider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + divider.getIntrinsicHeight();
divider.setBounds(left, top, right, bottom);
divider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//是向上偏移还是向左偏移
if(orientation == VERTICAL_LIST){
outRect.set(0,0,0,divider.getIntrinsicHeight());
}else {
outRect.set(0,0,divider.getIntrinsicWidth(),0);
}
}
}
效果:
该实现类通过读取系统主题中的Android.R.attr.listDivider作为Item间的分割线,并且支持横向和纵向
获取到listdivider后,该属性的值是个Drawable,在getItemOffsets中,outRect设置绘制范围。onDraw中实现了绘制
然后在原来的代码中加:
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL_LIST));
该分割线是系统默认的,我们可以在theme.xml中找到该属性的使用的情况,并且可以进行改变
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:listDivider">@drawable/divider</item>
</style>
</resources>
在style中找一个item,叫android:listDivider
然后自己写一个drawable
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:centerColor="#ff00ff00"
android:endColor="#ff0000ff"
android:startColor="#ffff0000"
android:type="linear" />
<size android:height="4dp"/>
</shape>
LayoutManager
上面的例子是通过使用默认的LinearLayoutManager实现的
RecyclerView.LayoutManager,是一个抽象类,系统提供了三个实现类:
- LinearLyaoutManager 线性布局,支持横向,纵向
- GridLayoutManager 网格布局
- StaggeredGridLayoutManager 瀑布流布局
GridLayoutManager
mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));
重新写适合于这个的divider
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration
{
private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
private Drawable mDivider;
public DividerGridItemDecoration(Context context)
{
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, State state)
{
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private int getSpanCount(RecyclerView parent)
{
// 列数
int spanCount = -1;
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
spanCount = ((StaggeredGridLayoutManager) layoutManager)
.getSpanCount();
}
return spanCount;
}
public void drawHorizontal(Canvas c, RecyclerView parent)
{
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++)
{
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin
+ mDivider.getIntrinsicWidth();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawVertical(Canvas c, RecyclerView parent)
{
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++)
{
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin;
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
int childCount)
{
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
{
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
{
return true;
}
} else
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最后一列,则不需要绘制右边
return true;
}
}
return false;
}
private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
int childCount)
{
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最后一行,则不需要绘制底部
return true;
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
// StaggeredGridLayoutManager 且纵向滚动
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
childCount = childCount - childCount % spanCount;
// 如果是最后一行,则不需要绘制底部
if (pos >= childCount)
return true;
} else
// StaggeredGridLayoutManager 且横向滚动
{
// 如果是最后一行,则不需要绘制底部
if ((pos + 1) % spanCount == 0)
{
return true;
}
}
}
return false;
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent)
{
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边
{
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
}
}
}
注意,当开始我的左边的分割线怎么都画不出来,但是用系统默认的就可以,是因为在我的drawable文件下的画图,没有规定width,在规定width后,好了
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<gradient
android:startColor="#ffff0000"
android:centerColor="#ff00ff00"
android:endColor="#ff0000ff"
android:type="linear"/>
<size android:height="1dp"
android:width="1dp"
/>
</shape>
StaggeredGridLayoutManager
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL));
这种写法和上面是的效果是一致的,但是第二个参数传的是一个orientation,如果传入的是StaggeredGridLayoutManager.VERTICAL代表有多少列;那么传入的如果是StaggeredGridLayoutManager.HORIZONTAL就代表有多少行
如果改为:
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,
StaggeredGridLayoutManager.HORIZONTAL));
可以进行左右滑动
我们都是固定了高度
现在我们再onBindViewHolder只能怪为我们的Item设置个随机高度
@Override
public void onBindViewHolder(myHolder holder, int position) {
holder.textView.setText(data.get(position));
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.textView.getLayoutParams();
params.height = size[position%6];
}
ItemAnimator
ItemAnimator也是个抽象类,好在系统为我们提供了一种默认的实现类
借助默认的实现,当Item添加和移除的时候,添加动画效果很简单
// 设置item动画
recyclerView.setItemAnimator(new DefaultItemAnimator());
如果是GridLayoutManger:
注意,这里更新数据集不是用adapter.notifyDataSetChanged()而是
notifyItemInserted(position)与notifyItemRemoved(position)
为adapter添加两个方法:
public void addData(int position) {
mDatas.add(position, "Insert One");
notifyItemInserted(position);
}
public void removeData(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
在Main中点击MenuItem触发:
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.id_action_add:
mAdapter.addData(1);
break;
case R.id.id_action_delete:
mAdapter.removeData(1);
break;
}
return true;
}
Click and LingClick
recyclerview没有提供ClickListener和LongClickListener
不过我们可以自己添加
我选择通过adapter中提供回调
首先在Adaptet中定一个接口和方法
public interface onItemClickListener{
void onClick(int position);
void LongClivk(int position);
}
public void SetOnItmeClickListener(onItemClickListener listener){
this.listener = listener;
}
然后在onBindViewHolder中为每一个Item设置onClickListener和OnLongClickListener
@Override
public void onBindViewHolder(final myHolder holder, int position) {
holder.textView.setText(data.get(position));
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) holder.textView.getLayoutParams();
params.height = size[position%6];
if(listener != null){
holder.itemView .setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(holder.getLayoutPosition());
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
listener.LongClivk(holder.getLayoutPosition());
return false;
}
});
}
}
然后在MainActivity中设置Listener
adapter.SetOnItmeClickListener(new MyAdapter.onItemClickListener() {
@Override
public void onClick(int position) {
Toast.makeText(MainActivity.this,"onClick",Toast.LENGTH_SHORT).show();
}
@Override
public void LongClivk(int position) {
adapter.remove(position);
}
});
刚开始我对于画分割线充满了问号,下来自己解析解析,以画水平分割线为例:
public void drawVertical(Canvas c,RecyclerView parent){
final int left = parent.getPaddingLeft();
final int riht = parent.getWidth()-parent.getPaddingRight();
final int childCount = parent.getChildCount();
for(int i = 0;i<childCount;i++){
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
child.getLayoutParams();
final int top = child.getBottom()+params.bottomMargin;
final int bottom = top +divider.getIntrinsicHeight();
divider.setBounds(left,top,riht,bottom);
divider.draw(c);
Log.d("wnw",String.valueOf(top)+" "+String.valueOf(bottom)+" "+String.valueOf(left)+
" "+String.valueOf(riht) );
}
}
final View child = parent.getChildAt(i);得到的是LinearLayout整个布局。
left是图中的红点,我只是大概画了个位置,right是绿点,
确定了两个X轴上的点,现在是确定Y轴。
top的长度其实就是图中margin的长度,所以矩形第一个左上角的(left,top)就确定了
bottom就是top加上分割线的高度,所以矩形右下角的(right,bottom)就确定了。这样我们分割线画的位置和大小也确定了
然后再通过getItemOffsets()将整个LinearLayout布局向上平移分割线的高度,这样我们分割线的画的空间就有了,就可以画了
参考于:
http://blog.csdn.net/lmj623565791/article/details/45059587;
本文出自:【张鸿洋的博客】