参考:
Android RecyclerView 使用完全解析 体验艺术般的控件
Hongyang对RecyclerView的adapter进行了各种封装:
博客: Android 优雅的为RecyclerView添加HeaderView和FooterView
github: https://github.com/hongyangAndroid/baseAdapter
1 RecycleView实现ListView的功能
需要添加依赖:
compile 'com.android.support:recyclerview-v7:24.2.0'
相关方法:
RecyclerView的方法:
方法 | 含义 |
---|---|
setLayoutManager(…) | 设置布局管理者 |
setAdapter | 设置适配器 |
Adapter中的方法:
方法 | 含义 |
---|---|
onCreateViewHolder(…) | 创建ViewHolder |
onBindViewHolder(…) | 给ViewHolder里面的view设置属性 |
getItemCount | item的数量 |
步骤
- 找到RecycleView
- 给RecycleView设置 LayoutManager
- 给RecycleView设置 Adapter
下面通过一个demo显示RecycleView的基本使用。
见图:
MainActivity
package com.cqc.recyclerview01;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private Context context;
private List<String> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
initData();
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(new MyAdapter());
}
private void initData() {
list.clear();
for (int i = 0; i < 100; i++) {
list.add("item" + i);
}
}
public class MyAdapter extends RecyclerView.Adapter {
private MyHolder myHolder;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TextView tv = new TextView(context);
tv.setHeight(50);
myHolder = new MyHolder(tv);
return myHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// MyHolder myHolder = (MyHolder) holder;
myHolder = (MyHolder) holder;
myHolder.textView.setText(list.get(position));
}
@Override
public int getItemCount() {
return 100;
}
}
public class MyHolder extends RecyclerView.ViewHolder {
public TextView textView;
public MyHolder(View itemView) {
super(itemView);
textView = (TextView) itemView;
}
}
}
实现item的分割线
com.android.support:recyclerview-v7:25.0.1
官方有了默认的DividerItemDecoration
,25.0.1之前没有这个类。
我们发现recyclerview没有分割线,需要调用mRecyclerView.addItemDecoration()添加分割线,
但是ItemDecoration是抽象类,需要我们自己实现。
该类DividerItemDecoration源于:
http://blog.csdn.net/lmj623565791/article/details/45059587
怎么添加默认的分割线?
导入类DividerItemDecoration到项目中,调用方法:
recyclerView.addItemDecoration(new DividerItemDecoration(context,LinearLayoutManager.VERTICAL));
//或者
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
注意:注意context为null的情况,尽量用getActivity()
或者getContext()
,最近就报了错,找了半天才发现是这里错了。尽量不要让context
变成成员变量,
private Context context = getActivity();
怎么添加自定义分割线 ?
创建自定义的分割线:item_divider.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="4dp"/><!--不用设置宽度-->
<gradient
android:centerColor="#ff0000ff"
android:endColor="#ffff0000"
android:startColor="#ff00ff00"
android:type="linear"/>
</shape>
<!--recyclerView分割线的设置-->
使用自定义的分割线:styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
...
<!--自定义item的分割线-->
<item name="android:listDivider">@drawable/item_divider</item>
</style>
</resources>
效果图:
package com.cqc.recyclerview01;
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.util.Log;
import android.view.View;
/**
* RecyclerView添加分割线(当使用LayoutManager为LinearLayoutManager时)
* 该类源于:http://blog.csdn.net/lmj623565791/article/details/45059587
*/
public class DividerItemDecoration 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 mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
Log.v("recyclerview - itemdecoration", "onDraw()");
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.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 + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
然后在mainActivity中调用:
recyclerView.addItemDecoration(new DividerItemDecoration(context,LinearLayoutManager.VERTICAL));//添加分割线
添加点击事件
方法一:在ViewHolder的构造方法里面写点击监听事件
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView tv;
public MyViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int adapterPosition = getAdapterPosition();
}
});
}
}
方法二:在onBindViewHolder(...)
里面写itemView的点击监听
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
MyViewHolder holder = (MyViewHolder) viewHolder;
holder.tv.setText("" + position);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "position=" + position);
}
});
}
方法三:利用回调,在Activity中写点击监听
先在adapter中设置回调
//设置点击回调
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
然后在onBindViewHolder(…)中调用点击事件监听
//如果设置了回调,则调用点击事件
if (mOnItemClickListener != null) {
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(v,position);
}
});
holder.textView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mOnItemClickListener.onItemLongClick(v, position);
return true;//事件被处理
}
});
}
如果只有短点击,同理也需要在onBindViewHolder()中设置.
//如果设置了回调,则调用点击事件
if (mOnItemClickListener != null) {
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(v,position);
}
});
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
Mactivity中调用
adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
}
});
最后在activity中使用点击事件
//点击事件(用adapter调用点击方法)
adapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClickListerner(View view, int position) {
Toast.makeText(context,"短点击",Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClickListener(View view, int position) {
Toast.makeText(context,"长点击",Toast.LENGTH_SHORT).show();
}
});
效果图:
添加item和删除item
方法:
添加item:adapter.notifyItemInserted(position)
删除item:adapter.notifyItemRemoved(position);
btn_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
list.add(0,"btn_add");
// adapter.notifyDataSetChanged();//也可以,但是没有动画效果
adapter.notifyItemInserted(0);
recyclerView.scrollToPosition(0);//滑动到第一个item,不加不会滑动到顶部。
}
});
btn_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
list.remove(0);
adapter.notifyDataSetChanged();//也可以,但是没有动画效果
// adapter.notifyItemRemoved(0);
}
});
item添加/删除的动画
recyclerView.setItemAnimator(new DefaultItemAnimator());//item添加移出动画
notifyItemRemoved(int position)引起的角标越界异常
在调用该方法后,
list.remove(position);
adapter.notifyItemRemoved(position);
容易引起角标越界异常,因为调用该方法后,不会重新走onBindViewHolder(…),所以position的值没有变化。
解决方法:
list.remove(position);
adapter.notifyItemRemoved(position);
if (position != list.size()) {//如果删除的是最后一个,则不刷新数据。
adapter.notifyItemRangeChanged(position, list.size() - deleteIndex);//刷新position后的数据
}
添加HeadView和FootView
1 这里只讨论添加一个HeadView或FootView
2 下面的是没有给HeadView或FootView创建单独的ViewHolder,用的是同一个ViewHolder,并在其构造方法中判断;
3 当然也可以给HeadView或FootView创建单独的ViewHolder, 并在onCreateViewHolder(…)中返回对应的ViewHolder。
首先定义HeadView和FootView的类型
//默认只加一个header 和footer,2种类型不能为0,因为getItemViewType(int position)默认返回0
private int HeaderType = 1;
private int FooterType = 2;
其次:定义HeadView和FootView变量,并设置和获取他们的数量(0/1)
private View headerView;
private View footerView;
public void setHeaderView(View headerView) {
this.headerView = headerView;
}
public void setFooterView(View footerView) {
this.footerView = footerView;
}
public int getHeadViewCount() {
return headerView == null ? 0 : 1;
}
public int getFootViewCount() {
return footerView == null ? 0 : 1;
}
第三步:重写方法getItemViewType(int position)
此处注意:定义HeaderType 和FooterType的值不能是0,因为该方法默认返回0,如果头或尾定义了0,而你又没有给其它item(HeadView/FootView之外的item)定义type,仍然使用
return super.getItemViewType(position);
,那么0就代表2种类型了,这是不对的。
@Override
public int getItemViewType(int position) {
if (position == 0) {
return HeaderType;
}
if (position == 100 + getHeadViewCount()) {
return FooterType;
}
return super.getItemViewType(position);
}
上面的是确定有header,其实不应该这样判断
@Override
public int getItemViewType(int position) {
if (position < getHeadViewCount()) {
return HEAD;
}
if (position >= list.size() + getHeadViewCount()) {
return FOOT;
}
return NORMAL;
}
第四步:修改Adapter中的3个方法
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == HeaderType) {
return new MyViewHolder(headerView);
}
if (viewType == FooterType) {
return new MyViewHolder(footerView);
}
View itemView = View.inflate(context, android.R.layout.two_line_list_item, null);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (getItemViewType(position) == HeaderType) {
return;
}
if (getItemViewType(position) == FooterType) {
return;
}
MyViewHolder holder = (MyViewHolder) viewHolder;
holder.tv1.setText("Title");
holder.tv2.setText("message");
}
@Override
public int getItemCount() {
return 100 + getHeadViewCount() + getFootViewCount();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tv_item;
public MyViewHolder(View itemView) {
super(itemView);
if (itemView == headerView) {
return;
}
if (itemView == footerView) {
return;
}
tv_item = (TextView) itemView.findViewById(R.id.tv_item);
}
}
第五步:我们添加HeadView和FootView,setHeaderView(view)放在setAdapter(adapter)前后无所谓。
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new MyAdapter();
recyclerView.setAdapter(adapter);
View headView = View.inflate(context, R.layout.layout_header, null);
View footView = View.inflate(context, R.layout.layout_footer, null);
adapter.setHeaderView(headView);
adapter.setFooterView(footView);
这是我们发现:HeadView或FootView的宽度没有充满屏幕,这是什么原因呢? 详见:Android基础:三种inflate的区别
这是因为我们在填充HeadView或FootView使用的是
View headView = View.inflate(context, R.layout.layout_header, null);
View footView = View.inflate(context, R.layout.layout_footer, null);
改成下面这种,注意:第二个参数parent是recyclerView
View headView = LayoutInflater.from(context).inflate(R.layout.layout_header,recyclerView, false);
View footView =LayoutInflater.from(context).inflate(R.layout.layout_footer, recyclerView,false);
参考:
Android 优雅的为RecyclerView添加HeaderView和FooterView
Android 简捷地为RecyclerView添加HeadView和FootView
Demo:https://git.oschina.net/RecyclerView/RecyclerViewDemo2
Demo源码:https://git.oschina.net/RecyclerView/RecyclerViewHeaderFooter01
setHasFixedSize(true)
recyclerView.setHasFixedSize(true);
它的作用是当我们队数据进行增删的时候,不会重新计算item宽高。
在Developer Guild–> Meterial Design–>创建列表和卡片中是这样介绍的
注意事项
1 onCreateViewHolder(…)不可以复用holder,必须new。
if (myHolder == null){
myHolder = new MyHolder(tv);
}
2 onCreateViewHolder(…)中的holder和onBindViewHolder(RecyclerView.ViewHolder holder, int position) {。。}中的holder就是我们创建的MyHolder,可以直接进行格式转换,也可以声明成员变量。
public class MyAdapter extends RecyclerView.Adapter {
private MyHolder myHolder;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
TextView tv = new TextView(context);
tv.setHeight(50);
myHolder = new MyHolder(tv);
return myHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// MyHolder myHolder = (MyHolder) holder;
myHolder = (MyHolder) holder;
myHolder.textView.setText(list.get(position));
}
@Override
public int getItemCount() {
return 100;
}
}
3 最好先创建类ViewHolder,再创建Adapter,这样可以使用直接指定泛型,就不需要再格式转换了。
public class MyAdapter extends RecyclerView.Adapter<MyHolder>
4 item的高度设为match_parent,父布局设置100dp,item的高无效
父布局设置具体的高度,子view设match_parent,这是无效的。
方法一:必须给子view(textview设置具体的数值),父布局math——parent
方法二:item的view:使用 LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!(View.inflate(..))
5 item的子view(textview)宽度match_parent,父布局的宽度也是match_parent,但是仍然无法充满屏幕。
方法:item的view:使用LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!(View.inflate(..))
6 总结:结合4+5,使用View.inflate(context, R.layout.item_main, null);创建item的view,会导致item的view高度无效,子view(textview)的高宽无效。
方法:item的view:使用LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false); 而不是其他任何方法!
错误:(View.inflate(context, R.layout.item_main, null)
错误:LayoutInflater.from(context).inflate(R.layout.item_main,null))
7 item宽度没有充满屏幕
RecyclerView子View宽度不能全屏的问题,在Adapter的onCreateViewHolder创建子view的时候要把parent传进去;
//这种inflate方式有事会导致item宽度不充满屏幕
//View itemView = View.inflate(parent.getContext(), R.layout.item_gate_frag, null);
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_gate_frag,parent,false);
复杂的RecyclerView
和listview一样,复杂的RecyclerView即使item有多种类型。这是需要重写adapter的2方法
,定义View类型:
getItemViewType(int position) {...} getItemCount() {...}private static int ViewTypeHead = 0; private static int ViewTypeBody = 1;
,在创建ViewHolder是也需要判断ViewType
,有几种类型就创建几个ViewHolder
。
以下是主要代码:
private static int ViewTypeHead = 0;
private static int ViewTypeBody = 1;
@Override
public int getItemViewType(int position) {
if (position % 3 == 0) {
return ViewTypeHead;
} else {
return ViewTypeBody;
}
}
@Override
public int getItemCount() {
return 100;
}
public class ItemHeadViewHeader extends RecyclerView.ViewHolder {
...
public ItemHeadViewHeader(View itemView) {
super(itemView);
...
}
}
public class ItemBodyViewHeader extends RecyclerView.ViewHolder {
...
public ItemBodyViewHeader(View itemView) {
super(itemView);
...
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ViewTypeHead) {
View itemView = View.inflate(parent.getContext(), R.layout.item_header, null);
return new ItemHeadViewHeader(itemView);
} else {
View itemView = View.inflate(parent.getContext(), R.layout.item_body, null);
return new ItemBodyViewHeader(itemView);
}
}
Demo: https://git.oschina.net/Android5x/ShopCartDemo01
RecycleView(二):实现GridView的功能
同ListView基本一样,只是LayoutManager变成了GridLayoutManager
效果图
代码
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this, 3));
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerGridItemDecoration(MainActivity.this));
adapter = new RecyclerGridAdapter(list);
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new RecyclerGridAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
toast.setText(list.get(position));
toast.show();
}
});
分割线
RecyclerView实现GridView没有提供默认的分割线(25.0.1开始有了),V7包中的DividerItemDecoration
只是给ListView
使用,而GridView
应该使用自定义的DividerGridItemDecoration
DividerGridItemDecoration的下载地址
效果图:
自定义分割线
ListView的divider 只需要设置高度
GridView的divider 需要设置高度+宽度
<?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" />
<!--listView的divider 只需要设置高度-->
<!--gridView 的divider 需要设置高度+宽度-->
<size android:height="4dp" android:width="4dp"/>
</shape>
item的margin无效的问题
margin无效 效果图: margin有效 效果图
要想使itemView 跟布局的layout_margin生效,必须指定root即parent,也就是不能使用第一种方式创建itemView
//第一种
View itemView = View.inflate(parent.getContext(), R.layout.item_grid, null);
//第二种
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid, parent, false);
RecylerView实现瀑布流
效果图
同ListView和GridView的不同之处
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
recyclerView.addItemDecoration(new DividerGridItemDecoration(StaggeredGridActivity.this));
怎么是这种效果的?
通过设置每个item的随机高度,达到显示瀑布流的效果。
heights.add(random.nextInt(100) + 100);
ViewGroup.LayoutParams params = holder.tv.getLayoutParams();
params.height = heights.get(position);
holder.tv.setLayoutParams(params);
源码: https://git.oschina.net/Android5x/RecyclerViewDemo02
ScrollView嵌套RecylerView
下面的内容来源自android ScrollView嵌套RecyclerView ,之验证了重写LinearLayoutManager
,可以正常使用,而且乜有重写ScrollView
。
ScrollView
嵌套ListView
,我们一般重写onMeasure()
方法,但是RecyclerView
不行,而是要重写LayoutManager
,比如LinearLayoutManager
、GridLayoutManager
和StaggeredGridLayoutManager
。但是值得注意的是,如果recyclerView
很长那么强烈不建议去做嵌套,因为这样recyclerView
会在展示的时候立刻展示所有内容,效率极低。
重写LinearLayoutManager
public class FullyLinearLayoutManager extends LinearLayoutManager {
private static final String TAG = FullyLinearLayoutManager.class.getSimpleName();
public FullyLinearLayoutManager(Context context) {
super(context);
}
public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public FullyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private int[] mMeasuredDimension = new int[2];
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode
+ " \nheightMode " + heightSpec
+ " \nwidthSize " + widthSize
+ " \nheightSize " + heightSize
+ " \ngetItemCount() " + getItemCount());
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
height = height + mMeasuredDimension[1];
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
try {
View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
重写GridLayoutManager
public class FullyGridLayoutManager extends GridLayoutManager {
public FullyGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public FullyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
int count = getItemCount();
int span = getSpanCount();
for (int i = 0; i < count; i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
if (i % span == 0) {
width = width + mMeasuredDimension[0];
}
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
if (i % span == 0) {
height = height + mMeasuredDimension[1];
}
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
if (position < getItemCount()) {
try {
View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
重写StaggeredGridLayoutManager
public class ExStaggeredGridLayoutManager extends StaggeredGridLayoutManager {
public ExStaggeredGridLayoutManager(int spanCount, int orientation) {
super(spanCount, orientation);
}
// 尺寸的数组,[0]是宽,[1]是高
private int[] measuredDimension = new int[2];
// 用来比较同行/列那个item罪宽/高
private int[] dimension;
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
// 宽的mode+size
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
// 高的mode + size
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
// 自身宽高的初始值
int width = 0;
int height = 0;
// item的数目
int count = getItemCount();
// item的列数
int span = getSpanCount();
// 根据行数或列数来创建数组
dimension = new int[span];
for (int i = 0; i < count; i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), measuredDimension);
// 如果是竖直的列表,计算item的高,否则计算宽度
//Log.d("LISTENER", "position " + i + " height = " + measuredDimension[1]);
if (getOrientation() == VERTICAL) {
dimension[findMinIndex(dimension)] += measuredDimension[1];
} else {
dimension[findMinIndex(dimension)] += measuredDimension[0];
}
}
if (getOrientation() == VERTICAL) {
height = findMax(dimension);
} else {
width = findMax(dimension);
}
switch (widthMode) {
// 当控件宽是match_parent时,宽度就是父控件的宽度
case View.MeasureSpec.EXACTLY:
width = widthSize;
break;
case View.MeasureSpec.AT_MOST:
break;
case View.MeasureSpec.UNSPECIFIED:
break;
}
switch (heightMode) {
// 当控件高是match_parent时,高度就是父控件的高度
case View.MeasureSpec.EXACTLY:
height = heightSize;
break;
case View.MeasureSpec.AT_MOST:
break;
case View.MeasureSpec.UNSPECIFIED:
break;
}
// 设置测量尺寸
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
// 挨个遍历所有item
if (position < getItemCount()) {
try {
View view = recycler.getViewForPosition(position);//fix 动态添加时报IndexOutOfBoundsException
if (view != null) {
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), lp.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), lp.height);
// 子view进行测量,然后可以通过getMeasuredWidth()获得测量的宽,高类似
view.measure(childWidthSpec, childHeightSpec);
// 将item的宽高放入数组中
measuredDimension[0] = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
recycler.recycleView(view);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private int findMax(int[] array) {
int max = array[0];
for (int value : array) {
if (value > max) {
max = value;
}
}
return max;
}
/**
* 得到最数组中最小元素的下标
*
* @param array
* @return
*/
private int findMinIndex(int[] array) {
int index = 0;
int min = array[0];
for (int i = 0; i < array.length; i++) {
if (array[i] < min) {
min = array[i];
index = i;
}
}
return index;
}
}
此种方法在4.x系统上好用,能显示滑动也流畅,但是在5.x上虽然显示正常,但是滑动的时候好像被粘住了,没有惯性效果。。。
最后解决方法是重写最外层的Scrollview
public class MyScrollview extends ScrollView {
private int downX;
private int downY;
private int mTouchSlop;
public MyScrollview(Context context) {
super(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public MyScrollview(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
int action = e.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = (int) e.getRawX();
downY = (int) e.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveY = (int) e.getRawY();
if (Math.abs(moveY - downY) > mTouchSlop) {
return true;
}
}
return super.onInterceptTouchEvent(e);
}
}
RecyclerView的itemView包含CheckBox的问题
找到了2篇博客,代码如下
Recycleview checkbox 复用出现混乱解决方法
RecyclerView中使用checkbox遇到的问题
感觉还是用标志位方便。
bug效果图+修复后效果图
解决代码
public class MyRecyclerViewAdapter extends RecyclerView.Adapter {
private MyViewHolder holder;
private List<Integer> checkList = new ArrayList<>();
private List<String> list;
public MyRecyclerViewAdapter(List<String> list) {
this.list = list;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = View.inflate(parent.getContext(), R.layout.item, null);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
holder = (MyViewHolder) viewHolder;
holder.tv.setText(list.get(position));
holder.checkBox.setTag(new Integer(position));//把组件的状态更新为一个合法的状态值
if (checkList != null) {
((MyViewHolder) holder).checkBox.setChecked((checkList.contains(new Integer(position)) ? true : false));
} else {
((MyViewHolder) holder).checkBox.setChecked(false);
}
holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
int index = (int) compoundButton.getTag();//事件处理前要判断状态
if (index == -2) {
return;
}
if (isChecked) {
//这句要有 ,否则不复用了,但是你再滑动回去的时候,都成了为选中。这是因为你的item滑出可视范围时,就会触发oncheckchange事件,所以用第一步绑定的tag进行二次判断。防止选中的丢失
if (!checkList.contains(holder.checkBox.getTag())) {
checkList.add(new Integer(position));
}
} else {
if (checkList.contains(holder.checkBox.getTag())) {//这句同上,二次判断
checkList.remove(new Integer(position));
}
}
}
});
}
@Override
public int getItemCount() {
return list.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
CheckBox checkBox;
TextView tv;
public MyViewHolder(View itemView) {
super(itemView);
checkBox = (CheckBox) itemView.findViewById(R.id.cb);
checkBox.setTag(new Integer(-2));
tv = (TextView) itemView.findViewById(R.id.tv);
}
}
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
CheckBox cb = ((MyViewHolder) holder).checkBox;
cb.setTag(new Integer(-2));
super.onViewRecycled(holder);
}
public List<Integer> getCheckList() {
return checkList;
}
}
item margin
我们在使用RecyclerView
中的item设置上下间距,如果是第一个,那么设置marginTop即可,但如果是最后一个,就必须设置marginBottom,而如果 都设置那么中间的item的间距就是marginTop+marginBottom
纸之和,,怎么实现每一个item的间距都相等,而且第一个和最后一个跟屏幕的间距也是一样的呢。
给item只设置
marginTop
属性,最后一个item用java代码设置margin
属性.
if (position == list.size()-1) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,DpPxUtil.dip2px(context,100));
int marginValue = DpPxUtil.dip2px(context, 10);
params.setMargins(marginValue,marginValue,marginValue,marginValue);
cardView.setLayoutParams(params);
}
RecyclerView嵌套RecyclerView
不需要特别注意,该怎么用就怎么用,不需要计算高度。
效果图
创建数据
注意:采用list.clear()
有问题。
public class ClassBean {
private String className;
private List<StudentBean> students;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public List<StudentBean> getStudents() {
return students;
}
public void setStudents(List<StudentBean> students) {
this.students = students;
}
}
public class StudentBean {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
private List<StudentBean> studentList ;
private List<ClassBean> classList = new ArrayList<>();
private void initData() {
for (int i = 0; i < 30; i++) {
ClassBean classBean = new ClassBean();
classBean.setClassName("班级" + i);
studentList= new ArrayList<>();
// studentList.clear();//采用clear()无法全部清除,
int maxValue = new Random().nextInt(5);
for (int j = 0; j < maxValue; j++) {
StudentBean student = new StudentBean();
student.setName("小明"+(j+1));
student.setAge("15");
studentList.add(student);
}
classBean.setStudents(studentList);
classList.add(classBean);
}
}
item_1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv1"
android:background="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
MyAdapter1
public class MyAdapter extends RecyclerView.Adapter {
private List<ClassBean> classList;
public MyAdapter(List<ClassBean> classList) {
this.classList = classList;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
MyViewHolder holder = new MyViewHolder(itemView);
return holder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
MyViewHolder holder = (MyViewHolder) viewHolder;
ClassBean info = classList.get(position);
holder.tv1.setText(info.getClassName());
//重点
holder.recyclerView2.setLayoutManager(new LinearLayoutManager(holder.recyclerView2.getContext()));
MyAdapter2 adapter2 = new MyAdapter2(info.getStudents());
holder.recyclerView2.setAdapter(adapter2);
}
@Override
public int getItemCount() {
return classList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView tv1;
RecyclerView recyclerView2;
public MyViewHolder(View itemView) {
super(itemView);
tv1 = (TextView) itemView.findViewById(R.id.tv1);
recyclerView2 = (RecyclerView) itemView.findViewById(R.id.recyclerView2);
}
}
}
当然,还有item_2.xml
、MyAdapter2.java
,
public class MyAdapter2 extends RecyclerView.Adapter {
private List<StudentBean> studentList;
public MyAdapter2(List<StudentBean> studentList) {
this.studentList = studentList;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_2, parent, false);
MyViewHolder holder = new MyViewHolder(itemView);
return holder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
MyViewHolder holder = (MyViewHolder) viewHolder;
StudentBean info = studentList.get(position);
holder.tv2.setText(info.getName());
holder.tv3.setText(info.getAge());
}
@Override
public int getItemCount() {
return studentList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView tv2;
TextView tv3;
public MyViewHolder(View itemView) {
super(itemView);
tv2 = (TextView) itemView.findViewById(R.id.tv2);
tv3 = (TextView) itemView.findViewById(R.id.tv3);
}
}
}
其他
Demo:http://git.oschina.net/AndroidUI/nestedrecyclerview01
recyclerview嵌套recyclerview
怎么让最后一个item不显示 分割线
只需要修改一行代码即可。重写DividerItemDecoration
这个类,继承RecyclerView.ItemDecoration
,把下面的i < childCount
改成i < childCount-1
,大功告成。
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount-1; i++) {
final View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
final int top = bottom - mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
修改的地方属于这个方法(以竖直排列为例)
修改的地方:
使用补间动画+recyclerView快速滑动, item会堆在一起,造成卡顿现象。
参考:RecyclerView实现Item滑动加载进入动画效果
使用补间动画+recyclerView快速滑动, item会堆在一起,造成卡顿现象,到动画持续时长(比如设置500ms)后才消失。
ScaleAnimation scale = new ScaleAnimation(0.8f, 1.0f, 0.8f, 1.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scale.setDuration(500);//或者
//holder.itemView.startAnimation(scale);
holder.itemView.setAnimation(scale);
scale.start();
怎么解决卡顿?
方法一:采用属性动画
方法二:item滑出屏幕的时候立即取消动画。
方法一:使用属性动画
AnimatorSet set = new AnimatorSet();
ObjectAnimator scaleX = ObjectAnimator.ofFloat(holder.itemView, "scaleX", 0.9f, 1.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(holder.itemView, "scaleY", 0.9f, 1.0f);
set.setDuration(500);
set.playTogether(scaleX, scaleY);
set.start();
方法二:针对补间动画
ScaleAnimation scale = new ScaleAnimation(0.8f, 1.0f, 0.8f, 1.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scale.setDuration(500);//或者
//holder.itemView.startAnimation(scale);
holder.itemView.setAnimation(scale);
scale.start();
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
holder.itemView.clearAnimation();
}
相关资料收集
Android5.x:RecycleView(一):实现ListView + GridView + StaggeredGridLayou效果
Android5.x:RecycleView(二):单选 、多选、item背景色
Android5.x:RecycleView(三):上下拖动和左右滑动删除