Kotlin actual combat case: realize RecyclerView paging query function (imitating mainstream e-commerce APP, switchable list)

n practical case: take you to implement RecyclerView paging query function (follow the mainstream e-commerce APP, you can switch list and grid effect)

With the promotion of Kotlin, Android project development of some domestic companies has been completely cut into Kotlin from Java. Although Kotlin ranks relatively low in all kinds of programming languages (according to the ranking of programming languages released by TIOBE in August of 19, Kotlin actually ranks 45), as an Android Developer, mastering the language is the general trend.

The basic usage of Kotlin is relatively simple as a whole. There are many articles on the Internet. You can familiarize yourself with them.

Case needs

In this case, the paging list is selected mainly because it has strong versatility, covers many technical points, and is helpful for developers to be familiar with Kotlin.

The main requirements of the case are as follows (refer to the implementation of mainstream e-commerce APP):
1. The list supports gesture sliding paging query (when sliding to the bottom, the next page will be queried automatically until there is no more data)
2. Switchable list style and grid style
3. After switching the style, the data position remains the same (for example, it is currently in the 100th position, and the position remains the same after switching the style)
4. footview displays different contents according to the query status:

a. Data loading... (displayed when querying data)
b. No more data (query succeeded, but there is no data to return)
c. Error, click Retry!! (exception occurred during query, which may be caused by network or other reasons)

5. When there is an error in the query, click footview again to restart the request (for example, the network is abnormal)
6. When switching grid styles, footview should have one row exclusive

Design

Although it's a simple case, when we develop it, we should also carry out a simple design first, so that each module and each type can perform their own duties and decouple logic, so that we can learn more simply.
Here, do not draw class diagram. According to the project structure, briefly introduce:

1. pagedata refers to the data module, including:

DataInfo entity class, defining commodity field properties
 DataSearch data access class, which simulates the asynchronous query of paging data by sub thread, can call back the data result through lambda

2. pageMangage refers to the paging management module, which manages all the paging logic to the module for processing. In order to simplify the implementation of paging logic, simple splitting is carried out according to the single function:

PagesManager page management class, which is used to query, display and style switch the overall list data
 PagesLayoutManager paging layout management class, used for list style and grid style management, data location record
 PagesDataManager paging data management class, used to encapsulate paging list data and footview data

3. Adapter refers to the adapter module, which is mainly used to define all kinds of adapters

PagesAdapter page adapter class, used to create and display itemview and footview, and handle footview callback events

4. utils is a tool module used to define some common tools

AppUtils tool class, used to judge the network connection

code implementation

At the end of the article, we will share the download address of Demo source code to you for reference.

1. pagedata data module

1.1. DataInfo.kt entity class

If the attribute is defined in the kotlin class, the default get and set are already included

package com.qxc.kotlinpages.pagedata

/**
 * Entity class
 *
 * @author Qi Xing Chao
 * @date 19.11.30
 */
class DataInfo {
    /**
     * Title
     */
    var title: String = ""
    /**
     * describe
     */
    var desc: String = ""
    /**
     * picture
     */
    var imageUrl: String = ""
    /**
     * Price
     */
    var price: String = ""
    /**
     * link
     */
    var link: String = ""
}

1.2. DataSearch data access class:

package com.qxc.kotlinpages.pagedata
import com.qxc.kotlinpages.utils.AppUtils

/**
 * Data query class: simulate network request and obtain data from server database
 *
 * @author Qi Xing Chao
 * @date 19.11.30
 */
class DataSearch {
    //Total number of data in server database (simulation)
    private val totalNum = 25

