Después de leer "JetPack", Paging3 probablemente esté listo para comenzar.

Autor: Huang Lin Ching

Este artículo resume el uso relacionado y los ejemplos de Paging3 en JetPack

1. ¿Qué es la paginación?

Piense en nuestro negocio anterior, ¿cómo lidiar con la carga de paginación?

Generalmente, encapsulamos RecycleView por nosotros mismos o usamos una biblioteca de terceros como XRecycleView para hacerlo. Paging es la biblioteca estándar para funciones de paginación proporcionada por Google, por lo que no necesitamos implementar funciones de paginación basadas en RecycleView por nosotros mismos, y Paging nos proporciona Muchas opciones configurables hacen que la función de búsqueda sea más flexible.

Paging3 es la última versión actual de la biblioteca Paging y todavía se encuentra en la versión beta. Comparado con Paging2, el uso es mucho más simple.


En segundo lugar, el uso de paginación

Construcción del proyecto

Primero, creamos un nuevo proyecto y hacemos referencia a la biblioteca de paginación en gradle de la siguiente manera:

def paging_version = "3.0.0-alpha07"
implementation "androidx.paging:paging-runtime:$paging_version"
testImplementation "androidx.paging:paging-common:$paging_version"

Para el ejemplo del proyecto, usamos el lenguaje Kotlin y usamos coroutine y Flow, por lo que también necesitamos agregar bibliotecas de coroutine de la siguiente manera:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-dev-11'

Ejemplo de proyecto

El documento oficial también proporciona un diagrama de nuestro uso de Paging en la arquitectura.

En la figura anterior, también podemos ver claramente que Paging tiene un rendimiento específico en la capa de almacén, ViewModel y UI. A continuación, usaremos un ejemplo para explicar gradualmente cómo funciona Paging en la arquitectura del proyecto.

Preparación de la interfaz API

Aquí hemos escrito el código de servicio de la clase RetrofitService que se usa para crear solicitudes de red de la siguiente manera:

Con la interfaz DataApi, aquí declaramos el método como una función de suspensión, que es conveniente para llamar en la corrutina.

interface DataApi {

    /**
     * 获取数据
     */
    @GET("wenda/list/{pageId}/json")
    suspend fun getData(@Path("pageId") pageId:Int): DemoReqData
}

Definir la fuente de datos

Primero, definamos la fuente de datos que DataSource hereda de PagingSource, el código es el siguiente:

class DataSource():PagingSource<Int,DemoReqData.DataBean.DatasBean>(){
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DemoReqData.DataBean.DatasBean> {
        TODO("Not yet implemented")
    }
}

Podemos ver que hay dos parámetros Key y Value en PagingSource. Aquí definimos Key como el tipo Int. Value DemoReqData es la clase de entidad correspondiente a los datos devueltos por la interfaz. Esto significa que pasamos el valor del tipo Int (como el número de página) para obtener los datos devueltos. Información del objeto DemoReqData.

Lo que debe recordarse aquí es que si no está utilizando la co-rutina de Kotlin sino Java, debe heredar el PagingSource correspondiente, como RxPagingSource o ListenableFuturePagingSource.

DataSource genera automáticamente el método de carga para nosotros, y nuestra operación de solicitud principal se completa en el método de carga. El código principal es el siguiente:

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DemoReqData.DataBean.DatasBean> {

    return try {

        //页码未定义置为1
        var currentPage = params.key ?: 1
        //仓库层请求数据
        var demoReqData = DataRespority().loadData(currentPage)
        //当前页码 小于 总页码 页面加1
        var nextPage = if (currentPage < demoReqData?.data?.pageCount ?: 0) {
            currentPage + 1
        } else {
            //没有更多数据
            null
        }
        if (demoReqData != null) {
            LoadResult.Page(
                data = demoReqData.data.datas,
                prevKey = null,
                nextKey = nextPage
            )
        } else {
            LoadResult.Error(throwable = Throwable())
        }
    } catch (e: Exception) {
        LoadResult.Error(throwable = e)
    }
}

