一個全新的RecyclerView Adapter框架源碼開源

RecyclerViewAdapter

從新定義RecyclerView Adapter的封裝,追求既簡單又實用,結合Kotlin的高級特性,優化代碼書寫方式,真正作到高內聚低耦合android

開源地址

Github RecyclerViewAdaptergit

框架設計核心思想

  • 摒棄notifyDataSetChanged無腦操做,利用ObservableList自動匹配數據,並實現局部刷新
  • 真正通用的ViewHolder抽象,今後只關注Layout XML佈局
  • 真正通用的Adapter,今後再也不寫Adapter子類
  • ItemViewType自動匹配對象Layout XML,再也不關心它的細節
  • 科學的分包處理,真正作到框架的各取所需(通常列表只需引用Adapter-core核心庫便可)
  • 擴展Anko Layout版本,體驗Anko Layout的魅力,並能獲取高於XML加載至少3倍以上的效率提高
  • 像堆積木同樣,將頁面的每一個模塊都作到了複用,跟Fragment能夠說再見

設計圖 github

Image

規劃

  • 核心庫Core的完善
  • Anko擴展
  • FlexboxLayout 擴展
  • SortedList 擴展
  • paging 3 擴展
  • DiffUtil 擴展
  • DataBinding 擴展
  • 等等.. 將來有好的想法繼續擴展

如今有ArrayListAdapter,AnkoListAdapter,將來會有SortedListAdapter,PagingAdapter,爲啥這麼設計呢?bash

  • 第1、原則上我根本不會去設計一款超級龐大的Adapter支持各類功能,單一職責須要貫穿始終
  • 第2、保持各個Lib的特色,能夠根據業務的不一樣自由選擇,最大程度的減小包體積,真正體現了沒有最好最全,只有最合適的思想
  • 第3、保持可動態擴展的特性,你理解了原理,你也能夠根據本身的須要定製,將來官方提供MergeAdapter組合使用方式,以及咱們將來作一個WrapAdapter,你會發現更多組合額的可能性。

少了點什麼?

你是否是以爲少了點什麼功能?空佈局,上拉加載,下拉加載,拖動,頭佈局,腳佈局,展開摺疊,分割線,動畫等等,這些後期都會帶着大家去實現,上面的規劃更偏向於底層框架的封裝,而這些功能更偏向業務組件,方向不一樣,不要着急哦,帶着大家一步步完善,來體驗封裝Adapter中的樂趣app

庫大小

名字 release aar size 其餘
Core 25kb 核心庫目前包含ArrayListAdapter的實現
Anko 12kb anko擴展庫包含AnkoListAdapter
Sorted 0kb 待實現
.. .. 待實現

環境須要

  • Kotlin
  • JAVA
  • AndroidX 抱歉目前按照最新的AndroidX適配的,若有其餘須要請私聊我。

怎麼用

ArrayListAdapter

step1

建立xml佈局,和以前同樣的佈局方式框架

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp"
    android:layout_margin="5dp">

    <LinearLayout
        android:background="?attr/selectableItemBackground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/colorPrimary"
            android:textSize="22sp" />

        <TextView
            android:id="@+id/tv_subTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/colorAccent"
            android:textSize="18sp" />

    </LinearLayout>

</androidx.cardview.widget.CardView>
複製代碼

step2

定義ViewModel及Model,能夠看到,邏輯簡單明瞭,刷新本身的時候只須要更新Model,並reBindView便可,刷新別人的話,須要經過Adapter去更新,複雜頁面只須要再新建一個ArrayItemViewModel的子類便可,並建立一個新的XML佈局,從這裏的代碼能夠看出,一樣一個ViewModel將來能夠複用不少XML佈局,徹底作到了ViewModel、View、Model三個角色的任意複用。爲業務多樣化提供最底層的支持。dom

/**
 * Model
 */
data class ModelTest(var title: String, var subTitle: String)

/**
 * ViewModel
 */
class ArrayViewModelTest : ArrayItemViewModel<ModelTest>() {

    var index = 0

    override fun onBindView(adapter: ArrayListAdapter?) {
        viewHolder.itemView.apply {
            tv_title.text = model.title
            tv_subTitle.text = model.subTitle
            cardItem.setOnClickListener {
                model.title = "${index++}"
                reBindView()
            }
        }
    }

    override fun getLayoutRes() = R.layout.item_test

}
複製代碼

複用邏輯以下圖: ide

step3

Activity 中增刪改,增刪改都是對ViewModel層的操做,簡單實用。佈局

/**
 * Activity
 */
class ArrayListActivity : AppCompatActivity() {

    private val mArrayListAdapter by lazy {
        ArrayListAdapter()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_array_list)
        rv_list.bindListAdapter(mArrayListAdapter)


        // 新增一個
        new_add.setText("新增").setOnClickListener {
            mArrayListAdapter.add(ArrayViewModelTest().apply {
                model = ModelTest("標題", "副標題")
            })
        }


        // 刪除第一個
        delete.setText("刪除").setOnClickListener {
            if (mArrayListAdapter.size > 0)
                mArrayListAdapter.removeAt(0)
            else
                toast("請添加新用例後再試")
        }


