Google はかなり前から Jetpack のコンポーネント化を展開してきました。さまざまなコンポーネントも無限に登場します。
Jetpackにもいろいろありますが、
今日この ページングを実行してください
Pagingの出現は、リストのページング読み込みに使用されます。実際、 Smartrefreshlayoutなど、成熟した効率的なオープン ソース リスト読み込みコントロールがすでに多数存在します。しかし、Google が立ち上げたものには利点もあれば、もちろん限界もあるはずです。
まず利点について説明します。ページングを使用するには、ViewModle や LiveData などのコントロールと連携する必要があります。データ リクエストは、メモリ リークを避けるためにページのライフ サイクルを感知してバインドします。DataSource と DataSource の Factory をバインドすることも必要です。これにより、トレースなしでより多くのデータをロードでき、ユーザー エクスペリエンスをある程度向上させることができます。
主なプロセスは次のとおりです。
1: PositionalDataSource をカスタマイズします。内部の機能はデータ ページングを要求することです。
2: DataSource.Factory をカスタマイズし、PositionalDataSource を LiveData にバインドします
3: アクティビティは ViewModel をカスタマイズし、PositionalDataSource と Factory をバインドし、ViewModel にデータの変更を認識させます。
4: ViewModel はデータの変更を認識し、PagesListAdapter の submitList を更新します。
まず、これらの依存関係のインポートを見てください。
implementation "androidx.paging:paging-runtime:2.0.0"
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "android.arch.lifecycle:extensions:1.1.1"
【1】まずアクティビティのコードを見てください。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myPagedListAdapter = MyPagedListAdapter()
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = myPagedListAdapter
/**
* ViewModel 绑定 Activity 生命周期
* */
val myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
/**
* ViewModel 感知数据变化,更新 Adapter 列表
* */
myViewModel.getConvertList().observe(this, Observer {
myPagedListAdapter.submitList(it)
})
}
}
[2] アダプターのコードを見てください。
ここではPagedListAdapter が使用されています。実際には、RecyclerView.Adapter を継承しているため、使用中のRecyclerView.Adapterとあまり違いはありません。DiffUtil.ItemCallback を実装するための内部ニーズがもう 1 つあります。DiffUtil.ItemCallback の具体的な原理についてはここでは触れませんし、ブロガーも詳しく調べていませんが、データを比較するために使用されます。その内部実装メソッドは基本的に死ぬまで書くことができます。
class MyPagedListAdapter extends PagedListAdapter<MyDataBean, MyViewHolder> {
private static DiffUtil.ItemCallback<MyDataBean> DIFF_CALLBACK =
new DiffUtil.ItemCallback<MyDataBean>() {
@Override
public boolean areItemsTheSame(MyDataBean oldConcert, MyDataBean newConcert) {
// 比较两个Item数据是否一样的,可以简单用 Bean 的 hashCode来对比
return oldConcert.hashCode() == newConcert.hashCode();
}
@Override
public boolean areContentsTheSame(MyDataBean oldConcert,
MyDataBean newConcert) {
// 写法基本上都这样写死即可
return oldConcert.equals(newConcert);
}
};
public MyPagedListAdapter() {
// 通过 构造方法 设置 DiffUtil.ItemCallback
super(DIFF_CALLBACK);
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_my, parent, false);
return new MyViewHolder(inflate);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
// getItem() 是 PagedListAdapter 内部方法,通过此方法可以获取到对应位置Item 的数据
holder.bindData(getItem(position));
}
}
ちなみにViewHolderとMyDataBeanも貼り付けます
ビューホルダー :
class MyViewHolder(val itemV: View) : RecyclerView.ViewHolder(itemV) {
fun bindData(data: MyDataBean) {
itemV.findViewById<TextView>(R.id.tvNum).text = data.position.toString()
if (data.position % 2 == 0){
itemV.findViewById<TextView>(R.id.tvNum).setBackgroundColor(itemV.context.resources.getColor(R.color.colorAccent))
}else{
itemV.findViewById<TextView>(R.id.tvNum).setBackgroundColor(itemV.context.resources.getColor(R.color.colorPrimaryDark))
}
}
}
MyDataBean :
data class MyDataBean(val position: Int)
[3] DataSource コードを見てください。
PositionalDataSource、内部の関数はデータ ページングを要求することです。
次の 2 つのメソッドを実装する必要があります。
loadInitial() : ページを初めて開いたとき、データを取得するためにこのメソッドをコールバックする必要があります。
loadRange(): 初期化データが利用可能になった後、スライド時にデータをロードする必要がある場合、このメソッドが呼び出されます。
class MyDataSource : PositionalDataSource<MyDataBean>() {
/**
* 第一次打开页面,需要回调此方法来获取数据
* */
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<MyDataBean>) {
// 获取网络数据
val list = getData(params.requestedStartPosition, params.pageSize)
/**
* 这个方法是返回数据,让 绑定ViewModel 感知。 这里要用对方法
* @param1 数据列表
* @param2 数据为起始位置
* @param3 数据列表总长度,这个一定要设置好哦,如果设置了50,
* 当列表的长度为50时,列表再也无法出发 loadRange() 去加载更多了
* 如果不知道列表总长度,可以设置 Int 的最大值 999999999
* 这里设置 10000
* */
callback.onResult(list, 0, 10000)
}
/**
* 当有了初始化数据之后,滑动的时候如果需要加载数据的话,会调用此方法。
* */
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<MyDataBean>) {
/**
* params.startPosition 列表需要从 startPosition 加载更多
* params.loadSize 列表需要从 startPosition 加载长度 为 loadSize的数据
* */
val list = getData(params.startPosition, params.loadSize)
callback.onResult(list)
}
/**
* 模拟网络获取数据
* */
private fun getData(startPosition: Int, pageSize: Int): MutableList<MyDataBean> {
Handler(Looper.getMainLooper()).post {
Toast.makeText(
MyApplication.instant,
"加载数据 从 $startPosition 加载到 ${startPosition + pageSize}",
Toast.LENGTH_SHORT
).show()
}
val list = mutableListOf<MyDataBean>()
for (i in startPosition until startPosition + pageSize) {
list.add(MyDataBean(i))
}
return list
}
}
[4] DataSource.Factory コードを見てください。
PositionalDataSource を LiveData にバインドする
class DataFactory: DataSource.Factory<Int, MyDataBean>() {
private val mSourceLiveData: MutableLiveData<MyDataSource> =
MutableLiveData<MyDataSource>()
override fun create(): DataSource<Int, MyDataBean> {
val myDataSource = MyDataSource()
mSourceLiveData.postValue(myDataSource)
return myDataSource
}
}
[5] 最後に、ViewModel コードを確認します。
class MyViewModel : ViewModel() {
private var mDataSource : DataSource<Int, MyDataBean>
private var mDataList: LiveData<PagedList<MyDataBean>>
init {
// 把 PositionalDataSource 和 Factory 绑定,让 ViewModel 感知数据的变化
var dataFactory = DataFactory()
mDataSource = dataFactory.create()
/**
* @param1 dataFactory 设定 dataFactory
* @param2 设定每一次加载的长度
* 这个和 PositionalDataSource 回调方法 loadSize 一致的
* */
mDataList = LivePagedListBuilder(dataFactory, 20).build()
}
// 暴露方法,让Activity 感知数据变化,去驱动 Adapter更新列表
fun getConvertList(): LiveData<PagedList<MyDataBean>> {
return mDataList
}
}
[5] 最後に、アクティビティのコードを確認します。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myPagedListAdapter = MyPagedListAdapter()
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = myPagedListAdapter
/**
* ViewModel 绑定 Activity 生命周期
* */
val myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
/**
* ViewModel 感知数据变化,更新 Adapter 列表
* */
myViewModel.getConvertList().observe(this, Observer {
myPagedListAdapter.submitList(it)
})
}
}
activity_main.xmlのコードを貼り付けます
<?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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
実行して効果を確認します。
問題なく正常に実行されます。
冒頭で、Paging には欠点があると述べましたが、実際には、Paging にはプルダウン更新がなく、より多くの機能をロードするためのプルアップのみがあります。多くの上場の場合、これでは十分ではありません。
ただし、プルアップしてさらに読み込むだけの場合は、Google が提供しているため、Paging を使用することをお勧めします。
上記のコードテストには問題ありませんが、ご不明な点がございましたらメッセージを残してください。