En el código anterior, podemos ver que en la fuente de datos usamos la capa de almacén DataRespority () para solicitar datos. Si no hay más datos, devuelve nulo, y finalmente usa LoadResult.Page para devolver el resultado. Si la carga falla, regresa con LoadResult.Error, porque Los datos en LoadResult.Page deben ser de tipo no nulo, por lo que debemos determinar si la devolución es nula.

A continuación, miramos el código de la capa de almacén de DataRespority, el código es relativamente simple, como se muestra a continuación:

class DataRespority {

    private var netWork = RetrofitService.createService(
        DataApi::class.java
    )

    /**
     * 查询护理数据
     */
    suspend fun loadData(
        pageId: Int
    ): DemoReqData? {
        return try {
            netWork.getData(pageId)
        } catch (e: Exception) {
            //在这里处理或捕获异常
            null
        }

    }
}

El procedimiento dado por la invocación de carga es el siguiente:

A partir de la figura anterior, podemos saber que el método de carga lo activamos automáticamente a través de la configuración de Paginación, y no necesitamos llamarlo cada vez, entonces, ¿cómo usamos DataSource?

Llamar a PagingSource

El objeto Pager llama al método load () desde el objeto PagingSource, proporcionándole el objeto LoadParams y recibiendo el objeto LoadResult a cambio.

El significado traducido de esta oración es: el objeto Pager llama al método load () desde el objeto PagingSource, le proporciona el objeto LoadParams y, a cambio, recibe el objeto LoadResult.

Así que estamos creando un objeto viewModel y creando un objeto pager para llamar al método PagingSource. El código es el siguiente:

class MainActivityViewModel : ViewModel() {

    /**
     * 获取数据
     */
    fun getData() = Pager(PagingConfig(pageSize = 1)) {
        DataSource()
    }.flow
}

En el modelo de vista, definimos un método getData. En Pager, la personalización especial se logra configurando PagingConfig. Echemos un vistazo a los parámetros en PagingConfig de la siguiente manera:

pageSize: define el número de elementos cargados a la vez desde PagingSource.

prefetchDistance: La distancia de prefetch, la explicación simple es cargar automáticamente la página siguiente cuando qué tan lejos de la parte inferior, es decir, llamar automáticamente al método de carga, el valor predeterminado es igual a pageSize.

enablePlaceholders: si se muestran marcadores de posición. Cuando la red no es buena, el marco de la página se puede probar para mejorar la experiencia del usuario.

Hay algunos otros parámetros que no se introducirán aquí. Desde el código fuente del método de construcción, se puede ver que el parámetro pageSize es obligatorio y los otros son opcionales, por lo que pasamos 1 aquí.

Definir RecycleViewAdapter

Este paso no es muy diferente de nuestra definición habitual de RecycleViewAdapter ordinario, pero heredamos PagingDataAdapter. El código principal es el siguiente:

class DataRecycleViewAdapter :
    PagingDataAdapter<DemoReqData.DataBean.DatasBean, RecyclerView.ViewHolder>(object :
        DiffUtil.ItemCallback<DemoReqData.DataBean.DatasBean>() {

        override fun areItemsTheSame(
            oldItem: DemoReqData.DataBean.DatasBean,
            newItem: DemoReqData.DataBean.DatasBean
        ): Boolean {
            return oldItem.id == newItem.id
        }

        @SuppressLint("DiffUtilEquals")
        override fun areContentsTheSame(
            oldItem: DemoReqData.DataBean.DatasBean,
            newItem: DemoReqData.DataBean.DatasBean
        ): Boolean {
            return oldItem == newItem
        }
    }) {
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        var dataBean = getItem(position)
        (holder as DataViewHolder).binding.demoReaData = dataBean
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {

    return TestViewHolder(
        DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.health_item_test,
            parent,
            false
        )
    )
}

    inner class DataViewHolder(private val dataBindingUtil: ItemDataBinding) :
        RecyclerView.ViewHolder(dataBindingUtil.root) {
        var binding = dataBindingUtil
    }

}

