列表控件RecyclerView的使用
来源:互联网 发布:淘宝卖ps软件犯法吗 编辑:程序博客网 时间:2024/05/02 01:50
[TOC]
列表控件也算是很常见的控件了,现在基本都切换到RecyclerView了,这边记录下列表控件的基本的使用以及几种情况的处理:
Demo链接
RecyclerView
官网介绍
使用上基本步骤如下:
1. 设置布局管理器
// LinearLayout布局LinearLayoutManager mLinearLayoutMgr = new LinearLayoutManager(this);mLinearLayoutMgr.setOrientation(LinearLayoutManager.HORIZONTAL);// Grid布局,数值表示列数GridLayoutManager mGridLayoutMgr = new GridLayoutManager(this, 3);// 瀑布流布局StaggeredGridLayoutManager mStaggedGridLayoutMgr = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.HORIZONTAL);mRv.setLayoutManager(mLinearLayoutMgr);
- 设置适配器
适配器需要继承 RecyclerView.Adapter
public class RvAdapter extends RecyclerView.Adapter<RvAdapter.MyViewHolder> { private final ArrayList<Integer> data;//数据源 private final LayoutInflater mInflater;//在创建View时需要用 private static final String TAG = "RvAdapter"; public RvAdapter(Context cxt, ArrayList<Integer> picList) { this.data = picList; mInflater = LayoutInflater.from(cxt); } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // 在这里创建ItemView并设置ViewHolder以便复用 MyViewHolder viewHolder = new MyViewHolder(mInflater.inflate(R.layout.item_rv, parent, false)); return viewHolder; } @Override public void onBindViewHolder(MyViewHolder holder, final int position) { // 设置数据 holder.iv.setBackgroundResource(data.get(position)); holder.tv.setText(position + ""); // 设置事件 holder.iv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i(TAG, "onClick pos:" + position); } }); } @Override public int getItemCount() { // 设个没啥好说的,返回总item个数 return data.size(); } class MyViewHolder extends RecyclerView.ViewHolder { // 复用的ViewHolder 需要继承RecycleView ImageView iv; TextView tv; public MyViewHolder(View itemView) { super(itemView); iv = (ImageView) itemView.findViewById(R.id.iv_item); tv = (TextView) itemView.findViewById(R.id.tv_index); } }}
- 设置分割线和动画,这两个我没基本没用到,就先跳过了;
添加header
对于Grid布局管理器,如果想添加一个占据一整行的header,需要重写指定位置的item所占的宽度:
mLayoutMgr = new GridLayoutManager(this, 3);mRv.setLayoutManager(mLayoutMgr);mLayoutMgr.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return position == 0 ? mLayoutMgr.getSpanCount() : 1; } });
跳转动效
直接跳转到指定position位置时,recycleView的变化是瞬间的,体验不是很好,我们会希望是缓慢滑动过去,直接想到的方法自然是 smoothScrollTo***
,效果类似如下
看看RecycleView的相应方法源码:
public void smoothScrollToPosition(RecyclerView recyclerView, State state, int position) { Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling"); }
最终还是使用smoothScrollToPosition(int position),重写布局管理器即可:
// 控制滑动速度的LinearLayoutManagerpublic class ScrollSpeedLinearLayoutManger extends LinearLayoutManager { private float MILLISECONDS_PER_INCH = 0.3f; private Context context; public ScrollSpeedLinearLayoutManger(Context context) { super(context); this.context = context; } @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) { @Override public PointF computeScrollVectorForPosition(int targetPosition) { return ScrollSpeedLinearLayoutManger.this .computeScrollVectorForPosition(targetPosition); } //返回滑动一个pixel需要多少毫秒 @Override protected float calculateSpeedPerPixel (DisplayMetrics displayMetrics) { return MILLISECONDS_PER_INCH / displayMetrics.density; } }; linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); } public void setSpeedSlow() { //自己在这里用density去乘,希望不同分辨率设备上滑动速度相同 //0.3f是自己估摸的一个值,可以根据不同需求自己修改 MILLISECONDS_PER_INCH = context.getResources().getDisplayMetrics().density * 0.3f; } public void setSpeedFast() { MILLISECONDS_PER_INCH = context.getResources().getDisplayMetrics().density * 0.03f; }}
下拉刷新
RecycleView也没有了类似ListView那样的header和footer部分,下拉刷新其实可以用系统提供的控件:SwipeRefreshLayout
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/srl_refresh" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/rv_load_more" android:layout_width="match_parent" android:layout_height="match_parent"/></android.support.v4.widget.SwipeRefreshLayout>
SwipeRefreshLayout mSrl = findView(R.id.srl_refresh);// 使用系统控件来监听刷新,记得数据更新后要取消刷新动画mSrl.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { // TODO: 更新数据 // 取消加载动画 mSrl.setRefreshing(false); }});
上拉加载更多
update: 现在我一般是用这个库 SwipyRefreshLayout ,上拉下拉都是一个效果
类似分页加载,由于没有单独提供footer,所以我们考虑通过ViewType来模拟;
在adapter中需有两种ItemViewType,一种为底部进度加载条样式,我们通过判断recycleView是否已经滑动到底部,来动态添加/删除一行标志数据用以表示是否需要显示进度条的itemView,另外,数据加载完后,需要删除原先的标志数据,即删掉加载条,然后更新列表即可:
mRv.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); totalItemCount = mLayoutMgr.getItemCount(); lastVisibleItemPos = mLayoutMgr.findLastVisibleItemPosition(); // 加1是position和size的区别 if (!isLoading && totalItemCount <= (lastVisibleItemPos + 1)) { loadMoreData(); isLoading = true; } }});// 模拟加载数据过程private void loadMoreData() { // 在原数据集末尾添加一条标志数据,告诉适配器显示加载进度条 mData.add(null);//加载什么样的数据,只要跟adapter配合能识别出来即可 mAdapter.notifyItemInserted(mData.size() - 1); mHandler.postDelayed(new Runnable() { @Override public void run() { // 加载过程结束后,记得清除最后一个标志位 mData.remove(mData.size() - 1); mAdapter.notifyItemRemoved(mData.size()); // 获取新增数据 int start = mData.size(); int end = start + 10; for (int i = start; i < end; i++) { mData.add("added pos: " + i); } // 更新列表 mAdapter.notifyDataSetChanged(); isLoading = false; } }, 2000);}// 在adapter中重写判断itemViewType的方法@Overridepublic int getItemViewType(int position) { // 标志数据也可以用其他的,这里我用 null 或者 "" 来表示 if (TextUtils.isEmpty(mData.get(position))) { return TYPE_LOADING; } else { return TYPE_NORMAL; }}
默认添加删除动画
Demo
RecyclerView自带的一个 DefaultItemAnimator 可以实现添加删除item时,插入移除动画效果
//kotlin代码//设置recyclerview的动画recyclerView.itemAnimator = DefaultItemAnimator()//添加或删除数据源后,要调用如下方法才有动画效果recyclerView.adapter.notifyItemRangeInserted(addPos,addItemCount)recyclerView.adapter.notifyItemRemoved(removePos)
使用ItemTouchHelper实现拖拽改变item顺序及swipe滑动删除item
Demo
// kotlin// 添加滑动/拖拽功能// java的匿名内部类对应过来就是object对象表达式了ItemTouchHelper(object : ItemTouchHelper.Callback() { var vh: RecyclerView.ViewHolder? = null /** * 设置itemView可以移动的方向 * */ override fun getMovementFlags(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?): Int { // 拖拽的标记,这里允许上下左右四个方向 val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT // 滑动的标记,这里允许左右滑动 val swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END return makeMovementFlags(dragFlags, swipeFlags) } /** * 当一个Item被另外的Item替代时回调,也就是数据集的内容顺序改变 * 返回true, onMoved()才会进行 * */ override fun onMove(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, target: RecyclerView.ViewHolder?): Boolean { return true } /** * 当onMove返回true的时候回调,刷新列表 * */ override fun onMoved(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?, fromPos: Int, target: RecyclerView.ViewHolder?, toPos: Int, x: Int, y: Int) { super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y) // 移动完成后修改列表位置并刷新列表 Collections.swap(data, viewHolder!!.adapterPosition, target!!.adapterPosition) rv_main.adapter.notifyItemMoved(viewHolder!!.adapterPosition, target!!.adapterPosition) } /** * 滑动完成时回调,这里设置为滑动删除,删除相应数据后刷新列表 * */ override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) { data.removeAt(viewHolder!!.adapterPosition) rv_main.adapter.notifyItemRemoved(viewHolder!!.adapterPosition) toast("删除成功") } /** * Item是否可以滑动 * */ override fun isItemViewSwipeEnabled() = true /** * Item是否可以长按 * */ override fun isLongPressDragEnabled() = true}).attachToRecyclerView(rv_main)
popupWindow中使用RecyclerView
recyclerView的高度自适应
默认情况下,即使设置其高度为wrap_content,其高度也是全屏的,需要重新布局管理器来计算item总高度
测试时发现适用于v7-23.1.1,升级到23.4.0后就会提示数组下标越界,可将
View child = recycler.getViewForPosition(i);
修改为View child = getChildAt(i);if (child != null) {...}
,但其实没有必要,因为在v7-23.4.0的时候,系统已经可以自适应高度了,不需要手动去计算
public class FixGridLayoutManager extends GridLayoutManager { public FixGridLayoutManager(Context context, int spanCount) { //默认方向是VERTICAL super(context, spanCount); } public FixGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { int height = 0; int childCount = getItemCount(); for (int i = 0; i < childCount; i++) { View child = recycler.getViewForPosition(i); measureChild(child, widthSpec, heightSpec); ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); if (i % getSpanCount() == 0) { int measuredHeight = child.getMeasuredHeight() + getDecoratedBottom(child) + lp.topMargin + lp.bottomMargin; height += measuredHeight; } } setMeasuredDimension(View.MeasureSpec.getSize(widthSpec), height); } }
点击事件中使用itemNotify时FC
使用自定义的布局管理器后,点击事件会报错:
java.lang.IllegalArgumentException: Tmp detached view should be removed from RecyclerView before it can be recycled: ViewHolder
没去细究为啥,我在adapter中使用的是 notifyItemChanged(position);
改成普通的全量刷新就可以了
notifyDataSetChanged();
GridView
gridView基本没再用了,不过之前碰到过几个坑,在此也一并记录下:
// 基本使用方法GridView mGv = findViewById(R.id.gv_basic);mGv.setNumColumns(3);//设置列数,也可在xml中设定// 适配器同样与ListView类似,继承自BaseAdapterGvAdapter gvAdapter = new GvAdapter(this, mData, true);mGv.setAdapter(gvAdapter);//添加点击监听mGv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.i(TAG, "onItemClick 您点击了第 position: " + position + " 个item"); }});
1. 固定item高度
之前有个需求是在一个页面显示9个item,填满屏幕:
@Overridepublic View getView(int position, View convertView, ViewGroup parent) { ...... //固定item高度,这里使用3*3填满整个屏幕/gridView convertView.setLayoutParams(new AbsListView.LayoutParams(parent.getWidth() / 3, parent.getHeight() / 3)); // 恢复默认的话设置高度为wrap_content就可以了 // convertView.setLayoutParams(new AbsListView.LayoutParams(parent.getWidth() / 3,ViewGroup.LayoutParams.WRAP_CONTENT)); ...... return convertView;}
2. ListView中嵌套GridView
- gridView只显示一行的问题
//重写GridView的onMeasure()方法public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec);}
- 同时设置ListView和GridView的点击事件,只有GridView的有响应
需要在ListView的item布局顶层屏蔽子元素焦点事件
<LinearLayout ...... android:descendantFocusability="blocksDescendants"> <org.lynxz.androiddemos.widget.FixGridView ....../></LinearLayout>
这样listView的item点击事件就能被触发了,同时若是点击到GridView的item会触发GridView的事件;
同理,若是GridView的item中有抢焦点的控件导致其点击事件失效,也同样在其item布局顶层添加该属性;
- 列表控件RecyclerView的使用
- RecyclerView控件的使用
- RecyclerView控件的使用
- 使用RecyclerView制作包含左滑删除按钮的列表控件
- android常用控件RecyclerView(三) RecyclerView的使用
- RecyclerView,SwipeRefreshLayout,CardView最新控件的使用
- [Android]关于RecyclerView控件的使用
- Android 高级编程 RecyclerView 控件的使用
- Android控件之RecyclerView的基本使用
- Android 控件 RecyclerView 的基本使用
- Android常用控件--RecyclerView的简单使用
- 使用Recyclerview控件遇到的一些问题
- Android RecyclerView控件的使用(一)
- RecyclerView 不一样的列表
- RecyclerView 控件使用
- 虚拟列表控件的使用
- MFC 列表控件的使用
- 列表ListView控件的使用
- 魏则西事件后推广模式有所改变,SEO优化对企业显得尤为重要
- 如果看了此文你还不懂傅里叶变换,那就过来掐死我吧【完整版】
- java把byte类型数组转化成对象的步骤。
- JavaScript学习--Item14 使用prototype的几点注意事项
- nRF51822外部中断学习总结
- 列表控件RecyclerView的使用
- iOS 数据库篇7—FMDB简单介绍
- inner join 和where哪个效率更高
- PendingIntent的使用
- JS获取URL参数
- mysql 二进制日志清理
- 链接 <a> 的CSS伪类不起作用
- MySQL优化
- 索引