Android RecyclerView从入门到玩坏
目录
- 前言
- 基础使用
- 分隔线
- 点击监听
- 搭配CardView
- 更丰富的条目
- 增删条目
- 快速添加视图
- 让RecyclerView支持复杂视图
- 最后
前言
RecyclerView在Android界面开发当中是很重要的, 那掌握它也是很必要的. 但是有些时候会觉得它很厚重, 这里就从RecyclerView的基础一直说到扩展, 让你把RecyclerView学薄了.
RecyclerView官方文档也是非常厚重.
这篇文章融合了自己原来的多篇文章, 并进行了修正和改进, 而且添加了很多很有趣的内容.
本文需要20分钟以上的阅读时间, 请合理安排.
多图预警, 转载请注明出处!
基础使用
要使用RecyclerView在Android Studio 2.x(以下简称AS), 要这样:
compile 'com.android.support:cardview-v7:25.3.1' compile 'com.android.support:recyclerview-v7:25.3.1'
到了AS 3.x, 要这样:
implementation 'com.android.support:cardview-v7:26.1.0' implementation 'com.android.support:recyclerview-v7:26.1.0'
之后在布局文件中写入如下代码就引入了RecyclerView了.
<android.support.v7.widget.RecyclerView android:id="@+id/rv_main" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical" />
接下来说说介绍些各种布局. 可以看RecyclerView.LayoutManager官方文档.
布局类 | 效果 |
---|---|
LinearLayoutManager | 以垂直或水平滚动列表方式显示项目 |
GridLayoutManager | 在网格中显示项目 |
StaggeredGridLayoutManager | 在分散对齐网格中显示项目 |
mRvMain = (RecyclerView) findViewById(R.id.rv_main); // 设置布局 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); mRvMain.setLayoutManager(linearLayoutManager);
最关键的还是适配器的撰写. 但是理解起来不是很难, 你只要将ListView的适配器写法带入理解就好. 这里把全部代码贴出来, 因为后面要在这个基础上不断扩充.
public class MyRVAdapter2 extends RecyclerView.Adapter<MyRVAdapter2.MyTVHolder> { private final LayoutInflater mLayoutInflater; private final Context mContext; private final ArrayList<String> mData; public MyRVAdapter2(Context context) { mLayoutInflater = LayoutInflater.from(context); mContext = context; mData = new ArrayList<>(); for (int i = 0; i < 40; i++) { mData.add("hello " + i); } } @Override public MyRVAdapter2.MyTVHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new MyRVAdapter2.MyTVHolder(mLayoutInflater.inflate(R.layout.rv_txt_item, parent, false)); } @Override public void onBindViewHolder(final MyRVAdapter2.MyTVHolder holder, int pos) { holder.mTextView.setText(mData.get(pos)); } @Override public int getItemCount() { return mData == null ? 0 : mData.size(); } class MyTVHolder extends RecyclerView.ViewHolder { TextView mTextView; MyTVHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(R.id.tv_txt); } } }
然后写个最基础的TextView条目. 让它跑起来看看效果.
<?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="horizontal"> <TextView android:id="@+id/tv_txt" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:padding="@dimen/eight_dp" android:text="@string/tmp" android:textSize="@dimen/thirty_sp" /> </LinearLayout>
分隔线
前面的部分已经是基础的RecyclerView使用了. 那比起ListView是不是没有了分隔线. 这里上一个简单好用的开源库RecyclerView-FlexibleDivider.
引入:
implementation 'com.yqritc:recyclerview-flexibledivider:1.4.0'
使用:
mRvMain.addItemDecoration( new HorizontalDividerItemDecoration.Builder(this).build());
看效果就达到了吧.
觉得不好看, 还可以自定义, 更多写法可以参见文档内容.
mRvMain.addItemDecoration( new HorizontalDividerItemDecoration.Builder(this) .color(Color.BLUE) .sizeResId(R.dimen.two_dp) .marginResId(R.dimen.eight_dp, R.dimen.eight_dp) .build());
而且而且, 竖着的分隔线也大丈夫哦.
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2); mRvMain.setLayoutManager(gridLayoutManager);
mRvMain.addItemDecoration( new VerticalDividerItemDecoration.Builder(this).build());
点击监听
再回忆一下在天国的ListView, 还有item的点击吧, 这个也要自己写.
适配器中:
public interface OnItemClickListener { void onItemClick(View view, int position); void onItemLongClick(View view, int position); } private MyRVAdapter2.OnItemClickListener mOnItemClickListener; public void setOnItemClickListener(MyRVAdapter2.OnItemClickListener mOnItemClickListener) { this.mOnItemClickListener = mOnItemClickListener; }
onBindViewHolder中设置点击监听.
@Override public void onBindViewHolder(final MyRVAdapter2.MyTVHolder holder, int pos) { holder.mTextView.setText(mData.get(pos)); if (mOnItemClickListener != null) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = holder.getLayoutPosition(); mOnItemClickListener.onItemClick(holder.itemView, pos); } }); holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { int pos = holder.getLayoutPosition(); mOnItemClickListener.onItemLongClick(holder.itemView, pos); return false; } }); } }
使用监听:
mAdapter.setOnItemClickListener(new MyRVAdapter2.OnItemClickListener() { @Override public void onItemClick(View view, int position) { Toast.makeText(UIUtil.getContext(), "click" + position, Toast.LENGTH_SHORT).show(); } @Override public void onItemLongClick(View view, int position) { Toast.makeText(UIUtil.getContext(), "long click" + position, Toast.LENGTH_SHORT).show(); } });
搭配CardView
是不是这个点击看着没啥感觉, 没事, 我们换上CardView再来一次.
布局文件:
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/eight_dp" android:foreground="@drawable/card_foreground" card_view:cardCornerRadius="@dimen/four_dp"> <TextView android:id="@+id/tv_txt" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:padding="@dimen/eight_dp" android:text="@string/tmp" android:textSize="@dimen/thirty_sp" /> </android.support.v7.widget.CardView>
给CardView加上水波纹点击特效:
<?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/colorPrimary" />
在老版本就只能用选择器了, 其实效果也还好:
<?xml version="1.0" encoding="utf-8"?> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/card_foreground_selector" android:insetBottom="@dimen/four_dp" android:insetLeft="@dimen/three_dp" android:insetRight="@dimen/three_dp" android:insetTop="@dimen/four_dp" />
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape android:shape="rectangle"> <solid android:color="@color/colorPrimaryTran" /> <corners android:radius="@dimen/four_dp" /> </shape> </item> <item android:state_enabled="true" android:state_focused="true"> <shape android:shape="rectangle"> <solid android:color="#0f000000" /> <corners android:radius="@dimen/four_dp" /> </shape> </item> </selector>
更丰富的条目
大家应该都知道TextView可以设置图标吧, 这里来看下效果图, 顺带感受下android界面设计语言的变化.
<?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="horizontal"> <TextView android:id="@+id/tv_txt" android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@mipmap/ic_launcher" android:drawablePadding="@dimen/sixteen_dp" android:drawableStart="@mipmap/ic_launcher" android:gravity="center_vertical" android:padding="@dimen/eight_dp" android:text="@string/tmp" android:textSize="@dimen/thirty_sp" /> </LinearLayout>
让GridLayoutManager展示不同宽度的条目
方的是4.x上的, 圆的是8.x上的, 可以看到, 变化还是很大的. 我们回正题. GridLayoutManager布局是可以设置宽度的, 不一定都是一样大的, 来看下实现.
// 指定item宽度 gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (position == 0 || position == (mAdapter.getItemCount() - 1) / 2 || position == (mAdapter.getItemCount() - 1)) { return gridLayoutManager.getSpanCount(); } else { return 1; } } });
来看效果图, 发现我们的分隔线崩了是吧, 如果真想用这个分隔线也还是要自己动手修补修补, 改动改动, 开源库再棒也猜不到你的项目需求呀.
当然了, 我还是很喜欢这个分隔线的, 我们来看看横着滚动的效果.
布局文件要改动:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:id="@+id/tv_txt" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:text="@string/tmp" android:textSize="@dimen/thirty_sp" /> </LinearLayout>
gridLayoutManager.setOrientation(GridLayoutManager.HORIZONTAL);
展示不同布局
之前变化宽度其实还是相同条目, 现在要展示不同条目:
写一个图的条目:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="@dimen/eight_dp"> <ImageView android:id="@+id/iv_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@mipmap/ic_launcher" /> </RelativeLayout>
public enum ITEM_TYPE { ITEM_TYPE_IMAGE, ITEM_TYPE_TEXT }
这里多了判断条目类型, 还要注意返回值的变化, 用了更基类的RecyclerView.ViewHolder.
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) { return new MyRVAdapter2.MyIVHolder(mLayoutInflater.inflate(R.layout.rv_img_item, parent, false)); } else { return new MyRVAdapter2.MyTVHolder(mLayoutInflater.inflate(R.layout.rv_txt_item, parent, false)); } }
类继承上面也要变成RecyclerView.ViewHolder, 这些都是要对应的.
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
当然了, holder也是不能少的.
public class MyIVHolder extends RecyclerView.ViewHolder { ImageView mImageView; MyIVHolder(View view) { super(view); mImageView = (ImageView) view.findViewById(R.id.iv_img); } }
@Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, int pos) { if (holder instanceof MyRVAdapter2.MyTVHolder) { ((MyRVAdapter2.MyTVHolder) holder).mTextView.setText(mData.get(pos)); } else if (holder instanceof MyRVAdapter2.MyIVHolder) { ((MyRVAdapter2.MyIVHolder) holder).mImageView.setImageDrawable(UIUtil.getDrawable(R.mipmap.ic_launcher)); } // 点击监听 ... }
顺带的, 我们把之前放宽的条目变成不同的视图, 也就是对应起来:
@Override public int getItemViewType(int position) { if (position == 0 || position == (getItemCount() - 1) / 2 || position == (getItemCount() - 1)) { return ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal(); } else { return ITEM_TYPE.ITEM_TYPE_TEXT.ordinal(); } }
看看效果:
它还能继续地复杂, 试试瀑布流StaggeredGridLayoutManager:
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/eight_dp" card_view:cardCornerRadius="@dimen/four_dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ImageView android:id="@+id/iv_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:src="@mipmap/ic_launcher" /> <TextView android:id="@+id/tv_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="@string/tmp" android:textSize="@dimen/thirty_sp" /> </LinearLayout> </android.support.v7.widget.CardView>
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL); mRvMain.setLayoutManager(staggeredGridLayoutManager);
分割线又崩了, 嘿嘿, 其实用上了CardView, 分割线没什么必要再用了.
增删条目
现在适配器中添加增删方法:
public void addData(int position) { mData.add(position, "hello x"); notifyItemInserted(position); } public void removeData(int position) { mData.remove(position); notifyItemRemoved(position); }
再写入点击事件中, 点击增加, 长按删除:
mAdapter.setOnItemClickListener(new MyRVAdapter2.OnItemClickListener() { @Override public void onItemClick(View view, int position) { mAdapter.addData(position); } @Override public void onItemLongClick(View view, int position) { mAdapter.removeData(position); } });
增删条目开源库
这里再上一个开源库recyclerview-animators, 可以修改增删动画, 种类也很丰富, 还能在它基础上自定义:
分类 | 动画类名 |
---|---|
Cool | LandingAnimator |
Scale | ScaleInAnimator, ScaleInTopAnimator, ScaleInBottomAnimator, ScaleInLeftAnimator, ScaleInRightAnimator |
Fade | FadeInAnimator, FadeInDownAnimator, FadeInUpAnimator, FadeInLeftAnimator, FadeInRightAnimator |
Flip | FlipInTopXAnimator, FlipInBottomXAnimator, FlipInLeftYAnimator, FlipInRightYAnimator |
Slide | SlideInLeftAnimator, SlideInRightAnimator, OvershootInLeftAnimator, OvershootInRightAnimator, SlideInUpAnimator, SlideInDownAnimator |
引入:
implementation 'jp.wasabeef:recyclerview-animators:2.3.0'
使用:
mRvMain.setItemAnimator(new SlideInLeftAnimator());
这里给大家展示两种效果, 其它的自己尝试吧.
mRvMain.setItemAnimator(new LandingAnimator());
快速添加视图
还有像Header, Foot这样的视图, 自己写也还是要费些功夫的, 这里推荐Android大神的库baseAdapter
引入:
implementation 'com.zhy:base-rvadapter:3.0.3'
添加头尾视图
HeaderAndFooterWrapper mHeaderAndFooterWrapper = new HeaderAndFooterWrapper(mAdapter); TextView t1 = new TextView(this); t1.setText("Header 1"); t1.setTextSize(30); TextView t2 = new TextView(this); t2.setText("Foot 1"); t2.setTextSize(30); mHeaderAndFooterWrapper.addHeaderView(t1); mHeaderAndFooterWrapper.addFootView(t2); mRvMain.setAdapter(mHeaderAndFooterWrapper);
添加更多视图
LoadMoreWrapper mLoadMoreWrapper = new LoadMoreWrapper(mAdapter); mLoadMoreWrapper.setLoadMoreView(R.layout.rv_cv_img_txt_item); mLoadMoreWrapper.setOnLoadMoreListener(new LoadMoreWrapper.OnLoadMoreListener() { @Override public void onLoadMoreRequested() { } }); mRvMain.setAdapter(mLoadMoreWrapper);
是不是感觉特别爽, 那看看更爽的, 在不写适配器的情况下快速添加条目:
final ArrayList<String> mData = new ArrayList<>(); for (int i = 0; i < 40; i++) { mData.add("hello " + i); } mRvMain.setAdapter(new CommonAdapter<String>(this, R.layout.rv_cv_txt_item, mData) { @Override protected void convert(ViewHolder holder, String s, int position) { holder.setText(R.id.tv_txt, mData.get(position)); } });
是不是感觉省了一万个小时呢.
让RecyclerView支持复杂视图
每次加入新的视图都要对适配器进行比较大程度的改动, 这样是很容易出错的. 这里引入一个非常棒的开源库-AdapterDelegates, 降低下代码耦合性.
引入:
implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1'
先不说使用细节, 来看看实现后想加入不同视图有多简单吧:
ArrayList<Base> data = new ArrayList<>(); for (int i = 0; i < 10; i++) { data.add(new B("b " + i)); } for (int i = 0; i < 10; i++) { data.add(new A("a " + i)); } BaseAdapter animalAdapter = new BaseAdapter(this, data); mRvMain.setAdapter(animalAdapter);
是不是惊了, 也就是说, 你只要实现了A, B这些视图类, 直接新建放入数组就完事了.
需要写基础适配器:
public class BaseAdapter extends RecyclerView.Adapter { private AdapterDelegatesManager<List<Base>> delegatesManager; private List<Base> items; public BaseAdapter(Activity activity, List<Base> items) { this.items = items; delegatesManager = new AdapterDelegatesManager<>(); delegatesManager.addDelegate(new AAdapterDelegate(activity)) .addDelegate(new BAdapterDelegate(activity)); } @Override public int getItemViewType(int position) { return delegatesManager.getItemViewType(items, position); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return delegatesManager.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { delegatesManager.onBindViewHolder(items, position, holder); } @Override public int getItemCount() { return items.size(); } }
需要对每个类进行进行具体设置, 这里以A为例.
public class AAdapterDelegate extends AdapterDelegate<List<Base>> { private LayoutInflater inflater; public AAdapterDelegate(Activity activity) { inflater = activity.getLayoutInflater(); } @Override public boolean isForViewType(@NonNull List<Base> items, int position) { return items.get(position) instanceof A; } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) { return new CatViewHolder(inflater.inflate(R.layout.rv_cv_img_txt_item, parent, false)); } @Override public void onBindViewHolder(@NonNull List<Base> items, int position, @NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) { CatViewHolder vh = (CatViewHolder) holder; A cat = (A) items.get(position); vh.name.setText(cat.getName()); } static class CatViewHolder extends RecyclerView.ViewHolder { public TextView name; public ImageView img; public CatViewHolder(View itemView) { super(itemView); name = (TextView) itemView.findViewById(R.id.tv_txt); img = (ImageView) itemView.findViewById(R.id.iv_img); } } }
最后
看完这篇应该是对RecyclerView有个大体认识了, 多练习练习就会得心应手起来了. 那还是有一点, 就像分隔线库的几次不理想表现, 具体项目要求还是要具体对待, 开源库也不是万能的. 最近不是又有什么开源项目套壳事件了嘛, 别人一开源就说自己有自主产权了真的好吗? 喜欢记得点赞或者关注我哦, 有意见或者建议评论区见~
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
十大游戏开发引擎优缺点对比
十大游戏开发引擎优缺点对比 戏引擎作为开发商研发游戏时必备的工具,每一款引擎的运用都关乎自家大作未来的销量,所以游戏商们对引擎的选择也非常小心翼翼。现在,手游、页游等新兴平台的表现又非常强势,所以选择正确的游戏引擎变得尤为重要。下面,小编就给大家带来开发商常用的10款游戏引擎,并且逐一分析其优缺点,未来想涉足游戏领域的朋友一定要看看。 Unity3D Unity3D对于游戏开发者们来说是一个真正可以负担的起的引擎,具有其他引擎难以匹敌的用户量。更为重要的是,你只需要付费一次,而且,不管你的游戏如何成功,都不用担心Unity会分走你的收入。这对于很多开发商来说当然是非常具有吸引力对,尤其是初创公司和新入行的开发者们。以下是Unity引擎的优点和缺点: 优点:业内最具竞争力的授权条款;易于使用而且兼容所有游戏平台;开发者社区支持强大;学习门槛非常第;开发商使用率最高。 缺点:工具数量有限,所以开发商必须给自己创作工具;做复杂和多样化的效果比较耗时。 虚幻引擎 数年以来,虚幻引擎一直是做高端EA游戏最受欢迎的引擎。《战争机器》、《蝙蝠侠:阿卡汉姆疯人院》(Batman: Arkham Asy...
- 下一篇
TextView未绘制情况下获取其宽高
/** * 注:StaticLayout是android中处理文字换行的一个类,TextView源码中也是通过这个类实现换行的,使用这个类可以 * 在不进行TextView绘制的前提下得到TextView的宽高,这里我们只需要获取到高度即可,这个高度当然也可以通过post * 在run中获取,但是这样做会有一个问题,界面是先绘制显示然后再计算高度根据我们的逻辑来收缩TextView的高度,在列表中 * 会出现闪烁的问题。使用这个类一定要注意构造方法中参数的传递,保证参数和布局中textView设置的一致,否则会有误差 * 这个问题和获取三行高度的要求是一样的 * * @param textView * @param content * @param width * @return */ public static int getTotalLineHeight(TextView textView, String content, int width) { if (TextUtils.isEmpty(content)) return 0; TextPaint textPaint = tex...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- 设置Eclipse缩进为4个空格,增强代码规范
- Mario游戏-低调大师作品
- MySQL8.0.19开启GTID主从同步CentOS8
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16