    //Declare callback function (Lambda expression parameter: errorCode error code, dataInfos data, no return value)
    private lateinit var listener: (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit

    /**
     * Set data request listener
     *
     * @param plistener Callback class object of data request listener
     */
    fun setDataRequestListener(plistener: 
                              (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit) {
        this.listener = plistener
    }

    /**
     * Query method (simulate query from database)
     * positionNum: Data start position
     * pageNum: Query quantity (query quantity per page)
     */
    fun search(positionNum: Int, pageNum: Int) {
        //Simulate network asynchronous request (in sub thread, make asynchronous request)
        Thread()
        {
            //Simulation network query time consuming
            Thread.sleep(1000)

            //Get data query end position
            var end: Int = if (positionNum + pageNum > totalNum) totalNum 
                           else positionNum + pageNum
            //Create set
            var datas = ArrayList<DataInfo>()

            //Judge network status
            if (!AppUtils.instance.isConnectNetWork()) {
                //Callback exception result
                this.listener(1,datas)
                //Callback exception result
                //this.listener.invoke(-1, datas)
                return@Thread
            }

            //Organization data (data obtained by simulation)
            for (index in positionNum..end - 1) {
                var data = DataInfo()
                data.title = "Android Kotlin ${index + 1}"
                data.desc = "Kotlin Is a static programming language for modern multi platform applications, which is composed of JetBrains ..."
                data.price = (100 * (index + 1)).toString()
                data.imageUrl = ""
                data.link = "Jump to Kotlin Counter -> JetBrains"
                datas.add(data)
            }

            //Callback result
            this.listener.invoke(0, datas)

        }.start()
    }
}

The DataSearch class has two key knowledge:

1.2.1 implementation of sub thread asynchronous query

a. Refer to the general paging network request API, the data query method should include parameters: starting position, number of pages per page
 b. The network query in Android needs to be operated in the sub thread. The current case directly uses Thread{}.start() to implement the sub thread

Thread implementation is not written in the same way as Java. Why? Let's explain in detail:
-----------------------------------Thread{}.start()-------------------------------------
Usually, a thread is implemented in Java, which can be written as follows:
new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();

If you change it to Kotlin completely according to the Java writing method, you should write as follows:
Thread(object: Runnable{
            override fun run() {

            }
        }).start()

But in this case, it is: Thread{}.start(), which does not see the Runnable object and run method, but is actually replaced by Lambda expression:
>>Runnable interface has and only has one abstract function run(), which conforms to the definition of "functional interface" (that is, an interface has only one abstract method) 
>>Such an interface can use Lambda expressions to simplify code writing:

Lambda is used to represent the implementation of Runnable interface. Since run() has no parameter and return value, the corresponding lambda implementation structure should be:
 { -> } 

When the Lambda expression has no parameters, you can omit the arrow symbol:
 { } 

We replace Lambda expression with Runnable interface implementation, and Kotlin code is as follows:
Thread({ }) .start()

If the Lambda expression is the function's last argument, it can be placed after the parenthesis:
Thread() { }  .start()

If Lambda is the only argument of a function, the parentheses can be omitted directly, which becomes the effect of our case:
Thread{ } .start()

-----------------------------------Thread{}.start()-------------------------------------

The above is a simplified process of thread Lambda expression!!!
Using Lambda expression, we can write the sub Thread implementation directly in the braces of "Thread {}". The code is simpler

For more information about Lambda expressions, please refer to the article: https://www.cnblogs.com/jettictors/p/8647888.html

1.2.2 data callback monitoring

This case implements data callback listening through Lambda expression (similar to iOS block):

a. Declare Lambda expression to define callback method structure (parameter, return value), such as:
      var listener: (errorCode: Int, dataInfos: ArrayList<DataInfo>) -> Unit
      Lambda expressions can be understood as a special type: abstract method type

b. The argument object passed by the caller to the Lambda expression (that is, the abstract method represented by the implemented Lambda expression of the caller)
      setDataRequestListener(plistener:....)

c. When the data query is finished, the result is returned by calling Lambda expression argument object, such as:
      this.listener(1,datas)
      this.listener.invoke(0, datas)
      Both call methods are available. invoke means to execute itself


Of course, we can also use interface callback in Kotlin in the same way as Java, but the code will be more tedious!!!

2. pageMangage page management module

In order to simplify the paging logic and make you better understand, here we separate the paging data and paging layout, so as to decouple the logic and facilitate the code management and maintenance.

2.1. PagesDataManager paging data management class

Main contents include:

1. Configure the query location and number of pages of paging data, and recalculate the next query location after each query
 2. Paging data interface query
 3. Paging status text processing (data query, no more data, query exception...)
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

/**
 * Paging data management class:
 * 1,Query location and quantity per page of paging data
 * 2,Paging data interface query
 * 3,Paging status text processing
 *
 * @author Qi Xing Chao
 * @date 19.11.30
 */
class PagesDataManager {
    var startIndex = 0 //Paging start position
    val pageNum = 10 //Quantity per page
    val TYPE_PAGE_MORE = "Data loading..." //Paging load type: more data
    val TYPE_PAGE_LAST = "No more data" //Paging load type: no data
    val TYPE_PAGE_ERROR = "Error, click Retry!!" //Paging load type: error, when this state, you can click Retry

    //Define data callback listening
    //Parameters: dataInfos current page data set, footType paging status text
    lateinit var listener: ((dataInfos: ArrayList<DataInfo>, footType: String) -> Unit)

    /**
     * Set callback
     */
    fun setDataListener(pListener: 
                       (dataInfos: ArrayList<DataInfo>, footType: String) -> Unit) {
        this.listener = pListener
    }

    /**
     * Query data
     */
    fun searchPagesData() {
        //Create data query object (simulation data query)
        var dataSearch = DataSearch()
        //Set data callback listening
        dataSearch.setDataRequestListener { errorCode, datas ->
            //Cut back main thread
            Handler(Looper.getMainLooper()).post {
                if (errorCode == 0) {
                    //Cumulative current position
                    startIndex += datas.size
                    //Judge whether there is any data behind
                    var footType = if (pageNum == datas.size) TYPE_PAGE_MORE 
                                   else TYPE_PAGE_LAST
                    //Callback result
                    listener.invoke(datas, footType)
                } else {
                    //Callback error results
                    listener.invoke(datas, TYPE_PAGE_ERROR)
                }
            }
        }
        //Query data
        dataSearch.search(startIndex, pageNum)
    }

    /**
     * Reset query
     */
    fun reset() {
        startIndex = 0;
    }
}

2.2 page layout manager

Main contents include:

1. Create list layout and grid layout (only once)
2. Record data location (keep the location unchanged when switching list layout and grid layout)
package com.qxc.kotlinpages.pagemanage

import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager

/**
 * Paging layout management class:
 * 1,Create list layout, grid layout
 * 2,Record data location (keep the location unchanged when switching list layout and grid layout)
 *
 * @author Qi Xing Chao
 * @date 19.11.30
 */
class PagesLayoutManager(
    pcontext: Context
) {
    val STYLE_LIST = 1 //List style (constant ID)
    val STYLE_GRID = 2 //Grid style (constant ID)

    var llManager: LinearLayoutManager //List layout manager objects
    var glManager: GridLayoutManager //Grid layout manager object

    var position: Int = 0 //Data location (when switching styles, you need to record the location of the list data to keep the data location unchanged)
    var context = pcontext //Context object

    init {
        llManager = LinearLayoutManager(context)
        glManager = GridLayoutManager(context, 2)
    }

    /**
     * Get layout manager object
     */
    fun getLayoutManager(pagesStyle: Int): LinearLayoutManager {
        //Record current data location
        recordDataPosition(pagesStyle)

        //Return layout manager objects based on style
        if (pagesStyle == STYLE_LIST) {
            return llManager
        }
        return glManager
    }

    /**
     * Get data location
     */
    fun getDataPosition(): Int {
        return position
    }

    /**
     * Record data location
     */
    private fun recordDataPosition(pagesStyle: Int) {
        //pagesStyle represents the target style, where you need to record the data location of the original style
        if (pagesStyle == STYLE_LIST) {
            position = glManager?.findFirstVisibleItemPosition()
        } else if (pagesStyle == STYLE_GRID) {
            position = llManager?.findFirstVisibleItemPosition()
        }
    }
}

2.3. PagesManager paging management class

Main contents include:

 1. Create and refresh adapter
 2. Querying and binding paging data
 3. Switch paging layout (list layout, grid layout)
 4. When switching to grid layout, set footview to exclusive one row (even if each row of grid layout displays multiple item s, footview also exclusive one row)

Main technical points include:

1. Set grid footview exclusive row
 2. Use of RecyclerView control (data binding, refresh, style switching, etc.)
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

/**
 * Paging management class:
 * 1,Create adapter
 * 2,Querying and binding paging data
 * 3,Switch page layout
 * 4,When switching to grid layout, set footview to exclusive one row
 *
 * @author Qi Xing Chao
 * @date 19.11.30
 */
class PagesManager(pContext: Context, pRecyclerView: RecyclerView) {
    private var context = pContext //Context object
    private var recyclerView = pRecyclerView //List control object
    private var adapter:PagesAdapter? = null //adapter object 
    private var pagesLayoutManager = PagesLayoutManager(context) //Paging layout management object
    private var pagesDataManager = PagesDataManager() //Paging data management object
    private var datas = ArrayList<DataInfo>() //Data set

    /**
     * Set paging style (list, grid)
     *
     * @param isGrid Grid style or not
     */
    fun setPagesStyle(isGrid: Boolean): PagesManager {
        //Get the corresponding layout type through style
        var style = if (isGrid) pagesLayoutManager.STYLE_GRID 
                    else pagesLayoutManager.STYLE_LIST
        var layoutManager = pagesLayoutManager.getLayoutManager(style)

        //Get the current data location (after switching styles, slide to the recorded data location)
        var position = pagesLayoutManager.getDataPosition()

        //Create adapter object
        adapter = PagesAdapter(context, datas, pagesDataManager.TYPE_PAGE_MORE)
        //Notify the adapter which paging layout style (list, grid) is currently used by itemview
        adapter?.setItemStyle(style)

        //List control settings adapter
        recyclerView.adapter = adapter
        //List control settings layout manager
        recyclerView.layoutManager = layoutManager
        //List control slide to specified location
        recyclerView.scrollToPosition(position)

        //When the layoutManager is a grid layout, set footview to exclusive one row
        if(layoutManager is GridLayoutManager){
            setSpanSizeLookup(layoutManager)
        }

        //Set up a listener
        setListener()
        return this
    }

    /**
     * To set up a listener:
     *
     * 1,When you slide to the bottom of the list, the next page of data is queried
     * 2,When you click "error in footview, click Retry!! "On, re query the data
     */
    fun setListener() {
        //1. When you slide to the bottom of the list, the next page of data is queried
        adapter?.setOnFootViewAttachedToWindowListener {
            //Query data
            searchData()
        }

        //2. When you click "error in footview, click Retry!! "On, re query the data
        adapter?.setOnFootViewClickListener {
            if (it.equals(pagesDataManager.TYPE_PAGE_ERROR)) {

                //Click query to change footview status
                adapter?.footMessage = pagesDataManager.TYPE_PAGE_MORE
                adapter?.notifyDataSetChanged()

                //"There is an error. Click Retry!!
                searchData()
            }
        }
    }

    /**
     * Set grid footview exclusive row
     */
    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
            }
        })
    }

    /**
     * Get query results and refresh the list
     */
    fun searchData() {
        pagesDataManager.setDataListener { pdatas, footMessage ->
            if (pdatas != null) {
                datas.addAll(pdatas)
                adapter?.footMessage = footMessage
                adapter?.notifyDataSetChanged()
            }
        }
        pagesDataManager.searchPagesData()
    }
}

