使用Paging Library獲取網路資料

順其自然55發表於2017-11-12

之前看architecture時候,看見了paging library這個庫,因為谷歌給出的案例是配合Room使用,所以一直沒去深究,今天偶然看見了別人的部落格,發現願挨還可以配合網路資料使用,所以今天就來趴一趴用法.

首先放上官網地址:https://developer.android.google.cn/topic/libraries/architecture/paging.html,

還有個中譯:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0920/8533.html

然後就是kotlin的:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0922/8539.html

現在開始,首先配置好gradle,在標準的lifecycle基礎上增加paging庫,這是我的 gradle配置:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation "android.arch.lifecycle:extensions:1.0.0"
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation 'com.android.support:recyclerview-v7:26.1.0'
    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
    implementation "android.arch.paging:runtime:1.0.0-alpha3"
    implementation 'com.github.bumptech.glide:glide:4.3.1'
}複製程式碼

然後現在是]我們用來配合recyclerview使用:

新建MainActivity,首先寫佈局,佈局很簡單,就是個recyclerView

 activity_main.xml:

<android.support.constraint.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="com.yzl.gank.pagetest.ui.mian.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>複製程式碼

然後開始寫data資料類

MainData:

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.paging.DataSource;
import android.arch.paging.LivePagedListProvider;
import android.arch.paging.PagedList;
import android.arch.paging.TiledDataSource;
import android.support.annotation.NonNull;

import com.yzl.gank.pagetest.bean.GankData;
import com.yzl.gank.pagetest.http.AppService;
import com.yzl.gank.pagetest.http.BaseResponse;
import com.yzl.gank.pagetest.http.RetrofitApi;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import retrofit2.Response;

/**
 * Created by 10488 on 2017-11-11.
 */

public class MainData extends AndroidViewModel {

    /**
     * 每次需要10個資料.
     */
    private static final int NEED_NUMBER = 10;

    /**
     * 福利第一頁.
     */
    private static final int PAGE_FIRST = 1;

    /**
     * 分頁.
     */
    private int mPage = PAGE_FIRST;


    /**
     * 列表資料.
     */
    private LiveData<PagedList<GankData>> mDataLiveData;

    public MainData(@NonNull Application application) {
        super(application);
    }

    public LiveData<PagedList<GankData>> getDataLiveData() {
        initPageList();
        return mDataLiveData;
    }

    /**
     * 初始化pageList.
     */
    private void initPageList() {
        //獲取dataSource,列表資料都從這裡獲取,
        final TiledDataSource<GankData> tiledDataSource = new TiledDataSource<GankData>() {

            /**
             * 需要的總個數,如果數量不定,就傳COUNT_UNDEFINED.
             */
            @Override
            public int countItems() {
                return DataSource.COUNT_UNDEFINED;
            }

            /**
             * 返回需要載入的資料.
             * 這裡是線上程非同步中執行的,所以我們可以同步請求資料並且返回
             * @param startPosition 現在第幾個資料
             * @param count 載入的資料數量
             */
            @Override
            public List<GankData> loadRange(int startPosition, int count) {
                List<GankData> gankDataList = new ArrayList<>();

                //這裡我們用retrofit獲取資料,每次獲取十條資料,數量不為空,則讓mPage+1
                try {
                    Response<BaseResponse<List<GankData>>> execute = RetrofitApi.getInstance().mRetrofit.create(AppService.class)
                            .getWelfare1(mPage, NEED_NUMBER).execute();
                    gankDataList.addAll(execute.body().getResults());

                    if (!gankDataList.isEmpty()) {
                        mPage++;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                return gankDataList;
            }
        };

        //這裡我們建立LiveData<PagedList<GankData>>資料,
        mDataLiveData = new LivePagedListProvider<Integer, GankData>() {

            @Override
            protected DataSource<Integer, GankData> createDataSource() {
                return tiledDataSource;
            }
        }.create(0, new PagedList.Config.Builder()
                .setPageSize(NEED_NUMBER) //每次載入的資料數量
                //距離本頁資料幾個時候開始載入下一頁資料(例如現在載入10個資料,設定prefetchDistance為2,則滑到第八個資料時候開始載入下一頁資料).
                .setPrefetchDistance(NEED_NUMBER)
                //這裡設定是否設定PagedList中的佔位符,如果設定為true,我們的資料數量必須固定,由於網路資料數量不固定,所以設定false.
                .setEnablePlaceholders(false)
                .build());
    }
}複製程式碼

這裡我們需要建立pageList的例項,pageList中有create方法

PageList:

@NonNull
private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
        @NonNull Executor mainThreadExecutor,
        @NonNull Executor backgroundThreadExecutor,
        @NonNull Config config,
        @Nullable K key) {
    if (dataSource.isContiguous() || !config.mEnablePlaceholders) {
        if (!dataSource.isContiguous()) {
            //noinspection unchecked
            dataSource = (DataSource<K, T>) ((TiledDataSource<T>) dataSource).getAsContiguous();
        }
        ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
        return new ContiguousPagedList<>(contigDataSource,
                mainThreadExecutor,
                backgroundThreadExecutor,
                config,
                key);
    } else {
        return new TiledPagedList<>((TiledDataSource<T>) dataSource,
                mainThreadExecutor,
                backgroundThreadExecutor,
                config,
                (key != null) ? (Integer) key : 0);
    }
}複製程式碼

可見我們需要四個資料,但是LivePagedListProvider幫我們預設配置了,現在我們只是修改下我們需要的配置.

接下來看DataSource,DataSourece上有註釋說明,大概意思就是叫我們按需求繼承KeyedDataSource或者TiledDataSource,目前我們先用TiledDataSource,KeyedDataSource幾個 方法在網路獲取資料有點多餘.

由於我們設定了PlaceHolders為false,所以我們最終的DataSource是ContiguousDataSource.

資料這邊我們就設定完了,接下來看Activity.

首先我們先來看RecyclerView需要的adapter:

MyAdapter:

import android.arch.paging.PagedListAdapter;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.recyclerview.extensions.DiffCallback;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.yzl.gank.pagetest.R;
import com.yzl.gank.pagetest.bean.GankData;

/**
 * Created by 10488 on 2017-11-11.
 */

public class MyAdapter extends PagedListAdapter<GankData, BaseViewHolder> {