Lo que queremos recordar aquí es el parámetro DiffUtil, una devolución de llamada utilizada para calcular la diferencia entre dos elementos no vacíos en la lista. Generalmente no se fijan circunstancias especiales.

Ver la solicitud de datos de la capa y mostrar el resultado en Ver

En este punto, el trabajo básico está casi hecho. Por supuesto, casi estamos hablando de los resultados, pero los veremos pronto. Aún quedan muchos lugares por explicar. En el último paso, solicitamos datos en la vista y vinculamos los resultados al adaptador. Llamamos al método getData en viewModel en el código de View, el código es el siguiente:

val manager = LinearLayoutManager(this)
rv_data.layoutManager = manager
rv_data.adapter = dataRecycleViewAdapter
btn_get.setOnClickListener {
    lifecycleScope.launch {
        mainActivityViewModel.getData().collectLatest {
            dataRecycleViewAdapter.submitData(it)
        }
    }
}

Llamamos al método getData en la corrutina para recibir los datos más recientes y proporcionar datos al adaptador a través del método submitData de PagingAdapter. El resultado de la operación es el siguiente (ignorando el feo UI.jpg).

Cuando nos deslizamos hacia abajo, la página siguiente se cargará automáticamente cuando queden 1 (tamaño de página) datos en la parte inferior.

Por supuesto, no es necesario pasar pageSize para esta interfaz, por lo que el tamaño de los datos devueltos no se verá afectado por pageSize. Como resultado, usamos Paging3 para completar una solicitud de paginación de datos simple.


Tres, estado de carga de paginación

Paging3 nos proporciona una manera de obtener el estado de carga de Paging, incluida la forma de agregar eventos de monitoreo y la forma de mostrarlos directamente en el adaptador Primero, veamos la forma de monitorear eventos.

Obtener el estado de carga escuchando el evento

Arriba creamos dataRecycleViewAdapter en Activity para mostrar los datos de la página, podemos usar el método addLoadStateListener para agregar eventos de escucha del estado de carga, como se muestra a continuación:

dataRecycleViewAdapter.addLoadStateListener {
    when (it.refresh) {
        is LoadState.NotLoading -> {
            Log.d(TAG, "is NotLoading")
        }
        is LoadState.Loading -> {
            Log.d(TAG, "is Loading")
        }
        is LoadState.Error -> {
            Log.d(TAG, "is Error")
        }
    }
}

Aquí está la clase de datos CombinedLoadStates, hay diferencias de actualización, adición y anteposición como se muestra en la siguiente tabla:

Es decir, si la monitorización es it.refresh, cuando se cargan la segunda página y la tercera página no se monitoriza el estado, aquí solo se toma como ejemplo it.refresh.

Hay tres valores de LoadState, a saber, NotLoading: cuando no hay acción de carga y no hay error. La carga y el error, como su nombre lo indica, corresponden a la carga y los errores de carga. Además de addLoadStateListener, también puede usar directamente loadStateFlow como método de monitoreo. Dado que el flujo es una función suspendida, debemos ejecutarlo en la co-rutina. El código es el siguiente :

lifecycleScope.launch {
    dataRecycleViewAdapter.loadStateFlow.collectLatest {
        when (it.refresh) {
            is LoadState.NotLoading -> {

            }
            is LoadState.Loading -> {

            }
            is LoadState.Error -> {

            }
        }
    }
}

A continuación, ejecutamos el ejemplo de la sección anterior. Después de que la operación sea exitosa, haga clic en el botón de consulta para mostrar los datos. Veamos la impresión de la siguiente manera:

2020-11-14 16:39:19.841 23729-23729/com.example.pagingdatademo D/MainActivity: is NotLoading
2020-11-14 16:39:24.529 23729-23729/com.example.pagingdatademo D/MainActivity: 点击了查询按钮
2020-11-14 16:39:24.651 23729-23729/com.example.pagingdatademo D/MainActivity: is Loading
2020-11-14 16:39:25.292 23729-23729/com.example.pagingdatademo D/MainActivity: is NotLoading

El primero es el estado NotLoading, porque no tenemos nada que hacer. Después de hacer clic en el botón de consulta, se convierte en el estado Loading. Debido a que los datos se están cargando, vuelven al estado NotLoading después de que finaliza la consulta. Cumple con nuestras expectativas. ¿Para qué sirve este estado? En el estado de carga, muestra una transición de barra de progreso para mejorar la experiencia del usuario, etc. Por supuesto, lo más importante es el estado de error, porque necesitamos informar al usuario en el estado de error.

Reabrimos la aplicación, desconectamos la conexión de red, volvemos a hacer clic en el botón de consulta e imprimimos el registro de la siguiente manera:

2020-11-14 16:48:25.943 26846-26846/com.example.pagingdatademo D/MainActivity: is NotLoading
2020-11-14 16:48:27.218 26846-26846/com.example.pagingdatademo D/MainActivity: 点击了查询按钮
2020-11-14 16:48:27.315 26846-26846/com.example.pagingdatademo D/MainActivity: is Loading
2020-11-14 16:48:27.322 26846-26846/com.example.pagingdatademo D/MainActivity: is Error

A lo que se debe prestar atención aquí es que Paging no nos devuelve automáticamente el estado de este error, sino que el método LoadResult.Error lo notifica después de que detectamos la excepción en el DataSource.

También necesitamos monitorear errores específicos en el estado de Error. Si no hay red, no mostrará IU de red. Si el servidor es anormal, indicará la anomalía del servidor. El código es el siguiente:

is LoadState.Error -> {
    Log.d(TAG, "is Error:")
    when ((it.refresh as LoadState.Error).error) {
        is IOException -> {
            Log.d(TAG, "IOException")
        }
        else -> {
            Log.d(TAG, "others exception")
        }
    }
}

Cuando estemos desconectados de Internet, hacemos clic en Consulta, y el registro es el siguiente:

2020-11-14 17:29:46.234 12512-12512/com.example.pagingdatademo D/MainActivity: 点击了查询按钮
2020-11-14 17:29:46.264 12512-12512/com.example.pagingdatademo D/MainActivity: 请求第1页
2020-11-14 17:29:46.330 12512-12512/com.example.pagingdatademo D/MainActivity: is Loading
2020-11-14 17:29:46.339 12512-12512/com.example.pagingdatademo D/MainActivity: is Error:
2020-11-14 17:29:46.339 12512-12512/com.example.pagingdatademo D/MainActivity: IOException

Mostrar en adaptador

Paging3 nos proporciona métodos para agregar adaptadores de fondo y cabeza, que son withLoadStateFooter, withLoadStateHeader y withLoadStateHeaderAndFooter al mismo tiempo. Aquí tomamos la adición del método tail como ejemplo.

Primero, creamos el diseño de enlace viewHolder LoadStateViewHolder es el diseño que se muestra en la parte inferior, una pantalla de carga y un botón de reintento, el diseño xml es el siguiente:

<layout>

    <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="match_parent">

        <LinearLayout
            android:id="@+id/ll_loading"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal"
            android:visibility="gone"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="正在加载数据... ..."
                android:textSize="18sp" />

            <ProgressBar
                android:layout_width="20dp"
                android:layout_height="20dp" />
        </LinearLayout>

        <Button
            android:id="@+id/btn_retry"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="加载失败,重新请求"
            android:visibility="gone"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/ll_loading" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