3. Adapter adapter module

3.1. PagesAdapter page adapter class

Main contents include:

1. Create and bind itemview (list item, grid item) and footview
 2. Determine whether to slide to the bottom of the list (a simpler way to monitor the bottom of the list)
3. Footview click event callback (if footview displays "error, click Retry", you need to get the click event and query the data again)
4. Slide to the bottom of the list event callback (when the list slides to the bottom, you need to query the next page of data)

Main technical points include:

1. Application of multi item
 2. Judgment by sliding to the bottom of the list (much simpler and more accurate than the normal practice of "monitoring the Scroll coordinates of RecyclerView")
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

/**
 * Paging adapter class
 * 1,Create and bind itemview (list item, grid item) and footview
 * 2,Determine whether to slide to the bottom of the list
 * 3,footview Click event callback
 * 4,Slide to the bottom of the list event callback
 *
 * @author Qi Xing Chao
 * @date 19.11.30
 */
class PagesAdapter(
    pContext: Context,
    pDataInfos: ArrayList<DataInfo>,
    pFootMessage : String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var context = pContext //Context object, which is passed through the construction of transfer function
    private var datas = pDataInfos //Data collection, passed through the constructor
    var footMessage = pFootMessage //footview text information can be passed through the constructor or modified again

    val TYPE_FOOTVIEW: Int = 1 //item type: footview
    val TYPE_ITEMVIEW: Int = 2 //item type: itemview
    var typeItem = TYPE_ITEMVIEW

    val STYLE_LIST_ITEM = 1 //Style types: Lists
    val STYLE_GRID_ITEM = 2 //Style type: Grid
    var styleItem = STYLE_LIST_ITEM

    /**
     * Override function to create ViewHolder
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)
            : RecyclerView.ViewHolder {
        //If it's itemview
        if (typeItem == TYPE_ITEMVIEW) {
            //Determine style type (list layout, grid layout)
            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)
        }
        //If it's footview
        else {
            var view = LayoutInflater.from(context)
                            .inflate(R.layout.item_page_foot, parent, false)
            return FootViewHolder(view)
        }
    }

    /**
     * Override the function that gets the number of items
     */
    override fun getItemCount(): Int {
        //Because footview (display paging status information) is added to the list, the total number of item s = the number of data + 1
        return datas.size + 1
    }

    /**
     * Override function binding 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

            //When you click footview, the event will be recalled
            holder.tv_msg.setOnClickListener {
                footViewClickListener.invoke(footMessage)
            }
        }
    }

    /**
     * Get the function of item type again (item types include: itemview, footview)
     */
    override fun getItemViewType(position: Int): Int {
        //Set to display footview at the bottom of the data
        typeItem = if (position == datas.size) TYPE_FOOTVIEW else TYPE_ITEMVIEW
        return typeItem
    }


    /**
     * When footview appears in the list for the second time, the event is recalled
     * (This is used to simulate the user's up sliding gesture. When sliding to the bottom, re request the data)
     */
    var footviewPosition = 0
    override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
        super.onViewAttachedToWindow(holder)
        if (footviewPosition == holder.adapterPosition) {
            return
        }
        if (holder is FootViewHolder) {
            footviewPosition = holder.adapterPosition
            //Callback query event
            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)
    }

    /**
     * Set Item style (list, grid)
     */
    fun setItemStyle(pstyle: Int) {
        styleItem = pstyle
    }

    //Define the callback when footview is attached to Window
    lateinit var footViewAttachedToWindowListener: () -> Unit
    fun setOnFootViewAttachedToWindowListener(pListener: () -> Unit) {
        this.footViewAttachedToWindowListener = pListener
    }

    //Define the callback when footview is clicked
    lateinit var footViewClickListener:(String)->Unit
    fun setOnFootViewClickListener(pListner:(String)->Unit){
        this.footViewClickListener = pListner
    }
}