    private Context mContext;

    public MyAdapter(@NonNull DiffCallback<GankData> diffCallback) {
        super(diffCallback);
    }

    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        mContext = parent.getContext();
        return new BaseViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_main, parent, false));
    }

    @Override
    public void onBindViewHolder(BaseViewHolder holder, int position) {
        TextView tvName = holder.getView(R.id.tv_name);
        ImageView imageView = holder.getView(R.id.iv);

        GankData data = getItem(position);
        tvName.setText(data.getCreatedAt());
        Glide.with(mContext).load(data.getUrl()).into(imageView);
    }
}複製程式碼

這裡我們需要使用庫提供的ListAdapter,其他寫法和普通的adapter一致,注意構造方法需要我們傳入DiffCallBack,是不是想到了之前RecyclerView的 工具類DiffUtil呢(如果不知道這個類的話,該去看看別人的部落格補下知識了).當然這個diffUtil不是recyclerView那個,這個是page庫自帶的,

我們來看下DiffCallBack:

DiffCallBack:

public abstract class DiffCallback<T> {
    /**
     * Called to decide whether two objects represent the same item.
     *
     * @param oldItem The item in the old list.
     * @param newItem The item in the new list.
     * @return True if the two items represent the same object or false if they are different.
     * @see android.support.v7.util.DiffUtil.Callback#areItemsTheSame(int, int)
     */
    public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);

    /**
     * Called to decide whether two items have the same data. This information is used to detect if
     * the contents of an item have changed.
     *
     * @param oldItem The item in the old list.
     * @param newItem The item in the new list.
     * @return True if the contents of the items are the same or false if they are different.
     * @see android.support.v7.util.DiffUtil.Callback#areContentsTheSame(int, int)
     */
    public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);

    /**
     * Called to get a change payload between an old and new version of an item.
     *
     * @see android.support.v7.util.DiffUtil.Callback#getChangePayload(int, int)
     */
    @SuppressWarnings("WeakerAccess")
    public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) {
        return null;
    }
}複製程式碼

好吧,除了少了兩個get方法,和DiffUtil的CallBack一模一樣.

好了,現在來看我們的activity

MainActivity:

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.arch.paging.PagedList;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.recyclerview.extensions.DiffCallback;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.yzl.gank.pagetest.R;
import com.yzl.gank.pagetest.adapter.MyAdapter;
import com.yzl.gank.pagetest.bean.GankData;

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRv;
    private MainData mMainData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mMainData = ViewModelProviders.of(this).get(MainData.class);
    initView();
    initList();
}

    private void initView() {
        mRv = findViewById(R.id.rv);
    }

    private void initList() {

        final MyAdapter myAdapter = new MyAdapter(new DiffCallback<GankData>() {

            @Override
            public boolean areItemsTheSame(@NonNull GankData oldUser, @NonNull GankData newUser) {
                return oldUser.get_id().equals(newUser.get_id());
            }

            @Override
            public boolean areContentsTheSame(@NonNull GankData oldUser, @NonNull GankData newUser) {
                return oldUser.getUrl().equals(newUser.getUrl());
            }
        });

        mRv.setAdapter(myAdapter);
        mRv.setLayoutManager(new LinearLayoutManager(this));

        mMainData.getDataLiveData().observe(this, new Observer<PagedList<GankData>>() {
            @Override
            public void onChanged(@Nullable PagedList<GankData> gankData) {
                myAdapter.setList(gankData);
            }
        });
    }
}複製程式碼

每次observer回撥時候執行myAdapter.setList(gankData),然後會去計算新舊資料的不同點,進行adapter的重新整理.

好了,基礎用法就結束了,來看看執行效果,雖然從網路獲取資料,但是滑動完全無感知載入更多.

至於更多的用法就要看怎麼去探索了

使用Paging Library獲取網路資料

本文只是pageLibrary庫對於網路的簡單使用,目前該庫還處於測試階段,所以不適合加入到正式專案中,所以大家可以在自己demo中寫下,順便看看原始碼,看看谷歌會給我們怎樣的設計思路.

最後放上文章demo地址:https://github.com/a1048823898/page_tes


相關文章