La solicitud de carga y el diseño de nueva solicitud están ocultos de forma predeterminada. El código LoadStateViewHolder es el siguiente:

class LoadStateViewHolder(parent: ViewGroup, var retry: () -> Void) : RecyclerView.ViewHolder(
    LayoutInflater.from(parent.context)
        .inflate(R.layout.item_loadstate, parent, false)
) {

    var itemLoadStateBindingUtil: ItemLoadstateBinding = ItemLoadstateBinding.bind(itemView)

    fun bindState(loadState: LoadState) {
        if (loadState is LoadState.Error) {
            itemLoadStateBindingUtil.btnRetry.visibility = View.VISIBLE
            itemLoadStateBindingUtil.btnRetry.setOnClickListener {
                retry()
            }
        } else if (loadState is LoadState.Loading) {
            itemLoadStateBindingUtil.llLoading.visibility = View.VISIBLE
        }

    }

}

Aquí estamos divididos en dos clases con Adapter, por lo que necesitamos pasar el padre en el adaptador como parámetro. Retry () es una función de orden superior, que es conveniente para hacer clic para reintentar y realizar la lógica de reintento en el adaptador.

bindState son los datos de configuración y se muestran diferentes IU de acuerdo con el estado del estado.

Luego creamos LoadStateFooterAdapter heredado de LoadStateAdapter, el viewHolder correspondiente es LoadStateViewHolder, el código es el siguiente:

class LoadStateFooterAdapter(private val retry: () -> Void) :
    LoadStateAdapter<LoadStateViewHolder>() {

    override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
        (holder as LoadStateViewHolder).bindState(loadState)
    }

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
        return LoadStateViewHolder(parent, retry)
    }

}

El código aquí es relativamente simple, así que no lo explicaré Finalmente, agreguemos este adaptador.

rv_data.adapter =
    dataRecycleViewAdapter.withLoadStateFooter(footer = LoadStateFooterAdapter(retry = {
        dataRecycleViewAdapter.retry()
    }))

Debe tenerse en cuenta aquí que el adaptador devuelto por withLoadStateFooter debe establecerse en la vista de reciclaje. Si escribe: dataRecycleViewAdapter.withLoadStateFooter y luego establece el adaptador de la vista de reciclaje por separado, no tendrá ningún efecto.

Aquí hacemos clic para volver a intentar el método retry () de dataRecycleViewAdapter. Después de ejecutar la primera página del programa para obtener ayuda, desconectamos la red y luego desplázate hacia abajo. El efecto es el siguiente:

De esta forma, hemos completado la visualización del estado de carga de datos en el adaptador.

Además, hay un RemoteMediator más importante en Paging3, que se utiliza para cargar mejor la base de datos de la red y la base de datos local. Tendremos la oportunidad de compartirlo por separado para todos en el futuro ~


Cuarto, actualización el 21 de noviembre de 2020

La filosofía de diseño de paging3 es que no se recomienda modificar los datos de la lista directamente; pero para operar en la fuente de datos, los cambios de la fuente de datos se actualizarán automáticamente a la lista. Al ver que muchos amigos en el área de comentarios dicen cómo eliminar y modificar elementos, aquí usamos La forma más sencilla puede ser.


Cinco, la modificación de un solo artículo

Todos sabemos que no hay una API que supervise directamente el elemento en RecycleView. Por lo general, se opera en onBindViewHolder o en la capa View a través de una devolución de llamada. Aquí, la devolución de llamada también se puede escribir como una función de orden superior. Aquí volvemos a llamar a la capa de Vista. La razón es que un socio en el área de comentarios comentó que viewModel debería ser operado, por lo que para evitar inyectar viewModel en el adaptador, podemos usar directamente una función de devolución de llamada de orden superior.

Modifique el código DataRecycleViewAdapter de la siguiente manera:

class DataRecycleViewAdapter(
    val itemUpdate: (Int, DemoReqData.DataBean.DatasBean?,DataRecycleViewAdapter) -> Unit
) :
    PagingDataAdapter<DemoReqData.DataBean.DatasBean, RecyclerView.ViewHolder>(object :
        DiffUtil.ItemCallback<DemoReqData.DataBean.DatasBean>() {

        override fun areItemsTheSame(
            oldItem: DemoReqData.DataBean.DatasBean,
            newItem: DemoReqData.DataBean.DatasBean
        ): Boolean {
            return oldItem.id == newItem.id
        }

        @SuppressLint("DiffUtilEquals")
        override fun areContentsTheSame(
            oldItem: DemoReqData.DataBean.DatasBean,
            newItem: DemoReqData.DataBean.DatasBean
        ): Boolean {
            return oldItem == newItem
        }
    }) {
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val dataBean = getItem(position)
        (holder as DataViewHolder).binding.demoReaData = dataBean
        holder.binding.btnUpdate.setOnClickListener {
            itemUpdate(position, dataBean,this)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val binding: ItemDataBinding =
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context),
                R.layout.item_data,
                parent,
                false
            )
        return DataViewHolder(binding)
    }

    inner class DataViewHolder(private val dataBindingUtil: ItemDataBinding) :
        RecyclerView.ViewHolder(dataBindingUtil.root) {
        var binding = dataBindingUtil
    }

}

Para facilitar la demostración, hemos agregado un nuevo botón para actualizar los datos en la lista de datos. El código para declarar el adaptador en la Actividad se modifica de la siguiente manera:

private var dataRecycleViewAdapter = DataRecycleViewAdapter { position, it, adapter ->
    it?.author = "黄林晴${position}"
    adapter.notifyDataSetChanged()
}

Modificamos el nombre del autor a Huang Linqing y el número de serie en el que se hace clic actualmente ejecutando una función de orden superior y luego llamamos a notifyDataSetChanged. El efecto de demostración es el siguiente:


6. Eliminar y agregar datos

Todos sabemos que antes, configuramos una Lista para el adaptador, si necesitamos eliminar o agregar, solo necesitamos cambiar la Lista, pero parece que no hay forma en Paging3, porque la fuente de datos es PagingSource, lea la introducción del sitio web oficial.

Un par PagingSource / PagingData es una instantánea del conjunto de datos. Se debe crear un nuevo PagingData / PagingData si se produce una actualización, como reordenar, insertar, eliminar o actualizar el contenido. Un PagingSource debe detectar que no puede continuar cargando su instantánea (por ejemplo, cuando la consulta de la base de datos nota que se invalida una tabla) y llamar a invalidate. Luego, se crearía un nuevo par PagingSource / PagingData para representar los datos del nuevo estado de la consulta de la base de datos.

La idea general es que si los datos cambian, se debe crear un nuevo PagingData, por lo que, por el momento, no sé cómo actualizar los datos después de eliminarlos o agregarlos sin volver a solicitarlos. Si tiene un buen plan, ¡ilumíneme!


Siete, finalmente

Para ayudar a todos a comprender profundamente los principios de los puntos de conocimiento relacionados con Android y el conocimiento relacionado con las entrevistas , también comparto un producto seco aquí.

El PDF de aprendizaje de Android + el video de la arquitectura + las notas de origen recopiladas por los grandes , así como la tecnología de arquitectura avanzada, los mapas cerebrales avanzados, los materiales especiales para entrevistas de desarrollo de Android, los materiales de arquitectura avanzada avanzada ayudan a todos a aprender y avanzar, y ahorran a todos los que buscan materiales en línea Es hora de aprender, también puede compartir con amigos a su alrededor para aprender juntos.

Si lo necesita, puede obtenerlo mediante [ Mensaje privado ] o agregar un grupo de fans: [ 1087084956 ] Consígalo

Supongo que te gusta

Origin blog.csdn.net/ajsliu1233/article/details/111499843
Recomendado
Clasificación