4. utils tool module

4.1. AppUtils project tool class

This case is mainly used to judge the network connection.
The main technical points of this class are: Kotlin's symbiotic object and thread safety single example. See the source code for details:

package com.qxc.kotlinpages.utils

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build

/**
 * Tool class
 *
 * @author Qi Xing Chao
 * @date 19.11.30
 */
class AppUtils {
    //Use symbiotic objects to represent static
    companion object{
        /**
         * Thread safe singleton (lazy singleton)
         */
        val instance : AppUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            AppUtils()
        }

        private lateinit var context:Context

        /**
         * register
         *
         * @param pContext context
         */
        fun register(pContext: Context){
            context = pContext
        }
    }

    /**
     * Determine whether the network is connected
     */
    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 module

5.1 MainActivity main page, used to display paging list and switch paging style (list style and grid style)

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() {
        //Toggle list style button click event
        iv_style.setOnClickListener {
            //Toggle icon (list and grid)
            var id: Int =
                if (isGrid) R.mipmap.product_search_list_style_grid 
                else R.mipmap.product_search_list_style_list
            iv_style.setImageResource(id)

            //Record the current icon type
            isGrid = !isGrid

            //Change style (list and grid)
            pagesManager!!.setPagesStyle(isGrid)
        }
    }

    fun initData() {
        //Initialize PagesManager, default query list
        pagesManager = PagesManager(this, rv_data)
        pagesManager!!.setPagesStyle(isGrid).searchData()
    }
}
Note: kotlinx. Android. Synthetic. Main. Activity? Main is referenced in the page*
      >>This means that there is no need to write findViewById(), just use the control id in xml

