您的位置:首页 > 移动开发 > Android开发

Android5.x:RecycleView(一):实现ListView + GridView + StaggeredGridLayou效果

2016-08-30 12:19 861 查看
参考:

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设置属性
getItemCountitem的数量

步骤

找到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);


源码:RecyclerView01

复杂的RecyclerView

和listview一样,复杂的RecyclerView即使item有多种类型。这是需要重写adapter的2方法
getItemViewType(int position) {...}   getItemCount() {...}
,定义View类型:
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();
}


相关资料收集

RecyclerView优秀文集,从入门到精通

Android5.x:RecycleView(一):实现ListView + GridView + StaggeredGridLayou效果

Android5.x:RecycleView(二):单选 、多选、item背景色

Android5.x:RecycleView(三):上下拖动和左右滑动删除
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