前言
RecycleView的問世,替代了ListViewt和GridView,性能得到提升。同時也出現了許多優秀的第三方開源庫。本文總結了在實現項目中是如何運用RecycleView的場景,以及總結了項目中使用時的一些心得,希望對你有所幫助。
文章目錄
1.RecycleView
官方對RecyclerView的描述:
A flexible view for providing a limited window into a large data set.
1.1 使用RecycleView的優缺點
優點:
- 內部實現了回收機制,無需我們考慮View的複用情況
- 支持不同方向,不同排版模式,實現多種展現數據的形式,涵蓋了ListView,GridView,瀑布流等數據表現的形式
- RecycleView強制封裝ViewHolder
- 可設置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.xx.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.xx.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 的使用,有這一篇就夠了