In the layout page of MainActivity, constraint layout is used, with fewer levels of nesting, and simpler:

<?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="paging 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 layout (list style) also uses constraint layout:

<?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 Is a static programming language for modern multi platform applications, which is composed of JetBrains Development..."
        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="Jump to Kotlin Counter -> 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 layout (grid style) still uses constraint layout:

<?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 Is a static programming language for modern multi platform applications, which is composed of JetBrains Development..."
        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="Jump to Kotlin Counter -> 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 layout

It is simple, with only one text control:

<?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="Loading..."
        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>

summary

Summary of difficulties in paging implementation:

1. Switch RecyclerView presentation style (list style, grid style) and keep the data position unchanged
2. In grid style, footview has one row
3. It's easier to judge whether the slide to the bottom is directly in the adapter than the normal way (monitoring the slide coordinate)
4. Paging status control (data loading, no more data, error, click Retry)

Summary of Kotlin's main technical points:

1. Multithreading implementation (Application of Lambda expression)
2. Asynchronous callback (Application of Lambda expression, high-order function)
3. Symbiotic object
4. Thread safety single example
5. Others (they are quite basic, you can get familiar with them)

This article is mainly to explain the implementation of regular paging, so it only does some basic splitting and decoupling. If you want to use it in the project, it is recommended to abstract it, and the scalability will be better (such as: footview interface extension, data query interface extension, etc.).

278 original articles published, 220 praised, 360000 visitors+
His message board follow

Tags: Android Lambda network Java

Posted on Thu, 06 Feb 2020 02:37:06 -0500 by pikymx