RecycleView中使用总结以及在项目中的实际运用场景总结
前言
RecycleView的问世,替代了ListViewt和GridView,性能得到提升。同时也出现了许多优秀的第三方开源库。本文总结了在实现项目中是如何运用RecycleView的场景,以及总结了项目中使用时的一些心得,希望对你有所帮助。
文章目录
1.RecycleView
官方对RecyclerView的描述:
A flexible view for providing a limited window into a large data set.
1.1 使用RecycleView的优缺点
优点:
- RecycleView强制封装ViewHolder
- 相当轻松的设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
- 可设置Item操作的动画,删除或者添加等
- 通过ItemDecoration,控制Item间的间隔,可自己绘制
缺点:
-
需要自己实现OnItemClickListener点击事件。
是缺点,也是优点,为何?
ListView 中对于点击事件的处理,其实是有很大弊端的,它的
setOnItemClickListener()
方法是为子项注册点击事件,这就导致只能识别到这一整个子项,对于子项中的组件比如按钮就束手无策了。因此,RecyclerView 直接放弃了这个为子项注册点击事件的监听方法,所有点击事件都有具体 View 去注册,我们可以按需为组件注册点击事件,不存在点击不到的组件。
1.2 基本用法
在xml布局中:(activity_launch.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:itemCount="5"
tools:listitem="@layout/data_item" />
</LinearLayout>
item布局:(data_item.xml)
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:padding="5dp"
android:textColor="@color/colorAccent"
tools:text="10" />
在Activity界面中:(LaunchActivity.kt)
class LaunchActivity : AppCompatActivity() {
private var listData = ArrayList<Int>()
private val adapter = MyAdapter(listData)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_launch)
initListener()
initRecycleView()
initData()
}
private fun initListener() {
adapter.setOnItemOnClickListener(object : MyAdapter.OnItemClickListener {
override fun onItemOnclick(view: View, position: Int) {
Toast.makeText(
this@LaunchActivity,
"你点了第" + position + "个,文本内容是:" + (view as TextView).text,
Toast.LENGTH_SHORT
).show()
}
})
}
private fun initRecycleView() {
//线型布局、显示垂直或水平滚动的列表项
rvList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
//显示网格中的item(项)
// rvList.layoutManager = GridLayoutManager(this, 5)
//显示交错的网格item(项目)
// rvList.layoutManager = StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL)
rvList.adapter = adapter
}
private fun initData() {
for (i in 0..100) {
listData.add(i)
}
}
}
自定义Adapter
class MyAdapter(private var mList: ArrayList<Int>) : RecyclerView.Adapter<MyAdapter.MyHolder>() {
private lateinit var mOnItemClickListener: OnItemClickListener
fun setOnItemOnClickListener(onItemClickListener: OnItemClickListener) {
this.mOnItemClickListener = onItemClickListener
}
///创建ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
return MyHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.data_item,
parent,
false
)
)
}
//返回个数
override fun getItemCount(): Int = mList.size
//填充视图
override fun onBindViewHolder(holder: MyHolder, position: Int) {
holder.itemView.tv.text = mList[position].toString()
//点击事件
mOnItemClickListener.let {
holder.itemView.setOnClickListener {
val position = holder.layoutPosition
mOnItemClickListener.onItemOnclick(holder.itemView, position)
}
}
}
class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
//实现自定义点击事件
interface OnItemClickListener {
fun onItemOnclick(view: View, position: Int)
}
}
实现效果:
上面就是recycleView
的基本用法 ,然后在实际使用中,需要不断完善。比较需要考虑下拉刷新、添加头文件或是尾部布局、或是多种类型的布局,还要考虑局部刷新、网络异常、数据异常时不同的布局显示等等。
下面总结一下项目中是如何使用的。
2.RecycleView中考虑多布局
2.1 版本1.0
在xml布局文件中:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvList"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
在Activity中:
class MutiTypeActivity : AppCompatActivity() {
private val lists = ArrayList<Category>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mutitype)
initData()
initRecycleView()
}
private fun initRecycleView() {
//显示网格中的item(项)
val layoutManager = GridLayoutManager(this, 3)
val adapter = CategoryAdapter(this, lists)
//根据不同类型返回不同的列数
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (adapter.getItemViewType(position)) {
Category.ONE_TYPE -> 3
else -> 1
}
}
}
rvList.layoutManager = layoutManager
rvList.adapter = adapter
adapter.setOnItemOnClickListener(object : CategoryAdapter.OnItemClickListener {
override fun onItemOnclick(view: View, position: Int, viewType: Int) {
if (viewType == Category.THIRD_TYPE) {
Toast.makeText(
this@MutiTypeActivity,
"你点了第" + position + "个,文本内容是:" + (view as TextView).text,
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
this@MutiTypeActivity,
"跳到更多界面,当前pos==$position",
Toast.LENGTH_SHORT
).show()
}
}
})
}
private fun initData() {
lists.add(Category("商机", 0))
lists.add(Category("车险", 1))
lists.add(Category("违章", 1))
lists.add(Category("年检", 1))
lists.add(Category("客户列表", 0))
lists.add(Category("有消费能力", 1))
lists.add(Category("朋友", 1))
lists.add(Category("同事", 1))
lists.add(Category("其他", 1))
lists.add(Category("品牌列表", 0))
lists.add(Category("别克", 1))
lists.add(Category("宝马", 1))
lists.add(Category("奔驰", 1))
lists.add(Category("标致", 1))
lists.add(Category("奔腾", 1))
lists.add(Category("奥迪", 1))
}
}
在自定义Adapter 中:
class CategoryAdapter(context: Context, lists: List<Category>?) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var lists = ArrayList<Category>()
private val layoutInflater: LayoutInflater
private lateinit var mOnItemClickListener: OnItemClickListener
fun setOnItemOnClickListener(listener: OnItemClickListener) {
this.mOnItemClickListener = listener
}
init {
this.lists = lists as ArrayList<Category>
this.layoutInflater = LayoutInflater.from(context)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == Category.ONE_TYPE) {
OneViewHolder(layoutInflater.inflate(R.layout.second_item, null, false))
} else {
ThirdViewHolder(layoutInflater.inflate(R.layout.thrid_item, null, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val viewType = getItemViewType(position)
when (viewType) {
Category.ONE_TYPE -> {
val oneViewHolder = holder as OneViewHolder
oneViewHolder.secondCategory.text = lists[position].categoryName
}
Category.THIRD_TYPE -> {
val thirdViewHolder = holder as ThirdViewHolder
thirdViewHolder.thirdCategory.text = lists[position].categoryName
}
}
//点击事件
mOnItemClickListener.let {
holder.itemView.setOnClickListener {
val pos = holder.layoutPosition
mOnItemClickListener.onItemOnclick(holder.itemView, pos, viewType)
}
}
}
override fun getItemCount(): Int {
return lists.size
}
override fun getItemViewType(position: Int): Int {
return lists[position].type
}
inner class OneViewHolder internal constructor(itemView: View) :
RecyclerView.ViewHolder(itemView) {
val secondCategory: TextView = itemView.findViewById(R.id.tvSecond)
}
inner class ThirdViewHolder internal constructor(itemView: View) :
RecyclerView.ViewHolder(itemView) {
val thirdCategory: TextView = itemView.findViewById(R.id.tvThird)
}
//实现自定义点击事件
interface OnItemClickListener {
fun onItemOnclick(view: View, position: Int, viewType: Int)
}
}
相关实体类:
class Category(val categoryName: String, val type: Int) {
companion object {
const val ONE_TYPE = 0
const val THIRD_TYPE = 1
}
}
实现效果如图所示:
注意:为了实现如上图的线性和网格
的混合视图效果,只需要一个 GridLayoutManager(其继承自 LinearLayoutManager)而关键的代码就是下图中的为 GridLayoutManager 设置 GridLayoutManager.SpanSizeLookup 监听器。用getSpanSize()方法来根据type返回不同的值,最后载显示不同的列数。 这样做体现了RecyclewView的灵活性,这个界面只需要用一个RecycleView中的多布局就可以实现。
2.2 版本2.0时代
比如我们写一个类似微博列表页面,这样的列表是十分复杂的:有纯文本的、带转发原文的、带图片的、带视频的、带文章的等等,甚至穿插一条可以横向滑动的好友推荐条目。不同的 item 类型众多,而且随着业务发展,还会更多。如果我们使用传统的开发方式,经常要做一些繁琐的工作,代码可能都堆积在一个 Adapter
中:我们需要覆写 RecyclerView.Adapter
的 getItemViewType
方法,罗列一些 type
整型常量,并且 ViewHolder
转型、绑定数据也比较麻烦。一旦产品需求有变,或者产品设计说需要增加一种新的 item 类型,我们需要去代码堆里找到原来的逻辑去修改,或找到正确的位置去增加代码。这些过程都比较繁琐,侵入较强,需要小心翼翼,以免改错影响到其他地方。
于是 出现了第三方的开源库:MultiType
支持功能如下:
- 使用 MultiTypeTemplates 插件自动生成代码
- 一个类型对应多个 ItemViewBinder
- 与 ItemViewBinder 通讯
- 使用断言,比传统 Adapter 更加易于调试
- 支持 Google AutoValue
- MultiType 与下拉刷新、加载更多、HeaderView、FooterView、Diff
- 实现 RecyclerView 嵌套横向 RecyclerView
- 实现线性布局和网格布局混排列表
实现步骤如下:
第一步:(开源库中的示例)新建一个类:Foo.kt 这个类的内容没有任何限制。
data class Foo(
val value: String
)
第二步:基于ItemViewBinder<T,VH:ViewHolder>
创建一个类:FooViewBinder.kt。
class FooViewBinder: ItemViewBinder<Foo, FooViewBinder.ViewHolder>() {
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
return ViewHolder(inflater.inflate(R.layout.item_foo, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, item: Foo) {
holder.fooView.text = item.value
Log.d("ItemViewBinder API", "position: ${getPosition(holder)}")
Log.d("ItemViewBinder API", "items: $adapterItems")
Log.d("ItemViewBinder API", "adapter: $adapter")
Log.d("More", "Context: ${holder.itemView.context}")
}
class ViewHolder(itemView : View): RecyclerView.ViewHolder(itemView) {
val fooView: TextView = itemView.findViewById(R.id.foo)
}
}
第三步:界面中实现,用register
注册用到的布局类型,并设置RecycleView相关属性。
class SampleActivity : AppCompatActivity() {
private val adapter = MultiTypeAdapter()
private val items = ArrayList<Any>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
val recyclerView = findViewById<RecyclerView>(R.id.list)
//注册不同的布局类型 注册和View的对应关系
adapter.register(TextItemViewBinder())
adapter.register(ImageItemViewBinder())
adapter.register(RichItemViewBinder())
recyclerView.adapter = adapter
val textItem = TextItem("world")
val imageItem = ImageItem(R.mipmap.ic_launcher)
val richItem = RichItem("小艾大人赛高", R.drawable.img_11)
for (i in 0..19) {
items.add(textItem)
items.add(imageItem)
items.add(richItem)
}
adapter.items = items
adapter.notifyDataSetChanged()
}
}
ItemViewBinder
源码解读:
abstract class ItemViewBinder<T, VH : ViewHolder> {
@Suppress("PropertyName")
internal var _adapter: MultiTypeAdapter? = null
/**
* Gets the associated [MultiTypeAdapter].
* @since v2.3.4
*/
val adapter: MultiTypeAdapter
get() {
if (_adapter == null) {
throw IllegalStateException(
"This $this has not been attached to MultiTypeAdapter yet. " +
"You should not call the method before registering the binder."
)
}
return _adapter!!
}
var adapterItems: List<Any>
get() = adapter.items
set(value) {
adapter.items = value
}
abstract fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): VH
abstract fun onBindViewHolder(holder: VH, item: T)
open fun onBindViewHolder(holder: VH, item: T, payloads: List<Any>) {
onBindViewHolder(holder, item)
}
fun getPosition(holder: ViewHolder): Int {
return holder.adapterPosition
}
open fun getItemId(item: T): Long = RecyclerView.NO_ID
open fun onViewRecycled(holder: VH) {}
open fun onFailedToRecycleView(holder: VH): Boolean {
return false
}
open fun onViewAttachedToWindow(holder: VH) {}
open fun onViewDetachedFromWindow(holder: VH) {}
ItemViewBinder
是个抽象类,其中 onCreateViewHolder
方法用于生产你的 Item View Holder, onBindViewHolder
用于绑定数据到 View
。 一般一个 ItemViewBinder
类在内存中只会有一个实例对象,MultiType 内部将复用这个 binder 对象来生产所有相关的 item views 和绑定数据。
该开源库中的onCreateViewHolder
和onBindViewHolder
方法沿用了RecycleView中的习惯,降低学习成本和理解难度。
实际项目中往往会加入上拉刷新或下拉加载更多。
纵观近几年的开发经验,下拉刷新控件经历了:PullToRefreshListView
、android-Ultra-Pull-To-Refresh,再到后来的官方组件SwipeRefreshLayout
技术在不停的迭代中,没有最好,选择适合自己的项目中的就行,保持一定的技术新鲜度最好。
2.2.1 上拉刷新实现
引入SwipeRefreshLayout
官方SwipeRefreshLayout简要说明
在竖直滑动时想要刷新页面可以用SwipeRefreshLayout来实现。它通过设置OnRefreshListener来监听界面的滑动从而实现刷新。也可以通过一些方法来设置SwipeRefreshLayout是否可以刷新。如:setRefreshing(true),展开刷新动画。setRefreshing(false),取消刷新动画。setEnable(false)下拉刷新将不可用。
使用这个布局要想达到刷新的目的,需要在这个布局里包裹可以滑动的子控件,如ListView等,并且只能有一个子控件。
主要方法:
-
isRefreshing()
判断当前的状态是否是刷新状态。 -
setColorSchemeResources(int… colorResIds)
设置下拉进度条的颜色主题,参数为可变参数,并且是资源id,最多设置四种不同的颜色,每转一圈就显示一种颜色。 -
setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener)
设置监听,需要重写onRefresh()方法,顶部下拉时会调用这个方法,在里面实现请求数据的逻辑,设置下拉进度条消失等等。 -
setProgressBackgroundColorSchemeResource(int colorRes)
设置下拉进度条的背景颜色,默认白色。 -
setRefreshing(boolean refreshing)
设置刷新状态,true表示正在刷新,false表示取消刷新。
在xml布局中:(截取实际项目中的运用示例)
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/dp_minus_10">
<cc.utimes.chejinjia.common.widget.layoutstatus.StatusLayout
android:id="@id/statusLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@id/rvList"
style="@style/CommonRecyclerViewStyle"
android:layout_height="match_parent"
android:background="@color/common_gray_F2" />
</cc.utimes.chejinjia.common.widget.layoutstatus.StatusLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
注意:swipeRefreshLayout
在布局中包裹最外层,中间的StatusLayout
为加载中的多种状态(加载中、加载成功、加载失败(服务器异常等失败)、数据为空)这里不展开叙述,后期看情况再详细补充说明。最下面的为列表布局。
这个布局为项目中有列表中的通用布局,在此基础上根据业务再添加头文件和尾部文件等。
在Activity中调用:(伪代码示例)
swipeRefreshLayout.setOnRefreshListener {
// 禁用上拉加载更多
adapter.setEnableLoadMore(false)
// 调用下拉刷新
refresh.onRefresh()
}
2.2.2 下拉加载更多实现
重写OnScrollListener
//加载更多
abstract class OnLoadMoreListener : RecyclerView.OnScrollListener() {
private var itemCount: Int = 0
private var lastPosition: Int = 0
private var lastItemCount: Int = 0
private var layoutManager: RecyclerView.LayoutManager? = null
//是否可以滑动
private var isCanScrolled = false
//加载更多
abstract fun onLoadMore()
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
// 拖拽或者惯性滑动时isScolled设置为true
isCanScrolled = (newState == SCROLL_STATE_DRAGGING || newState == SCROLL_STATE_SETTLING)
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
layoutManager = recyclerView?.layoutManager
if (recyclerView!!.layoutManager is LinearLayoutManager) {
itemCount = layoutManager!!.itemCount
lastPosition = (layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
}
if (lastItemCount != itemCount && lastPosition == itemCount - 1) {
lastItemCount = itemCount
this.onLoadMore()
}
}
}
注:上面这段代码还可以用于 判断界面中回到顶部的按钮显示逻辑判断。比如:在当前界面数据显示时不显示“回到顶部”按钮,当其他界面时再显示。
在Activity界面中调用:
rvList.addOnScrollListener(object : OnLoadMoreListener() {
override fun onLoadMore() {
//TODO:实现加载更多方法
}
})
在一款音视频App开发过程中,在订阅频道 我们用到了多布局。就采用了该开源库
根据用户是否登录,显示不同的界面,登录时显示推荐数据或订阅的数据,未登录则提示请登录界面。并用到了嵌套横向的RecycleView。由于业务较为复杂,代码实现与开源代码示例类似,此处不再展示代码。仅展示一下效果图。
2.3 版本3.0时代
在版本和技术的不断迭代中,我们用到了第三方的开源控件 BaseRecyclerViewAdapterHelper 截止目前有18.6 K star 貌似运用人数最多。
项目介绍:
BRVAH:Powerful and flexible RecyclerAdapter http://www.recyclerview.org/
简单来说: 就是很好很强大,能满足项目的开发需要,可替代之前的开源库。
具体的项目介绍 可点击前往
下面谈谈使用感受和实际运用。
2.3.1 精简代码
没用这个库之前的代码实现:
public class DefAdpater extends RecyclerView.Adapter<DefAdpater.ViewHolder> {
private final List<Status> sampleData = DataServer.getSampleData();
private Context mContext;
public DefAdpater(Context context) {
mContext = context;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.tweet, parent, false);
return new ViewHolder(item);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Status status = sampleData.get(position);
holder.name.setText(status.getUserName());
holder.text.setText(status.getText());
holder.date.setText(status.getCreatedAt());
Picasso.with(mContext).load(status.getUserAvatar()).into(holder.avatar);
holder.rt.setVisibility(status.isRetweet() ? View.VISIBLE : View.GONE);
}
@Override
public int getItemCount() {
return sampleData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private ImageView avatar;
private ImageView rt;
private TextView name;
private TextView date;
private TextView text;
public ViewHolder(View itemView) {
super(itemView);
text = (TextView) itemView.findViewById(R.id.tweetText);
name = (TextView) itemView.findViewById(R.id.tweetName);
date = (TextView) itemView.findViewById(R.id.tweetDate);
avatar = (ImageView) itemView.findViewById(R.id.tweetAvatar);
rt = (ImageView) itemView.findViewById(R.id.tweetRT);
}
}
}
用了这个库优化后,就可以这样实现,代码量减少三分之二。
public class QuickAdapter extends BaseQuickAdapter<Status> {
public QuickAdapter(Context context) {
super(context, R.layout.tweet, DataServer.getSampleData());
}
@Override
protected void convert(BaseAdapterHelper helper, Status item) {
helper.setText(R.id.tweetName, item.getUserName())
.setText(R.id.tweetText, item.getText())
.setText(R.id.tweetDate, item.getCreatedAt())
.setImageUrl(R.id.tweetAvatar, item.getUserAvatar())
.setVisible(R.id.tweetRT, item.isRetweet())
.linkify(R.id.tweetText);
}
}
优化思路:
找到重复部分代码,抽取到基类,非重复部分用抽象方法代替,具体让子类实现。
说了思路,看看BaseQuickAdapter源码是怎么写的:
基于版本 2.9.46分析
api 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46'
public abstract class BaseQuickAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K> {
@Override
public int getItemCount() {
int count;
if (getEmptyViewCount() == 1) {
count = 1;
if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
count++;
}
if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
count++;
}
} else {
count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
}
return count;
}
@Override
public K onCreateViewHolder(ViewGroup parent, int viewType) {
K baseViewHolder = null;
this.mContext = parent.getContext();
this.mLayoutInflater = LayoutInflater.from(mContext);
switch (viewType) {
case LOADING_VIEW:
baseViewHolder = getLoadingView(parent);
break;
case HEADER_VIEW:
baseViewHolder = createBaseViewHolder(mHeaderLayout);
break;
case EMPTY_VIEW:
baseViewHolder = createBaseViewHolder(mEmptyLayout);
break;
case FOOTER_VIEW:
baseViewHolder = createBaseViewHolder(mFooterLayout);
break;
default:
baseViewHolder = onCreateDefViewHolder(parent, viewType);
bindViewClickListener(baseViewHolder);
}
baseViewHolder.setAdapter(this);
return baseViewHolder;
}
//绑定监听事件
private void bindViewClickListener(final BaseViewHolder baseViewHolder) {
if (baseViewHolder == null) {
return;
}
final View view = baseViewHolder.itemView;
if (view == null) {
return;
}
if (getOnItemClickListener() != null) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setOnItemClick(v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount());
}
});
}
if (getOnItemLongClickListener() != null) {
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return setOnItemLongClick(v, baseViewHolder.getLayoutPosition() - getHeaderLayoutCount());
}
});
}
}
/**
* To bind different types of holder and solve different the bind events
*
* @param holder
* @param position
* @see #getDefItemViewType(int)
*/
@Override
public void onBindViewHolder(K holder, int position) {
//Add up fetch logic, almost like load more, but simpler.
autoUpFetch(position);
//Do not move position, need to change before LoadMoreView binding
autoLoadMore(position);
int viewType = holder.getItemViewType();
switch (viewType) {
case 0:
convert(holder, getItem(position - getHeaderLayoutCount()));
break;
case LOADING_VIEW:
mLoadMoreView.convert(holder);
break;
case HEADER_VIEW:
break;
case EMPTY_VIEW:
break;
case FOOTER_VIEW:
break;
default:
convert(holder, getItem(position - getHeaderLayoutCount()));
break;
}
}
/**
* Implement this method and use the helper to adapt the view to the given item.
*
* @param helper A fully initialized helper.
* @param item The item that needs to be displayed.
*/
protected abstract void convert(K helper, T item);
}
接下来再看看BaseViewHolder怎么写的:
public class BaseViewHolder extends RecyclerView.ViewHolder {
/**
* Views indexed with their IDs
*/
private final SparseArray<View> views;
public Set<Integer> getNestViews() {
return nestViews;
}
private final HashSet<Integer> nestViews;
private final LinkedHashSet<Integer> childClickViewIds;
private final LinkedHashSet<Integer> itemChildLongClickViewIds;
private BaseQuickAdapter adapter;
/**
* use itemView instead
*/
@Deprecated
public View convertView;
public BaseViewHolder(final View view) {
super(view);
this.views = new SparseArray<>();
this.childClickViewIds = new LinkedHashSet<>();
this.itemChildLongClickViewIds = new LinkedHashSet<>();
this.nestViews = new HashSet<>();
convertView = view;
}
//下面是各组件绑定赋值
public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
TextView view = getView(viewId);
view.setText(value);
return this;
}
public BaseViewHolder setText(@IdRes int viewId, @StringRes int strId) {
TextView view = getView(viewId);
view.setText(strId);
return this;
}
public BaseViewHolder setImageResource(@IdRes int viewId, @DrawableRes int imageResId) {
ImageView view = getView(viewId);
view.setImageResource(imageResId);
return this;
}
public BaseViewHolder setBackgroundColor(@IdRes int viewId, @ColorInt int color) {
View view = getView(viewId);
view.setBackgroundColor(color);
return this;
}
public BaseViewHolder setGone(@IdRes int viewId, boolean visible) {
View view = getView(viewId);
view.setVisibility(visible ? View.VISIBLE : View.GONE);
return this;
}
public BaseViewHolder setVisible(@IdRes int viewId, boolean visible) {
View view = getView(viewId);
view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
return this;
}
public BaseViewHolder setProgress(@IdRes int viewId, int progress, int max) {
ProgressBar view = getView(viewId);
view.setMax(max);
view.setProgress(progress);
return this;
}
/**
* add childView id
*
* @param viewIds add the child views id can support childview click
* @return if you use adapter bind listener
* @link {(adapter.setOnItemChildClickListener(listener))}
* <p>
* or if you can use recyclerView.addOnItemTouch(listerer) wo also support this menthod
*/
public BaseViewHolder addOnClickListener(@IdRes final int ...viewIds) {
for (int viewId : viewIds) {
childClickViewIds.add(viewId);
final View view = getView(viewId);
if (view != null) {
if (!view.isClickable()) {
view.setClickable(true);
}
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (adapter.getOnItemChildClickListener() != null) {
adapter.getOnItemChildClickListener().onItemChildClick(adapter, v, getClickPosition());
}
}
});
}
}
return this;
}
/**
* Sets the adapter of a adapter view.
*
* @param adapter The adapter;
* @return The BaseViewHolder for chaining.
*/
protected BaseViewHolder setAdapter(BaseQuickAdapter adapter) {
this.adapter = adapter;
return this;
}
@SuppressWarnings("unchecked")
public <T extends View> T getView(@IdRes int viewId) {
View view = views.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}
//省略其他点击事件和组件的绑定操作
}
- 利用SparseArray来做缓存,把常用方法全部写好,从而避免冗余代码。
- 用了 建造者模式来进行组件的绑定操作,调用时链式调用,方便扩展。
2.3.2 扩展功能
原生的RecycleView是没有点击事件的,需要我们自己实现。前面的代码已经做了很好的优化封装,我们只需要进行扩展该功能即可,方法也很简单,原理类似是上面介绍的基本用法中。不同的是我们的创建时机不同。
网上有很多写法都是在
onBindViewHolder
里面写,功能是可以实现但是会导致频繁创建,应该在onCreateViewHolder()
中每次为新建的 View 设置一次就行了。
如果你第一次了解如何实现点击事件,很有可能在网上找到的代码类似上面介绍的基本用法,在``onBindViewHolder`实现,可能不会考虑还有没有其他更好的方式,随着技术的迭代,有时间优化时我们去深入源码研究或参考别人的做法。我们就会进一步优化。
以上的分析大多参考网上的分析思路,说了这么多,我们在项目中是如何使用的呢?
3. 实际项目中的运用
3.1 简单界面运用
技术调研后,我们在项目进行运用,运用时进行简单必要的封装,方便随时替换第三的方框架。
abstract class BaseAdapter<T> : BaseQuickAdapter<T, BaseViewHolder> {
constructor() : super(0, null)
constructor(@LayoutRes layoutResId: Int) : super(layoutResId, null)
constructor(data: List<T>?) : super(0, data)
constructor(@LayoutRes layoutResId: Int, data: List<T>?) : super(layoutResId, data)
override fun setNewData(data: List<T>?) {
super.setNewData(data)
if (data != null && data is ObservableArrayList) {
data.setOnListChangedListener(object : ObservableArrayList.OnListChangedListener<ObservableArrayList<T>> {
override fun onChanged(sender: ObservableArrayList<T>) {
notifyDataSetChanged()
}
override fun onItemRangeChanged(sender: ObservableArrayList<T>, positionStart: Int, itemCount: Int) {
notifyItemRangeChanged(positionStart + headerLayoutCount, itemCount)
}
override fun onItemRangeInserted(sender: ObservableArrayList<T>, positionStart: Int, itemCount: Int) {
notifyItemRangeInserted(positionStart + headerLayoutCount, itemCount)
}
override fun onItemMoved(sender: ObservableArrayList<T>, fromPosition: Int, toPosition: Int) {
notifyItemMoved(fromPosition + headerLayoutCount, toPosition + headerLayoutCount)
}
override fun onItemRangeRemoved(sender: ObservableArrayList<T>, positionStart: Int, itemCount: Int) {
notifyItemRangeRemoved(positionStart + headerLayoutCount, itemCount)
}
})
}
}
//这里默认第三方框架的方法
override fun convert(helper: BaseViewHolder, item: T) {}
/**
* 优化点击事件,方法写在这
* 重写了item点击事件,加上了防止重复点击
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
val baseViewHolder = super.onCreateViewHolder(parent, viewType)
if (viewType != LOADING_VIEW && viewType != HEADER_VIEW && viewType != EMPTY_VIEW && viewType != FOOTER_VIEW) {
if (onItemClickListener != null) {
baseViewHolder.itemView.onClick {
onItemClickListener.onItemClick(
this,
baseViewHolder.itemView,
baseViewHolder.layoutPosition - headerLayoutCount
)
}
}
}
return baseViewHolder
}
//考虑局部刷新的实现
override fun onBindViewHolder(holder: BaseViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
handleLocalRefresh(holder, mData[holder.layoutPosition - headerLayoutCount], payloads)
}
}
protected open fun handleLocalRefresh(helper: BaseViewHolder, item: T, payloads: MutableList<Any>) {}
}
其中的OnListChangedListener接口实现如下:
interface OnListChangedListener<T : ObservableArrayList<*>> {
/**
* Called whenever a change of unknown type has occurred, such as the entire list being
* set to new values.
*
* @param sender The changing list.
*/
fun onChanged(sender: T)
/**
* Called whenever one or more items in the list have changed.
* @param sender The changing list.
* @param positionStart The starting index that has changed.
* @param itemCount The number of items that have changed.
*/
fun onItemRangeChanged(sender: T, positionStart: Int, itemCount: Int)
/**
* Called whenever items have been inserted into the list.
* @param sender The changing list.
* @param positionStart The insertion index
* @param itemCount The number of items that have been inserted
*/
fun onItemRangeInserted(sender: T, positionStart: Int, itemCount: Int)
/**
* Called whenever items in the list have been moved.
* @param sender The changing list.
* @param fromPosition The position from which the items were moved
* @param toPosition The destination position of the items
*/
fun onItemMoved(sender: T, fromPosition: Int, toPosition: Int)
/**
* Called whenever items in the list have been deleted.
* @param sender The changing list.
* @param positionStart The starting index of the deleted items.
* @param itemCount The number of items removed.
*/
fun onItemRangeRemoved(sender: T, positionStart: Int, itemCount: Int)
}
上面的封装可以做成通用的库文件。
在项目中还可以进一步封装 (根据情况可选):
open class MyBaseAdapter<T> : BaseAdapter<T> {
constructor() : super(0, null)
constructor(@LayoutRes layoutResId: Int) : super(layoutResId, null)
constructor(@Nullable data: List<T>) : super(0, data)
constructor(@LayoutRes layoutResId: Int, @Nullable data: List<T>) : super(layoutResId, data)
fun setArrayListLiveData(lifecycleOwner: LifecycleOwner, liveData: ArrayListLiveData<T>) {
setNewData(liveData.rawList)
ListChangedObserver(lifecycleOwner, this, liveData)
}
}
自定义adapter:
class VehicleBrandAdapter : MyBaseAdapter<VehicleBrandBean>(R.layout.recycle_item_vehicle_brand_header) {
override fun convert(helper: BaseViewHolder, item: VehicleBrandBean) {
super.convert(helper, item)
helper.setText(R.id.tvBrandName, item.brandName)
//注册点击事件
helper.addOnClickListener(R.id.ivDelete)
}
}
在界面上调用 :
//添加adapter中的子控件事件
vehicleBrandAdapter.setOnItemChildClickListener { _, view, position ->
if (view.id == R.id.ivDelete) {
//do what you want to do
}
}
3.2 复杂布局运用
3.2.1 嵌套布局
界面中RecycleView中嵌套一个横向的RecycleView。先看看我们实现的效果图:
在布局文件中:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:orientation="vertical">
<!--标题-->
<TextView
android:id="@+id/titleLayout"
android:layout_width="match_parent"
android:layout_height="44"
app:layout_constraintTop_toTopOf="parent"
android:text="选择4s店"/>
<!--选择日期-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/common_bg_white"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCalendar"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginStart="15dp"
android:layout_marginTop="@dimen/dp_5"
android:orientation="horizontal"
android:layout_marginEnd="@dimen/dp_15"
tools:layoutManager=" LinearLayoutManager"
android:layout_marginBottom="@dimen/dp_5"
tools:listitem="@layout/shop4s_maintenance_item_calendar" />
</LinearLayout>
<!-- 门店地址和时间 item-->
<cc.xxx.widget.status.StatusLayout
android:id="@+id/statusLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycleList"
style="@style/CommonRecyclerViewStyle"
android:background="@color/common_bg_white"
tools:listitem="@layout/shop4s_maintenance_item_store" />
</cc.xxx.widget.status.StatusLayout>
</LinearLayout>
预览效果如图所示:
Tips: Android Studio 预览RecyclerView 小技巧
加上tools命名空间后使用以下属性可以得到直观的预览:
tools:layoutManager="GridLayoutManager"
tools:orientation="horizontal"
tools:listitem="4"
tools:spanCount ="2"
其中的门店地址和时间 item
布局中又嵌套了一个RecycleView用来显示时间段的选择。(代码略)
在Adapter中:有两个,一个是最外层的Adapter,取名为ChooseStoreItemAdapter
,
另一个是子项中的Adapter,取名为 ChooseTimeSubItemAdapter
ChooseStoreItemAdapter
伪代码如下:
class ChooseStoreItemAdapter : MyBaseAdapter<ListStoreEntity>(R.layout.shop4s_item_store) {
override fun convert(helper: BaseViewHolder, item: ListStoreEntity) {
super.convert(helper, item)}
helper.setText(R.id.tvDescription, item.storeNickName)
helper.setText(R.id.tvAddress, item.address)
//时间列表处理 处理子项recycleView
handleRecyclerView(helper.getView(R.id.recycleListTimeItem), item.timePrices,item)
}
private fun handleRecyclerView(recycle: RecyclerView, list: ArrayList<ListStoreEntity.TimePricesEntity>,storeEntity: ListStoreEntity) {
if (recycle.layoutManager == null) {
recycle.layoutManager = GridLayoutManager(recycle.context, 3)
//添加间距,必须放到这里,防止刷新时界面错位 代码略
}
var adapter = recycle.adapter
if (adapter == null) {
adapter = ChooseTimeSubItemAdapter(storeEntity)
recycle.adapter = adapter
}
(adapter as ChooseTimeSubItemAdapter).setNewData(list)
}
//添加点击事件 代码略
}
ChooseTimeSubItemAdapter
伪代码如下:
class ChooseTimeSubItemAdapter(storeEntity: ListStoreEntity) :
MyBaseAdapter<TimePricesEntity>(R.layout.shop4s_maintenance_item_time) {
override fun convert(helper: BaseViewHolder, item: TimePricesEntity) {
super.convert(helper, item)
//组件赋值
helper.setText(R.id.tvPrice, item.salePrice)
//……
}
}
最后在主界面中调用:
private val mAdapterStore = ChooseStoreItemAdapter()
private fun initRecycleView() {
//初始化店铺
recycleList.layoutManager = LinearLayoutManager(this)
mAdapterStore.bindToRecyclerView(recycleList)
}
private fun handleData(data: StoreEntity) {
//网络获取数据后 绑定值
mAdapterStore.setNewData(data.list)
//……
}
}
//点击事件处理……
3.2.2 多布局
优化前:
public class MultipleItemAdapter extends BaseQuickAdapter<String> {
private final int TEXT_TYPE = 1;
private int mTextLayoutResId;
public MultipleItemAdapter(Context context, List data, int... layoutResId) {
super(context, layoutResId[0], data);
mTextLayoutResId = layoutResId[1];
}
@Override
protected int getDefItemViewType(int position) {
if (position % 2 == 0)
return TEXT_TYPE;
return super.getDefItemViewType(position);
}
@Override
protected BaseViewHolder onCreateDefViewHolder(ViewGroup parent, int viewType) {
if (viewType == TEXT_TYPE)
return new TextViewHolder(getItemView(mTextLayoutResId, parent));
return super.onCreateDefViewHolder(parent, viewType);
}
@Override
protected void onBindDefViewHolder(BaseViewHolder holder, String item) {
if (holder instanceof TextViewHolder)
holder.setText(R.id.tv, item);
}
@Override
protected void convert(BaseViewHolder helper, String item) {
helper.setImageUrl(R.id.iv, item);
}
public class TextViewHolder extends BaseViewHolder {
public TextViewHolder(View itemView) {
super(itemView.getContext(), itemView);
}
}
}
优化后:
public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem> {
public MultipleItemQuickAdapter(Context context, List data) {
super(context, data);
addItmeType(MultipleItem.TEXT, R.layout.text_view);
addItmeType(MultipleItem.IMG, R.layout.image_view);
}
@Override
protected void convert(BaseViewHolder helper, MultipleItem item) {
switch (helper.getItemViewType()) {
case MultipleItem.TEXT:
helper.setImageUrl(R.id.tv, item.getContent());
break;
case MultipleItem.IMG:
helper.setImageUrl(R.id.iv, item.getContent());
break;
}
}
}
原理分析:
基本用法中,多个不同类型的布局一定会用到getItemViewType
和onCreateViewHolder
优化后的代码中代码中却省略了,如何做到的?
优化前:getItemViewType
@Override
protected int getDefItemViewType(int position) {
if (position % 2 == 0)
return TEXT_TYPE;
return super.getDefItemViewType(position);
}
在BaseMultiItemQuickAdapter
中:
@Override
protected int getDefItemViewType(int position) {
T item = mData.get(position);
if (item != null) {
return item.getItemType();
}
return DEFAULT_VIEW_TYPE;
}
这样做,在填充数据的时候就把view type给添加进去了。
优化前:onCreateViewHolder
@Override
protected BaseViewHolder onCreateDefViewHolder(ViewGroup parent, int viewType) {
if (viewType == TEXT_TYPE)
return new TextViewHolder(getItemView(mTextLayoutResId, parent));
return super.onCreateDefViewHolder(parent, viewType);
}
优化后:
public abstract class BaseMultiItemQuickAdapter<T extends MultiItemEntity, K extends BaseViewHolder> extends BaseQuickAdapter<T, K> {
/**
* layouts indexed with their types
*/
private SparseIntArray layouts;
private static final int DEFAULT_VIEW_TYPE = -0xff;
public static final int TYPE_NOT_FOUND = -404;
@Override
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
return createBaseViewHolder(parent, getLayoutId(viewType));
}
private int getLayoutId(int viewType) {
return layouts.get(viewType, TYPE_NOT_FOUND);
}
protected void addItemType(int type, @LayoutRes int layoutResId) {
if (layouts == null) {
layouts = new SparseIntArray();
}
layouts.put(type, layoutResId);
}
}
原理分析:addItemType
中以type为key , layoutResId为 value 以 key-value键值对的形式存储到 SparseIntArray中,在onCreateDefViewHolder
中再根据viewType来获取相应的 layoutResId。
在项目中消息推送模块我们用到了多布局。
实现效果如下图所示:
实现伪代码如下:
class DrivingMulDialogueAdapter : MyBaseMultiItemAdapter<MessageListEntity.MessageEntity>() {
companion object {
//立即添加
const val ITEM_TYPE_ADD_NOW = 1
//查询分数
const val ITEM_TYPE_QUERY_SCORE = 2
//以上一个选择 其他两个选择
//我已换证
const val ITEM_TYPE_HAVE_CHANGED_DRIVING_LICENSE = 3
}
//添加不同类型,并关联对应的布局文件
init {
addItemType(
ITEM_TYPE_ADD_NOW,
R.layout.driving_license_dialogue_recycle_item_add
)
addItemType(
ITEM_TYPE_QUERY_SCORE,
R.layout.driving_license_dialogue_recycle_item_add
)
addItemType(
ITEM_TYPE_HAVE_CHANGED_DRIVING_LICENSE,
R.layout.driving_license_dialogue_recycle_item_have_changed
)
}
//不同类型的布局进行不同业务的处理
override fun convert(helper: BaseViewHolder, item: MessageEntity) {
super.convert(helper, item)
helper.setText(R.id.tvContent, item.dialogContent)
helper.setText(R.id.tvDialogTitle, item.dialogueTitle)
helper.setText(R.id.tvTime, TimeTool.getFullTime(item.addTime))
when (item.dialogueType) {
//立即添加
ITEM_TYPE_ADD_NOW -> {
helper.setText(R.id.tvOneSelected, ResourcesUtil.getString(R.string.driving_license_add))
helper.addOnClickListener(R.id.tvOneSelected)
}
//查询分数
ITEM_TYPE_QUERY_SCORE -> {
helper.setText(R.id.tvOneSelected, ResourcesUtil.getString(R.string.driving_license_query_score))
helper.addOnClickListener(R.id.tvOneSelected)
}
//我已换证
ITEM_TYPE_HAVE_CHANGED_DRIVING_LICENSE, ITEM_TYPE_TO_TIME-> {
helper.setText(R.id.tvFirstSelected, ResourcesUtil.getString(R.string.driving_license_changed))
helper.setText(R.id.tvSecondSelected, ResourcesUtil.getString(R.string.driving_license_look))
helper.addOnClickListener(R.id.tvFirstSelected,R.id.tvSecondSelected)
}
//其他类型 省略
}
}
}
在Activity中调用:
override fun initListener() {
super.initListener()
//监听 子控件事件,同时判断消息的类型,做不同处理
adapter.setOnItemChildClickListener { _, view, position ->
val item = adapter.data[position]
when (view.id) {
R.id.tvOneSelected -> {
if (item.dialogueType == ITEM_TYPE_ADD_NOW) {
// TODO:
} else {
// TODO:
}
}
R.id.tvFirstSelected -> {
// TODO:
}
}
3.3 如何集成下拉刷新和上拉加载更多
BaseRecyclerViewAdapterHelper
中已经集成了 上拉加载更多功能,下拉加载刷新可以直接用Google官方的swipeRefreshLayout
,也可以用其他三方的组件。
在项目中使用时 可以封装一个工具类,如这样的
/**
* 处理刷新、加载完成、加载失败、自定义加载状态布局文件等等。
**/
class RefreshAndLoadMoreUtils(
private val rvList: RecyclerView,
private val swipeRefreshLayout: SwipeRefreshLayout,
private val adapter: BaseQuickAdapter<*, *>,
private val refresh: IRefreshAndLoadMore,
private val loadMoreView: MyLoadMoreView
) {
init {
initListener()
}
private fun initListener() {
// 下拉刷新监听
swipeRefreshLayout.setOnRefreshListener {
// 调用下拉刷新
refresh()
}
// 设置加载更多时显示的布局
adapter.setLoadMoreView(loadMoreView)
// 加载更多监听
adapter.disableLoadMoreIfNotFullPage(recycleList)
adapter.setOnLoadMoreListener({
// 禁用下拉刷新
swipeRefreshLayout.isEnabled = false
// 调用上拉加载更多
loadMore()
}, recycleList)
}
private fun refresh() {
}
}
同于获取列表的布局比较通用,可以抽取出来进一步封装。封装一个通用的布局、通用的下拉刷新功能、上拉加载更多等通用的功能,并重写不同界面中相同方法实现接口(如IRecycler
)。然后在不同的界面中直接继承通用的CommmonRecycleActivity
处理逻辑即可。此处不在展示详细代码,有不清楚的朋友,可以留言讨论。
interface IRecycler<D : AbsLoadList<T>, T> { //泛型类中 D标示 界面的类,T 标示数据
//请示网络接口地址Url实现
//请求网络接口的参数params实现
//不同界面中的自定义适配器Adapter实现
// ……
}
abstract class AbsLoadList<T> {
// 滑动方向:1:向上滑动(加载下一页数据);2:向下滑动(加载上一页数据)
var slide = 0
// 页首参数, 向下滑动时使用
var top = ""
// 页尾参数, 向上滑动时使用
var bottom = ""
// 是否有下一页数据
var hasMore = false
// 数据列表
var list = ArrayList<T>()
}
3.4 其他场景运用
- 左右滑动删除
- 滑动 冲突
- 订单明细中显示不全如何解决
- TV端的运用
后期有时间再补充。
4. 总结
本文 总结了RecycleView
的使用情况,不同时期的项目 用到了不同的技术,即分析了基本用法,又分析了优化后的版本迭代情况,最后结合项目实际使用给出了几种运用场景。在介绍实际使用时,重在总结当时的使用思路,具体实现得根据不同业务来完善,基础运用+思路
就可以实现开发中常用的业务功能。在总结时想要展开的知识点太多,无法一一展开。后期会不断完善,如有不对的地方,欢迎大家指正。To be continuted……
参考资料:
1.Android 解决切换Tab后RecycleView/ListView自动回滚至顶部条目的Bug
3.Android:RecyclerView 的使用,有这一篇就够了