        // 隨機更新
        var updateSize = 0
        update.setText("更新").setOnClickListener {
            updateSize++
            if (mArrayListAdapter.size > 0) {
                val randomInt = Random.nextInt(0, mArrayListAdapter.size)
                mArrayListAdapter.set(randomInt, ArrayViewModelTest().apply {
                    model = ModelTest("標題$updateSize", "副標題$updateSize")
                })
            } else {
                toast("請添加新用例後再試")
            }

        }

    }
}
複製代碼

AnkoListAdapter

step1

定義AnkoLayoutpost

/**
 * AnkoItemView
 */
class AnkoItemView(val itemClick: () -> Unit) : AnkoComponent<ViewGroup> {

    var tvTitle: TextView? = null
    var tvSubTitle: TextView? = null
    var view: View? = null

    @SuppressLint("ResourceType")
    override fun createView(ui: AnkoContext<ViewGroup>) = with(ui) {

        cardView {

            layoutParams = FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT
            ).apply {
                margin = dip(5)
            }

            verticalLayout {

                setOnClickListener {
                    itemClick()
                }

                val typedValue = TypedValue()
                context.theme
                    .resolveAttribute(android.R.attr.selectableItemBackground, typedValue, true)
                val attribute = intArrayOf(android.R.attr.selectableItemBackground)
                val typedArray =
                    context.theme.obtainStyledAttributes(typedValue.resourceId, attribute)

                background = typedArray.getDrawable(0)

                layoutParams = FrameLayout.LayoutParams(
                    FrameLayout.LayoutParams.MATCH_PARENT,
                    FrameLayout.LayoutParams.WRAP_CONTENT
                ).apply {
                    padding = dip(10)
                }

                tvTitle = textView {
                    textSize = px2dip(60)
                    textColorResource = R.color.colorPrimary
                }.lparams(matchParent, wrapContent)

                tvSubTitle = textView {
                    textSize = px2dip(45)
                    textColorResource = R.color.colorAccent
                }.lparams(matchParent, wrapContent)

            }
        }

    }
}

複製代碼

step2

定義ViewModel,Model,這裏有個細節須要說一下,在ArrayListAdapter的例子中我是在onBindView裏設置的點擊事件,這樣就有個壞處就是致使每次從新onBindView都會致使設置點擊事件,這樣其實很很差,因此在Anko版本里我作了優化,在onCreateView處理點擊事件,這裏就作到了設置一次。

/**
 * Model
 */
data class ModelTest(var title: String, var subTitle: String)

/**
 * ViewModel
 */
class AnkoViewModelTest : AnkoItemViewModel<ModelTest, AnkoItemView>() {

    var index = 0

    override fun onBindView(adapter: AnkoListAdapter) {
        ankoView.tvTitle?.text = model.title
        ankoView.tvSubTitle?.text = model.subTitle
    }

    override fun onCreateView(): AnkoItemView {
        return AnkoItemView{
            model.title = "${index++}"
            reBindView()
        }
    }

}
複製代碼

step3

Activity 中增刪改

/**
 * Activity
 */
class AnkoLayoutActivity : AppCompatActivity() {

    private val mAnkoListAdapter by lazy {
        AnkoListAdapter()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        AnkoLayoutComponent(mAnkoListAdapter).setContentView(this).apply {

            // 新增一個
            new_add.setText("新增").setOnClickListener {
                mAnkoListAdapter.add(AnkoViewModelTest().apply {
                    model = ModelTest("標題", "副標題")
                })
            }


            // 刪除第一個
            delete.setText("刪除").setOnClickListener {
                if (mAnkoListAdapter.size > 0)
                    mAnkoListAdapter.removeAt(0)
                else
                    toast("請添加新用例後再試")
            }


            // 隨機更新
            var updateSize = 0
            update.setText("更新").setOnClickListener {
                updateSize++
                if (mAnkoListAdapter.size > 0) {
                    val randomInt = Random.nextInt(0, mAnkoListAdapter.size)
                    mAnkoListAdapter.set(randomInt, mAnkoListAdapter.getItem(randomInt).apply {
                        model.also {
                            it as ModelTest
                            it.title = "$updateSize"
                        }
                    })
                } else {
                    toast("請添加新用例後再試")
                }
            }

        }
    }

}

/**
 * View
 *
 */
class AnkoLayoutComponent(private val ankoListAdapter: AnkoListAdapter) : AnkoComponent<AnkoLayoutActivity> {

    override fun createView(ui: AnkoContext<AnkoLayoutActivity>) = with(ui) {

        verticalLayout {

            recyclerView {
                bindListAdapter(ankoListAdapter)
            }.lparams(matchParent) {
                weight = 1F
            }
            // Anko 兼容 xml佈局的加載
            include<View>(R.layout.include_button_bottom)

        }

    }

}
複製代碼

上一篇博客

一個資深的Android是否是應該學會本身作一個超級的RecyclerView.Adapter

開發者

相關文章
相關標籤/搜索