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
给RecycleView设置 LayoutManager
给RecycleView设置 Adapter
我们发现recyclerview没有分割线,需要调用mRecyclerView.addItemDecoration()添加分割线,
但是ItemDecoration是抽象类,需要我们自己实现。
该类DividerItemDecoration源于:
http://blog.csdn.net/lmj623565791/article/details/45059587
注意:注意context为null的情况,尽量用
使用自定义的分割线:styles.xml
效果图:
然后在mainActivity中调用:
方法二:在
然后在onBindViewHolder(…)中调用点击事件监听
如果只有短点击,同理也需要在onBindViewHolder()中设置.
Mactivity中调用
最后在activity中使用点击事件
效果图:
容易引起角标越界异常,因为调用该方法后,不会重新走onBindViewHolder(…),所以position的值没有变化。
解决方法:
2 下面的是没有给HeadView或FootView创建单独的ViewHolder,用的是同一个ViewHolder,并在其构造方法中判断;
3 当然也可以给HeadView或FootView创建单独的ViewHolder, 并在onCreateViewHolder(…)中返回对应的ViewHolder。
首先定义HeadView和FootView的类型
其次:定义HeadView和FootView变量,并设置和获取他们的数量(0/1)
第三步:重写方法getItemViewType(int position)
此处注意:定义HeaderType 和FooterType的值不能是0,因为该方法默认返回0,如果头或尾定义了0,而你又没有给其它item(HeadView/FootView之外的item)定义type,仍然使用
上面的是确定有header,其实不应该这样判断
第四步:修改Adapter中的3个方法
第五步:我们添加HeadView和FootView,setHeaderView(view)放在setAdapter(adapter)前后无所谓。
这是我们发现:HeadView或FootView的宽度没有充满屏幕,这是什么原因呢? 详见:Android基础:三种inflate的区别
这是因为我们在填充HeadView或FootView使用的是
改成下面这种,注意:第二个参数parent是recyclerView
参考:
Android 优雅的为RecyclerView添加HeaderView和FooterView
Android 简捷地为RecyclerView添加HeadView和FootView
Demo:https://git.oschina.net/RecyclerView/RecyclerViewDemo2
Demo源码:https://git.oschina.net/RecyclerView/RecyclerViewHeaderFooter01
它的作用是当我们队数据进行增删的时候,不会重新计算item宽高。
在Developer Guild–> Meterial Design–>创建列表和卡片中是这样介绍的
2 onCreateViewHolder(…)中的holder和onBindViewHolder(RecyclerView.ViewHolder holder, int position) {。。}中的holder就是我们创建的MyHolder,可以直接进行格式转换,也可以声明成员变量。
3 最好先创建类ViewHolder,再创建Adapter,这样可以使用直接指定泛型,就不需要再格式转换了。
4 item的高度设为match_parent,父布局设置100dp,item的高无效
5 item的子view(textview)宽度match_parent,父布局的宽度也是match_parent,但是仍然无法充满屏幕。
6 总结:结合4+5,使用View.inflate(context, R.layout.item_main, null);创建item的view,会导致item的view高度无效,子view(textview)的高宽无效。
7 item宽度没有充满屏幕
RecyclerView子View宽度不能全屏的问题,在Adapter的onCreateViewHolder创建子view的时候要把parent传进去;
源码:RecyclerView01
以下是主要代码:
Demo: https://git.oschina.net/Android5x/ShopCartDemo01
DividerGridItemDecoration的下载地址
效果图:
GridView的divider 需要设置高度+宽度
要想使itemView 跟布局的layout_margin生效,必须指定root即parent,也就是不能使用第一种方式创建itemView
源码: https://git.oschina.net/Android5x/RecyclerViewDemo02
此种方法在4.x系统上好用,能显示滑动也流畅,但是在5.x上虽然显示正常,但是滑动的时候好像被粘住了,没有惯性效果。。。
最后解决方法是重写最外层的Scrollview
Recycleview checkbox 复用出现混乱解决方法
RecyclerView中使用checkbox遇到的问题
感觉还是用标志位方便。
给item只设置
当然,还有
recyclerview嵌套recyclerview
修改的地方属于这个方法(以竖直排列为例)
修改的地方:
使用补间动画+recyclerView快速滑动, item会堆在一起,造成卡顿现象,到动画持续时长(比如设置500ms)后才消失。
怎么解决卡顿?
方法一:采用属性动画
方法二:item滑出屏幕的时候立即取消动画。
方法一:使用属性动画
方法二:针对补间动画
Android5.x:RecycleView(一):实现ListView + GridView + StaggeredGridLayou效果
Android5.x:RecycleView(二):单选 、多选、item背景色
Android5.x:RecycleView(三):上下拖动和左右滑动删除
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或FootView2 下面的是没有给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/nestedrecyclerview01recyclerview嵌套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(三):上下拖动和左右滑动删除
相关文章推荐
- Android RecyclerView详解之实现 ListView GridView瀑布流效果
- Android中Recyclerview使用1----实现ListView,GridView,瀑布流样式
- 使用RecyclerView实现GridView和ListView混排的效果
- Android RecycleView实现不同样式Item样式效果完美解决
- Android之ViewPager+GridView实现仿美团首页导航栏布局分页效果
- Android 实现横向标题栏滚动效果(HorizontalScrollView + GridView + Viewpager + 自定义适配器)
- (4.1.11.1)Android中使用RecyclerView和CardView实现瀑布流效果(StaggeredGrid)
- TimeLine -- Android 列表(ListView或者RecycleView)中时间线的效果
- 打造Android集合控件数据绑定(支持添加监听,支持AbsListView与RecycleView,支持异步加载等)(三)具体实现
- Android 自定义RecyclerView (替代ListView的最新组件)实现真正的Gallery效果
- RecyclerView--实现 ListView,GridView,瀑布流 效果
- Android RecyclerView实现listview效果
- Android编程实现ListView头部ViewPager广告轮询图效果
- android Material Design之 ToolBar+TabLayout+recycleView的效果实现
- Android中使用RecyclerView和CardView实现瀑布流效果(StaggeredGrid)
- Android 利用ViewPager+GridView实现首页导航栏布局分页效果
- Android RecyclerView (一)初学,实现ListView列表效果。
- Android RecyclerView (二)初学,实现GridView列表和瀑布流效果。
- android开发之&使用ViewPager加gridView实现菜单按钮分页滑动(类似QQ表情选择翻页效果)
- Android自定义View之ListView实现时间轴效果:我只是个送快递的。