• <rp id="vcwyv"></rp>

      <b id="vcwyv"></b>
      <tt id="vcwyv"></tt>

    1. Kotlin實戰案例:帶你實現RecyclerView分頁查詢功能(仿照主流電商APP,可切換列表和 ... [復制鏈接]

      2019-12-5 09:39
      byebye0521 閱讀:510 評論:0 贊:0
      Tag:  kotlin實戰

      Kotlin實戰案例:帶你實現RecyclerView分頁查詢功能(仿照主流電商APP,可切換列表和網格效果)

      演示:分頁查詢數據,切換列表樣式(列表、網格)

      演示:網絡異常時分頁查詢

      隨著Kotlin的推廣,一些國內公司的安卓項目開發,已經從Java完全切成Kotlin了。雖然Kotlin在各類編程語言中的排名比較靠后(據TIOBE發布了 19 年 8 月份的編程語言排行榜,Kotlin竟然排名45位),但是作為安卓開發者,掌握該語言,卻已是大勢所趨了。

      Kotlin的基礎用法,整體還是比較簡單的,網上已經有很多文章了,大家熟悉下即可。

      案例需求

      此次案例,之所以選擇分頁列表,主要是因為該功能通用性強,涵蓋的技術點也較多,對開發者熟悉Kotlin幫助性較大。

      案例的主要需求如下( 參考主流電商APP實現 ):
      1、列表支持手勢滑動分頁查詢(滑動到底部時,自動查詢下一頁,直到沒有更多數據)
      2、可切換列表樣式和網格樣式
      3、切換樣式后,數據位置保持不變(如當前在第100條位置,切換樣式后,位置不變)
      4、footview根據查詢狀態,顯示不同內容:

      a、數據加載中... (正在查詢數據時顯示)
      b、沒有更多數據了 (查詢成功,但是已沒有可返回的數據了)
      c、出錯了,點擊重試!!(查詢時出現異常,可能是網絡,也可能是其他原因)

      5、當查詢出錯時,再次點擊footview,可重新發起請求(例如:網絡異常了)
      6、當切換網格樣式時,footview應獨占一行

      設計

      雖然是簡單案例,咱們開發時,也應先進行簡單的設計,讓各模塊、各類都各司其職、邏輯解耦,這樣大家學起來會更簡單一些。
      此處,不畫類圖了,直接根據項目結構,簡單介紹一下吧:

      1、pagedata 是指數據模塊,包含:

      DataInfo 實體類,定義商品字段屬性
      DataSearch 數據訪問類,模擬子線程異步查詢分頁數據,可將數據結果通過lambda回調回去

      2、pageMangage 是指分頁管理模塊,將分頁的全部邏輯托管給該模塊處理。為了簡化分頁邏輯的實現,根據功能單一性進行了簡單拆分:

      PagesManager 分頁管理類,用于統籌列表數據查詢、展示、樣式切換
      PagesLayoutManager 分頁布局管理類,用于列表樣式和網格樣式的管理、數據位置記錄
      PagesDataManager 分頁數據管理類,用于分頁列表數據、footview數據的封裝

      3、adapter 是指適配器模塊,主要用于定義各類適配器

      PagesAdapter 分頁適配器類,用于創建、展示 itemview、footview,處理footview回調事件

      4、utils 是指工具模塊,用于定義一些常用工具

      AppUtils 工具類,用于判斷網絡連接情況

      代碼實現

      在文章的最后,會將Demo源碼下載地址分享給大家,以供參考。

      1、pagedata 數據模塊

      1.1、DataInfo.kt 實體類

      kotlin類中定義了屬性,則已包含了默認的get、set

      package com.qxc.kotlinpages.pagedata
      
      /**
       * 實體類
       *
       * @author 齊行超
       * @date 19.11.30
       */
      class DataInfo {
          /**
           * 標題
           */
          var title: String = ""
          /**
           * 描述
           */
          var desc: String = ""
          /**
           * 圖片
           */
          var imageUrl: String = ""
          /**
           * 價格
           */
          var price: String = ""
          /**
           * 鏈接
           */
          var link: String = ""
      }
      1.2、DataSearch 數據訪問類:
      package com.qxc.kotlinpages.pagedata
      import com.qxc.kotlinpages.utils.AppUtils
      
      /**
       * 數據查詢類:模擬網絡請求,從服務器數據庫獲取數據
       *
       * @author 齊行超
       * @date 19.11.30
       */
      class DataSearch {
          //服務器數據庫中的數據總數量(模擬)
          private val totalNum = 25
      
          //聲明回調函數(Lambda表達式參數:errorCode錯誤碼,dataInfos數據,無返回值)
          private lateinit var listener: (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit
      
          /**
           * 設置數據請求監聽器
           *
           * @param plistener 數據請求監聽器的回調類對象
           */
          fun setDataRequestListener(plistener: 
                                    (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit) {
              this.listener = plistener
          }
      
          /**
           * 查詢方法(模擬從數據庫查詢)
           * positionNum: 數據起始位置
           * pageNum: 查詢數量(每頁查詢數量)
           */
          fun search(positionNum: Int, pageNum: Int) {
              //模擬網絡異步請求(子線程中,進行異步請求)
              Thread()
              {
                  //模擬網絡查詢耗時
                  Thread.sleep(1000)
      
                  //獲得數據查詢結束位置
                  var end: Int = if (positionNum + pageNum > totalNum) totalNum 
                                 else positionNum + pageNum
                  //創建集合
                  var datas = ArrayList<DataInfo>()
      
                  //判斷網絡狀態
                  if (!AppUtils.instance.isConnectNetWork()) {
                      //回調異常結果
                      this.listener(1,datas)
                      //回調異常結果
                      //this.listener.invoke(-1, datas)
                      return@Thread
                  }
      
                  //組織數據(模擬獲取到數據)
                  for (index in positionNum..end - 1) {
                      var data = DataInfo()
                      data.title = "Android Kotlin ${index + 1}"
                      data.desc = "Kotlin 是一個用于現代多平臺應用的靜態編程語言,由 JetBrains ..."
                      data.price = (100 * (index + 1)).toString()
                      data.imageUrl = ""
                      data.link = "跳轉至Kotlin柜臺 -> JetBrains"
                      datas.add(data)
                  }
      
                  //回調結果
                  this.listener.invoke(0, datas)
      
              }.start()
          }
      }

      DataSearch類有兩個重點知識:

      1.2.1、子線程異步查詢的實現

      a、參考常規分頁網絡請求API,數據查詢方法應包含參數:起始位置、每頁數量
      b、安卓中的網絡查詢,需要在子線程中操作,當前案例直接使用Thread{}.start()實現子線程
      
      線程實現的寫法與Java中不太一樣,為什么這么寫呢?咱們具體展開說明一下:
      -----------------------------------Thread{}.start()-------------------------------------
      通常情況下,Java中實現一個線程,可這么寫:
      new Thread(new Runnable() {
                  @Override
                  public void run() {
      
                  }
              }).start();
      
      如果完全按照Java的寫法,將其轉為Kotlin,應該這么寫:
      Thread(object: Runnable{
                  override fun run() {
      
                  }
              }).start()
      
      但是在本案例中卻是:Thread{}.start(),并未看到Runnable對象和run方法,其實是被Lambda表達式替換了:
      >>  Runnable接口有且僅有1個抽象函數run(),符合“函數式接口”定義(即:一個接口僅有一個抽象方法) 
      >>  這樣的接口可以使用Lambda表達式來簡化代碼的編寫 :
      
      使用Lambda表示Runnable接口實現,因run()無參數、無返回值,對應的Lambda實現結構應該是:
       { -> } 
      
      當Lambda表達式無任何參數時,可以省略箭頭符號:
       { } 
      
      我們將Lambda表達式替換Runnable接口實現,Kotlin代碼如下所示:
      Thread({ }) .start()
      
      如果Lambda表達式是函數是最后一個實參,則可以放在小括號后面:
      Thread() { }  .start()
      
      如果Lambda是函數的唯一實參的時候,小括號可以直接省略,也就變成了咱們案例的效果了:
      Thread{ } .start()
      
      -----------------------------------Thread{}.start()-------------------------------------
      
      以上是線程Lambda表達式的簡化過程!!!
      使用Lambda表達式,使得我們可以在 “Thread{ }” 的大括號里直接寫子線程實現,代碼變簡單了
      
      更多Lambda表達式介紹,請參考文章:https://www.cnblogs.com/Jetictors/p/8647888.html

      1.2.2、數據回調監聽

      此案例通過Lambda表達式實現了數據回調監聽(與iOSblock類似):
      
      a、聲明Lambda表達式,用于定義回調方法結構(參數、返回值),如:
            var listener: (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit
            Lambda表達式可理解為一種特殊類型,即:抽象方法類型
      
      b、由調用方傳遞過來Lambda表達式的實參對象(即:調用方已實現的Lambda表達式所表示的抽象方法)
            setDataRequestListener(plistener:....)
      
      c、當執行完數據查詢時,將結果通過調用Lambda表達式實參對象回傳回去,如:
            this.listener(1,datas)
            this.listener.invoke(0, datas)
            這兩種調用方式都是可以的,invoke是指執行自身
      
      
      當然,我們也可以在Kotlin中使用接口回調的那種方式,與Java一樣,只是代碼會繁瑣一些而已!!!

      2、pageMangage 分頁管理模塊

      為了簡化分頁邏輯,讓大家更好理解,此處將分頁數據、分頁布局拆分出來,使其邏輯解耦,也便于代碼的管理維護。

      2.1、PagesDataManager 分頁數據管理類

      主要內容,包括:

      1、配置分頁數據的查詢位置、每頁數量,每次查詢數據后重新計算下次查詢位置
      2、分頁數據接口查詢
      3、分頁狀態文本處理(數據查詢中、沒有更多數據了、查詢異常...
      package com.qxc.kotlinpages.pagemanage
      
      import android.os.Handler
      import android.os.Looper
      import android.util.Log
      import com.qxc.kotlinpages.pagedata.DataInfo
      import com.qxc.kotlinpages.pagedata.DataSearch
      
      /**
       * 分頁數據管理類:
       * 1、分頁數據的查詢位置、每頁數量
       * 2、分頁數據接口查詢
       * 3、分頁狀態文本處理
       *
       * @author 齊行超
       * @date 19.11.30
       */
      class PagesDataManager {
          var startIndex = 0 //分頁起始位置
          val pageNum = 10 //每頁數量
          val TYPE_PAGE_MORE = "數據加載中..." //分頁加載類型:更多數據
          val TYPE_PAGE_LAST = "沒有更多數據了" //分頁加載類型:沒有數據了
          val TYPE_PAGE_ERROR = "出錯了,點擊重試!!" //分頁加載類型:出錯了,當這種狀態時可點擊重試
      
          //定義數據回調監聽
          //參數:dataInfos 當前頁數據集合, footType 分頁狀態文本
          lateinit var listener: ((dataInfos: ArrayList<DataInfo>, footType: String) -> Unit)
      
          /**
           * 設置回調
           */
          fun setDataListener(pListener: 
                             (dataInfos: ArrayList<DataInfo>, footType: String) -> Unit) {
              this.listener = pListener
          }
      
          /**
           * 查詢數據
           */
          fun searchPagesData() {
              //創建數據查詢對象(模擬數據查詢)
              var dataSearch = DataSearch()
              //設置數據回調監聽
              dataSearch.setDataRequestListener { errorCode, datas ->
                  //切回主線程
                  Handler(Looper.getMainLooper()).post {
                      if (errorCode == 0) {
                          //累計當前位置
                          startIndex += datas.size
                          //判斷后面是否還有數據
                          var footType = if (pageNum == datas.size) TYPE_PAGE_MORE 
                                         else TYPE_PAGE_LAST
                          //回調結果
                          listener.invoke(datas, footType)
                      } else {
                          //回調錯誤結果
                          listener.invoke(datas, TYPE_PAGE_ERROR)
                      }
                  }
              }
              //查詢數據
              dataSearch.search(startIndex, pageNum)
          }
      
          /**
           * 重置查詢
           */
          fun reset() {
              startIndex = 0;
          }
      }
      2.2、PagesLayoutManager 分頁布局管理類

      主要內容,包括:

      1、創建列表布局、網格布局(只創建一次即可)
      2、記錄數據位置(用于切換列表布局、網格布局時,保持位置不變)
      package com.qxc.kotlinpages.pagemanage
      
      import android.content.Context
      import androidx.recyclerview.widget.GridLayoutManager
      import androidx.recyclerview.widget.LinearLayoutManager
      
      /**
       * 分頁布局管理類:
       * 1、創建列表布局、網格布局
       * 2、記錄數據位置(用于切換列表布局、網格布局時,保持位置不變)
       *
       * @author 齊行超
       * @date 19.11.30
       */
      class PagesLayoutManager(
          pcontext: Context
      ) {
          val STYLE_LIST = 1 //列表樣式(常量標識)
          val STYLE_GRID = 2 //網格樣式(常量標識)
      
          var llManager: LinearLayoutManager //列表布局管理器對象
          var glManager: GridLayoutManager //網格布局管理器對象
      
          var position: Int = 0 //數據位置(當切換樣式時,需記錄列表數據的位置,用以保持數據位置不變)
          var context = pcontext //上下文對象
      
          init {
              llManager = LinearLayoutManager(context)
              glManager = GridLayoutManager(context, 2)
          }
      
          /**
           * 獲得布局管理器對象
           */
          fun getLayoutManager(pagesStyle: Int): LinearLayoutManager {
              //記錄當前數據位置
              recordDataPosition(pagesStyle)
      
              //根據樣式返回布局管理器對象
              if (pagesStyle == STYLE_LIST) {
                  return llManager
              }
              return glManager
          }
      
          /**
           * 獲得數據位置
           */
          fun getDataPosition(): Int {
              return position
          }
      
          /**
           * 記錄數據位置
           */
          private fun recordDataPosition(pagesStyle: Int) {
              //pagesStyle表示目標樣式,此處需要記錄的是原樣式時的數據位置
              if (pagesStyle == STYLE_LIST) {
                  position = glManager?.findFirstVisibleItemPosition()
              } else if (pagesStyle == STYLE_GRID) {
                  position = llManager?.findFirstVisibleItemPosition()
              }
          }
      }
      2.3、PagesManager 分頁管理類

      主要內容,包含:

       1、創建、刷新適配器
       2、查詢、綁定分頁數據
       3、切換分頁布局(列表布局、網格布局)
       4、當切換至網格布局時,設置footview獨占一行(即使網格布局每行顯示多個item,footview也獨占一行)

      主要技術點,包括:

      1、設置grid footview獨占一行
      2、RecyclerView控件的使用(數據綁定,刷新,樣式切換等)
      package com.qxc.kotlinpages.pagemanage
      import android.content.Context
      import androidx.recyclerview.widget.GridLayoutManager
      import androidx.recyclerview.widget.RecyclerView
      import com.qxc.kotlinpages.adapter.PagesAdapter
      import com.qxc.kotlinpages.pagedata.DataInfo
      
      /**
       * 分頁管理類:
       * 1、創建適配器
       * 2、查詢、綁定分頁數據
       * 3、切換分頁布局
       * 4、當切換至網格布局時,設置footview獨占一行
       *
       * @author 齊行超
       * @date 19.11.30
       */
      class PagesManager(pContext: Context, pRecyclerView: RecyclerView) {
          private var context = pContext //上下文對象
          private var recyclerView = pRecyclerView //列表控件對象
          private var adapter:PagesAdapter? = null //適配器對象
          private var pagesLayoutManager = PagesLayoutManager(context) //分頁布局管理對象
          private var pagesDataManager = PagesDataManager() //分頁數據管理對象
          private var datas = ArrayList<DataInfo>() //數據集合
      
          /**
           * 設置分頁樣式(列表、網格)
           *
           * @param isGrid 是否網格樣式
           */
          fun setPagesStyle(isGrid: Boolean): PagesManager {
              //通過樣式獲得對應的布局類型
              var style = if (isGrid) pagesLayoutManager.STYLE_GRID 
                          else pagesLayoutManager.STYLE_LIST
              var layoutManager = pagesLayoutManager.getLayoutManager(style)
      
              //獲得當前數據位置(切換樣式后,滑動到記錄的數據位置)
              var position = pagesLayoutManager.getDataPosition()
      
              //創建適配器對象
              adapter = PagesAdapter(context, datas, pagesDataManager.TYPE_PAGE_MORE)
              //通知適配器,itemview當前使用哪種分頁布局樣式(列表、網格)
              adapter?.setItemStyle(style)
      
              //列表控件設置適配器
              recyclerView.adapter = adapter
              //列表控件設置布局管理器
              recyclerView.layoutManager = layoutManager
              //列表控件滑動到指定位置
              recyclerView.scrollToPosition(position)
      
              //當layoutManager是網格布局時,設置footview獨占一行
              if(layoutManager is GridLayoutManager){
                  setSpanSizeLookup(layoutManager)
              }
      
              //設置監聽器
              setListener()
              return this
          }
      
          /**
           * 設置監聽器:
           *
           * 1、當滑動到列表底部時,查詢下一頁數據
           * 2、當點擊了footview的"出錯了,點擊重試!!"時,重新查詢數據
           */
          fun setListener() {
              //1、當滑動到列表底部時,查詢下一頁數據
              adapter?.setOnFootViewAttachedToWindowListener {
                  //查詢數據
                  searchData()
              }
      
              //2、當點擊了footview的"出錯了,點擊重試!!"時,重新查詢數據
              adapter?.setOnFootViewClickListener {
                  if (it.equals(pagesDataManager.TYPE_PAGE_ERROR)) {
      
                      //點擊查詢,更改footview狀態
                      adapter?.footMessage = pagesDataManager.TYPE_PAGE_MORE
                      adapter?.notifyDataSetChanged()
      
                      //"出錯了,點擊重試!!
                      searchData()
                  }
              }
          }
      
          /**
           * 設置grid footview獨占一行
           */
          fun setSpanSizeLookup(layoutManager: GridLayoutManager) {
              layoutManager.setSpanSizeLookup(object : GridLayoutManager.SpanSizeLookup() {
                  override fun getSpanSize(position: Int): Int {
                      return if (adapter?.TYPE_FOOTVIEW == adapter?.getItemViewType(position)) 
                                layoutManager.getSpanCount() 
                             else 1
                  }
              })
          }
      
          /**
           * 獲得查詢結果,刷新列表
           */
          fun searchData() {
              pagesDataManager.setDataListener { pdatas, footMessage ->
                  if (pdatas != null) {
                      datas.addAll(pdatas)
                      adapter?.footMessage = footMessage
                      adapter?.notifyDataSetChanged()
                  }
              }
              pagesDataManager.searchPagesData()
          }
      }

      3、adapter 適配器模塊

      3.1、PagesAdapter 分頁適配器類

      主要內容,包括:

      1、創建、綁定itemview(列表item、網格item)、footview
      2、判斷是否滑動到列表底部(更簡單的方式實現列表滑動到底部的監聽)
      3、footview點擊事件回調(如果是footview顯示為“出錯了,點擊重試”,需要獲取點擊事件,重新查詢數據)
      4、滑動到列表底部事件回調(當列表滑動到底部時,則需要查詢下一頁數據了)

      主要技術點,包括:

      1、多item項的應用
      2、滑動到列表底部的判斷(比“監聽RecyclerView的Scroll坐標”這種常規做法要簡化很多,且精準)
      package com.qxc.kotlinpages.adapter
      
      import android.content.Context
      import android.view.LayoutInflater
      import android.view.View
      import android.view.ViewGroup
      import android.widget.TextView
      import androidx.recyclerview.widget.RecyclerView
      import com.qxc.kotlinpages.R
      import com.qxc.kotlinpages.pagedata.DataInfo
      
      /**
       * 分頁適配器類
       * 1、創建、綁定itemview(列表item、網格item)、footview
       * 2、判斷是否滑動到列表底部
       * 3、footview點擊事件回調
       * 4、滑動到列表底部事件回調
       *
       * @author 齊行超
       * @date 19.11.30
       */
      class PagesAdapter(
          pContext: Context,
          pDataInfos: ArrayList<DataInfo>,
          pFootMessage : String
      ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
      
          private var context = pContext //上下文對象,通過構造傳函數遞過來
          private var datas = pDataInfos //數據集合,通過構造函數傳遞過來
          var footMessage = pFootMessage //footview文本信息,可通過構造函數傳遞過來,也可再次修改
      
          val TYPE_FOOTVIEW: Int = 1 //item類型:footview
          val TYPE_ITEMVIEW: Int = 2 //item類型:itemview
          var typeItem = TYPE_ITEMVIEW
      
          val STYLE_LIST_ITEM = 1 //樣式類型:列表
          val STYLE_GRID_ITEM = 2 //樣式類型:網格
          var styleItem = STYLE_LIST_ITEM
      
          /**
           * 重寫創建ViewHolder的函數
           */
          override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
                  : RecyclerView.ViewHolder {
              //如果是itemview
              if (typeItem == TYPE_ITEMVIEW) {
                  //判斷樣式類型(列表布局、網格布局)
                  var layoutId =
                      if (styleItem == STYLE_LIST_ITEM) R.layout.item_page_list 
                      else R.layout.item_page_grid
                  var view = LayoutInflater.from(context).inflate(layoutId, parent, false)
                  return ItemViewHolder(view)
              }
              //如果是footview
              else {
                  var view = LayoutInflater.from(context)
                                  .inflate(R.layout.item_page_foot, parent, false)
                  return FootViewHolder(view)
              }
          }
      
          /**
           * 重寫獲得項數量的函數
           */
          override fun getItemCount(): Int {
              //因列表中增加了footview(顯示分頁狀態信息),所以item總數量 = 數據數量 + 1
              return datas.size + 1
          }
      
          /**
           * 重寫綁定ViewHolder的函數
           */
          override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
              if (holder is ItemViewHolder) {
                  if (datas.size <= position) {
                      return
                  }
                  var data = datas.get(position)
                  holder.tv_title.text = data.title
                  holder.tv_desc.text = data.desc
                  holder.tv_price.text = data.price
                  holder.tv_link.text = data.link
      
              } else if (holder is FootViewHolder) {
                  holder.tv_msg.text = footMessage
      
                  //當點擊footview時,將該事件回調出去
                  holder.tv_msg.setOnClickListener {
                      footViewClickListener.invoke(footMessage)
                  }
              }
          }
      
          /**
           * 重新獲得項類型的函數(項類型包括:itemview、footview)
           */
          override fun getItemViewType(position: Int): Int {
              //設置在數據最底部顯示footview
              typeItem = if (position == datas.size) TYPE_FOOTVIEW else TYPE_ITEMVIEW
              return typeItem
          }
      
      
          /**
           * 當footview第二次出現在列表時,回調該事件
           * (此處用于模擬用戶上滑手勢,當滑到底部時,重新請求數據)
           */
          var footviewPosition = 0
          override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
              super.onViewAttachedToWindow(holder)
              if (footviewPosition == holder.adapterPosition) {
                  return
              }
              if (holder is FootViewHolder) {
                  footviewPosition = holder.adapterPosition
                  //回調查詢事件
                  footViewAttachedToWindowListener.invoke()
              }
          }
      
          /**
           * ItemViewHolder
           */
          class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
              var tv_title = itemView.findViewById<TextView>(R.id.tv_title)
              var tv_desc = itemView.findViewById<TextView>(R.id.tv_desc)
              var tv_price = itemView.findViewById<TextView>(R.id.tv_price)
              var tv_link = itemView.findViewById<TextView>(R.id.tv_link)
          }
      
          /**
           * FootViewHolder
           */
          class FootViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
              var tv_msg = itemView.findViewById<TextView>(R.id.tv_msg)
          }
      
          /**
           * 設置Item樣式(列表、網格)
           */
          fun setItemStyle(pstyle: Int) {
              styleItem = pstyle
          }
      
          //定義footview附加到Window上時的回調
          lateinit var footViewAttachedToWindowListener: () -> Unit
          fun setOnFootViewAttachedToWindowListener(pListener: () -> Unit) {
              this.footViewAttachedToWindowListener = pListener
          }
      
          //定義footview點擊時的回調
          lateinit var footViewClickListener:(String)->Unit
          fun setOnFootViewClickListener(pListner:(String)->Unit){
              this.footViewClickListener = pListner
          }
      }

      4、utils 工具模塊

      4.1、AppUtils 項目工具類

      此案例中主要用于判斷網絡連接情況。
      該類的主要技術點:Kotlin的共生對象、線程安全單例,詳見源碼:

      package com.qxc.kotlinpages.utils
      
      import android.content.Context
      import android.net.ConnectivityManager
      import android.net.NetworkCapabilities
      import android.os.Build
      
      /**
       * 工具類
       *
       * @author 齊行超
       * @date 19.11.30
       */
      class AppUtils {
          //使用共生對象,表示靜態static
          companion object{
              /**
               * 線程安全的單例(懶漢式單例)
               */
              val instance : AppUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
                  AppUtils()
              }
      
              private lateinit var context:Context
      
              /**
               * 注冊
               *
               * @param pContext 上下文
               */
              fun register(pContext: Context){
                  context = pContext
              }
          }
      
          /**
           * 判斷是否連接了網絡
           */
          fun isConnectNetWork():Boolean{
              var result = false
              val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) 
                       as ConnectivityManager?
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                  cm?.run {
                      this.getNetworkCapabilities(cm.activeNetwork)?.run {
                          result = when {
                              this.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                              this.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                              this.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                              else -> false
                          }
                      }
                  }
              } else {
                  cm?.run {
                      cm.activeNetworkInfo?.run {
                          if (type == ConnectivityManager.TYPE_WIFI) {
                              result = true
                          } else if (type == ConnectivityManager.TYPE_MOBILE) {
                              result = true
                          }
                      }
                  }
              }
              return result
          }
      }

      5、UI模塊

      5.1、MainActivity 主頁面,用于顯示分頁列表、切換分頁樣式(列表樣式、網格樣式)
      package com.qxc.kotlinpages
      
      import androidx.appcompat.app.AppCompatActivity
      import android.os.Bundle
      import com.qxc.kotlinpages.pagemanage.PagesManager
      import com.qxc.kotlinpages.utils.AppUtils
      import kotlinx.android.synthetic.main.activity_main.*
      
      class MainActivity : AppCompatActivity() {
          var isGrid = false
          var pagesManager: PagesManager? = null
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
              AppUtils.register(this)
      
              initEvent()
              initData()
          }
      
          fun initEvent() {
              //切換列表樣式按鈕的點擊事件
              iv_style.setOnClickListener {
                  //切換圖標(列表與網格)
                  var id: Int =
                      if (isGrid) R.mipmap.product_search_list_style_grid 
                      else R.mipmap.product_search_list_style_list
                  iv_style.setImageResource(id)
      
                  //記錄當前圖標類型
                  isGrid = !isGrid
      
                  //更改樣式(列表與網格)
                  pagesManager!!.setPagesStyle(isGrid)
              }
          }
      
          fun initData() {
              //初始化PagesManager,默認查詢列表
              pagesManager = PagesManager(this, rv_data)
              pagesManager!!.setPagesStyle(isGrid).searchData()
          }
      }
      注意:頁面中引用了 kotlinx.android.synthetic.main.activity_main.*
            》》這表示無需再寫findViewById()了,直接使用xml中控件id即可

      MainActivity的布局頁面,使用了約束布局,層級嵌套少,且更簡單一些:

      <?xml version="1.0" encoding="utf-8"?>
      <androidx.constraintlayout.widget.ConstraintLayout 
          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"
          tools:context=".MainActivity">
      
          <View
              android:id="@+id/v_top"
              android:layout_width="match_parent"
              android:layout_height="50dp"
              android:background="#FD4D4D"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintRight_toRightOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
      
          <TextView
              android:id="@+id/tv_title"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="分頁demo"
              android:textColor="#ffffff"
              android:textSize="18sp"
              app:layout_constraintBottom_toBottomOf="@id/v_top"
              app:layout_constraintLeft_toLeftOf="@id/v_top"
              app:layout_constraintRight_toRightOf="@id/v_top"
              app:layout_constraintTop_toTopOf="@id/v_top" />
      
          <ImageView
              android:id="@+id/iv_style"
              android:layout_width="40dp"
              android:layout_height="40dp"
              android:layout_marginRight="5dp"
              android:scaleType="center"
              android:src="@mipmap/product_search_list_style_grid"
              app:layout_constraintBottom_toBottomOf="@id/v_top"
              app:layout_constraintRight_toRightOf="@id/v_top"
              app:layout_constraintTop_toTopOf="@id/v_top" />
      
          <androidx.recyclerview.widget.RecyclerView
              android:id="@+id/rv_data"
              android:layout_width="match_parent"
              android:layout_height="0dp"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintRight_toRightOf="parent"
              app:layout_constraintTop_toBottomOf="@id/v_top" />
      
      </androidx.constraintlayout.widget.ConstraintLayout>
      5.2、item布局(列表樣式),也是使用了約束布局:

      <?xml version="1.0" encoding="utf-8"?>
      <androidx.constraintlayout.widget.ConstraintLayout 
          xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="150dp"
          android:layout_marginLeft="10dp"
          android:layout_marginTop="10dp"
          android:layout_marginRight="10dp"
          android:background="#eeeeee">
      
          <ImageView
              android:id="@+id/iv_image"
              android:layout_width="80dp"
              android:layout_height="110dp"
              android:layout_marginLeft="10dp"
              android:scaleType="fitXY"
              android:src="@mipmap/kotlin"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
      
          <TextView
              android:id="@+id/tv_title"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginLeft="10dp"
              android:text="Android Kotlin"
              android:textColor="#333333"
              android:textSize="18sp"
              app:layout_constraintLeft_toRightOf="@id/iv_image"
              app:layout_constraintTop_toTopOf="@id/iv_image" />
      
          <TextView
              android:id="@+id/tv_desc"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_marginLeft="10dp"
              android:layout_marginRight="10dp"
              android:lines="2"
              android:text="Kotlin 是一個用于現代多平臺應用的靜態編程語言,由 JetBrains 開發..."
              android:textColor="#888888"
              android:textSize="12sp"
              app:layout_constraintLeft_toRightOf="@id/iv_image"
              app:layout_constraintRight_toRightOf="parent"
              app:layout_constraintTop_toBottomOf="@id/tv_title" />
      
          <TextView
              android:id="@+id/tv_price_symbol"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginLeft="10dp"
              android:layout_marginTop="20dp"
              android:text="¥"
              android:textColor="#FD4D4D"
              android:textSize="10dp"
              app:layout_constraintLeft_toRightOf="@id/iv_image"
              app:layout_constraintTop_toBottomOf="@id/tv_desc" />
      
          <TextView
              android:id="@+id/tv_price"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="128.00"
              android:textColor="#FD4D4D"
              android:textSize="22sp"
              app:layout_constraintBaseline_toBaselineOf="@id/tv_price_symbol"
              app:layout_constraintLeft_toRightOf="@id/tv_price_symbol" />
      
          <TextView
              android:id="@+id/tv_link"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginLeft="10dp"
              android:text="跳轉至Kotlin柜臺 -> JetBrains"
              android:textColor="#aaaaaa"
              android:textSize="10sp"
              app:layout_constraintBottom_toBottomOf="@id/iv_image"
              app:layout_constraintLeft_toRightOf="@id/iv_image" />
      
      </androidx.constraintlayout.widget.ConstraintLayout>
      
      5.3、item布局(網格樣式),仍然使用了約束布局:

      <?xml version="1.0" encoding="utf-8"?>
      <androidx.constraintlayout.widget.ConstraintLayout 
          xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_marginLeft="10dp"
          android:layout_marginTop="10dp"
          android:layout_marginRight="10dp"
          android:paddingBottom="10dp"
          android:background="#eeeeee">
      
          <ImageView
              android:id="@+id/iv_image"
              android:layout_width="80dp"
              android:layout_height="110dp"
              android:layout_marginTop="10dp"
              android:scaleType="fitXY"
              android:src="@mipmap/kotlin"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintRight_toRightOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
      
          <TextView
              android:id="@+id/tv_title"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginLeft="10dp"
              android:layout_marginTop="20dp"
              android:text="Android Kotlin"
              android:textColor="#333333"
              android:textSize="18sp"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintTop_toBottomOf="@id/iv_image" />
      
          <TextView
              android:id="@+id/tv_desc"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_marginRight="10dp"
              android:lines="2"
              android:text="Kotlin 是一個用于現代多平臺應用的靜態編程語言,由 JetBrains 開發..."
              android:textColor="#888888"
              android:textSize="12sp"
              app:layout_constraintLeft_toLeftOf="@id/tv_title"
              app:layout_constraintRight_toRightOf="parent"
              app:layout_constraintTop_toBottomOf="@id/tv_title" />
      
          <TextView
              android:id="@+id/tv_price_symbol"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginTop="20dp"
              android:text="¥"
              android:textColor="#FD4D4D"
              android:textSize="10dp"
              app:layout_constraintLeft_toLeftOf="@id/tv_title"
              app:layout_constraintTop_toBottomOf="@id/tv_desc" />
      
          <TextView
              android:id="@+id/tv_price"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="128.00"
              android:textColor="#FD4D4D"
              android:textSize="22sp"
              app:layout_constraintBaseline_toBaselineOf="@id/tv_price_symbol"
              app:layout_constraintLeft_toRightOf="@id/tv_price_symbol" />
      
          <TextView
              android:id="@+id/tv_link"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="跳轉至Kotlin柜臺 -> JetBrains"
              android:textColor="#aaaaaa"
              android:textSize="10sp"
              app:layout_constraintTop_toBottomOf="@id/tv_price"
              app:layout_constraintLeft_toLeftOf="@id/tv_title" />
      
      </androidx.constraintlayout.widget.ConstraintLayout>
      
      5.4、footview布局

      比較簡單,僅有一個文本控件:

      <?xml version="1.0" encoding="utf-8"?>
      <androidx.constraintlayout.widget.ConstraintLayout 
          xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_width="match_parent"
          android:layout_height="50dp"
          android:layout_margin="10dp"
          android:background="#eeeeee">
      
          <TextView
              android:id="@+id/tv_msg"
              android:layout_width="0dp"
              android:layout_height="0dp"
              android:gravity="center"
              android:text="加載中..."
              android:textColor="#777777"
              android:textSize="12sp"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintRight_toRightOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
      
      </androidx.constraintlayout.widget.ConstraintLayout>

      總結

      分頁實現難點匯總:

      1、切換RecyclerView展示樣式(列表樣式、網格樣式),保持數據位置不變
      2、網格樣式時,footview獨占一行
      3、直接在adapter中判斷是否滑動到了底部,比常規做法(監聽滑動坐標)更簡單一些
      4、分頁狀態管控(數據加載中、沒有更多數據了、出錯了點擊重試)

      Kotlin主要技術點匯總:

      1、多線程實現(Lambda表達式的應用)
      2、異步回調(Lambda表達式的應用、高階函數)
      3、共生對象
      4、線程安全單例
      5、其他略(都比較基礎了,大家熟悉下即可)

      此篇文章主要是為了講解常規分頁的實現,所以只是做了一些基礎的拆分解耦,如果想在項目中使用,建議還是抽象一下,擴展性會更好一些(如:footview接口化擴展、數據查詢接口化擴展等)。

      Demo下載地址:
      https://pan.baidu.com/s/1gH0Zcd0QXdm4mRNMqJgS8Q

      出自:博客園


      我來說兩句
      您需要登錄后才可以評論 登錄 | 立即注冊
      facelist
      所有評論(0)
      領先的中文移動開發者社區
      18620764416
      7*24全天服務
      意見反饋:1294855032@qq.com

      掃一掃關注我們

      Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

      夫妻性姿势真人示范 - 视频 - 在线观看 - 影视资讯 - 唯爱网