【Android】Jetpack全组件实战开发短视频应用App(十一)

前言

项目地址
我们这一篇将使用Paging做分页加载和网络请求数据展示

分页加载

我们上一篇已经封装完Fragment的基类,这一次我们一样,先封装相关的ViewModel

/**
 * ViewModel基类
 */
public abstract class AbsViewModel<T> extends ViewModel {
    
}

我们先新建一个AbsViewModel继承自ViewModel,然后我们在它的构造方法中初始化一些数据,主要是PagedListLiveData

 PagedList.Config config = new PagedList.Config.Builder()
                .setPageSize(10)
                .setInitialLoadSizeHint(12)
                // .setMaxSize(100);
                // .setEnablePlaceholders(false)
                // .setPrefetchDistance()
                .build();

简单解释下setPageSize表示每页请求个数,setInitialLoadSizeHint表示第一次请求几条,setMaxSize表示这个列表一共有多少条数据,这个一般用不到,setEnablePlaceholders这个表示是否需要占位图,setPrefetchDistance这个表示具体屏幕底部多少个时加载下一页
然后就是LiveData

 LiveData<PagedList<T>> pagedListLiveData = new LivePagedListBuilder(mFactory, config)
                .setInitialLoadKey(0)
                .setBoundaryCallback(mCallback)
                .build();

这个config就是我们上面的PagedList.Config,这个mFactoryDataSource.Factory,

 DataSource.Factory mFactory = new DataSource.Factory() {
        @NonNull
        @Override
        public DataSource create() {
            if (mDataSource == null || mDataSource.isInvalid()) {
                mDataSource = createDataSource();
            }
            return mDataSource;
        }
    };

这里我们把创建DataSource方法抽象画,让子类去实现

    /**
     * 创建DataSource
     */
    public abstract DataSource createDataSource();
 //PagedList数据被加载 情况的边界回调callback
    //但 不是每一次分页 都会回调这里,具体请看 ContiguousPagedList#mReceiver#onPageResult
    //deferBoundaryCallbacks
    PagedList.BoundaryCallback<T> mCallback = new PagedList.BoundaryCallback<T>() {
        @Override
        public void onZeroItemsLoaded() {
            //新提交的PagedList中没有数据
            mBoundaryPageData.postValue(false);
        }

        @Override
        public void onItemAtFrontLoaded(@NonNull T itemAtFront) {
            //新提交的PagedList中第一条数据被加载到列表上
            mBoundaryPageData.postValue(true);
        }

        @Override
        public void onItemAtEndLoaded(@NonNull T itemAtEnd) {
            //新提交的PagedList中最后一条数据被加载到列表上
        }
    };

整个类贴下

/**
 * ViewModel基类
 */
public abstract class AbsViewModel<T> extends ViewModel {

    private DataSource mDataSource;
    private LiveData<PagedList<T>> mPageData;

    private MutableLiveData<Boolean> mBoundaryPageData = new MutableLiveData<>();

    public AbsViewModel() {

        PagedList.Config config = new PagedList.Config.Builder()
                .setPageSize(10)
                .setInitialLoadSizeHint(12)
                // .setMaxSize(100);
                // .setEnablePlaceholders(false)
                // .setPrefetchDistance()
                .build();

        mPageData = new LivePagedListBuilder(mFactory, config)
                .setInitialLoadKey(0)
                .setBoundaryCallback(mCallback)
                .build();
    }


    public LiveData<PagedList<T>> getPageData() {
        return mPageData;
    }

    public DataSource getDataSource() {
        return mDataSource;
    }

    public LiveData<Boolean> getBoundaryPageData() {
        return mBoundaryPageData;
    }

    //PagedList数据被加载 情况的边界回调callback
    //但 不是每一次分页 都会回调这里,具体请看 ContiguousPagedList#mReceiver#onPageResult
    //deferBoundaryCallbacks
    PagedList.BoundaryCallback<T> mCallback = new PagedList.BoundaryCallback<T>() {
        @Override
        public void onZeroItemsLoaded() {
            //新提交的PagedList中没有数据
            mBoundaryPageData.postValue(false);
        }

        @Override
        public void onItemAtFrontLoaded(@NonNull T itemAtFront) {
            //新提交的PagedList中第一条数据被加载到列表上
            mBoundaryPageData.postValue(true);
        }

        @Override
        public void onItemAtEndLoaded(@NonNull T itemAtEnd) {
            //新提交的PagedList中最后一条数据被加载到列表上
        }
    };

    DataSource.Factory mFactory = new DataSource.Factory() {
        @NonNull
        @Override
        public DataSource create() {
            if (mDataSource == null || mDataSource.isInvalid()) {
                mDataSource = createDataSource();
            }
            return mDataSource;
        }
    };

    /**
     * 创建DataSource
     */
    public abstract DataSource createDataSource();


    /**
     * 可以在这个方法里 做一些清理 的工作
     */
    @Override
    protected void onCleared() {

    }
}

然后让我们首页的HomeViewModel继承这个类

public class HomeViewModel extends AbsViewModel<Feed> {

    @Override
    public DataSource createDataSource() {
        return null;
    }
}

我们看下Paging内置那些DataSource
【Android】Jetpack全组件实战开发短视频应用App(十一)_第1张图片
这里我们使用ItemKeyedDataSource

ItemKeyedDataSource<Integer,Feed> mDataSource=new ItemKeyedDataSource<Integer, Feed>() {
        @Override
        public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Feed> callback) {
            //加载初始化数据的
        }

        @Override
        public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Feed> callback) {
            //向后加载分页数据的
        }

        @Override
        public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Feed> callback) {
            //能够向前加载数据的
        }

        @NonNull
        @Override
        public Integer getKey(@NonNull Feed item) {
            return item.id;
        }
    };

加载数据

我们加载数据就需要网络请求,所以我们需要先初始化我们的ApiService

/**
 * 项目在线Api文档地址:http://123.56.232.18:8080/serverdemo/swagger-ui.html#/
 */
public class JokeApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ApiService.init("http://123.56.232.18:8080/serverdemo", null);
    }
}
    <application
        android:name=".JokeApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>

        <activity android:name=".ui.publish.PublishActivity" />
    application>

manifest>

接着我们就可以在HomeViewModel中写网络请求了,我们在loadInitial方法中加载第一页数据

  @Override
        public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Feed> callback) {
            //加载初始化数据的
            loadData(0, callback);
            witchCache = false;
        }

private void loadData(int key, ItemKeyedDataSource.LoadCallback<Feed> callback) {
        Request request = ApiService.get("/feeds/queryHotFeedsList")
                .addParam("feedType", null)
                .addParam("userId", 0)
                .addParam("feedId", key)
                .addParam("pageCount", 10)
                .responseType(new TypeReference<ArrayList<Feed>>() {
                }.getType());

        if (witchCache) {
            request.cacheStrategy(Request.CACHE_ONLY);
            request.execute(new JsonCallback() {
                @Override
                public void onCacheSuccess(ApiResponse response) {
                }
            });
        }
        Request netRequest = null;
        try {
            netRequest = witchCache ? request.clone() : request;
            netRequest.cacheStrategy(key == 0 ? Request.NET_CACHE : Request.NET_ONLY);
            ApiResponse<List<Feed>> response = netRequest.execute();
            List<Feed> data = response.body == null ? Collections.emptyList() : response.body;

            callback.onResult(data);
            if (key > 0) {
                //通过BoundaryPageData发送数据 告诉UI层 是否应该主动关闭上拉加载分页的动画
                ((MutableLiveData) getBoundaryPageData()).postValue(data.size() > 0);
            }
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }

我们需要在HomeFragment中触发数据加载,也就是需要HomeModelPageData触发,但是每个列表Fragment都需要这个触发条件,所以我们把它放在AbsListFragment中,ViewModel我们通过泛型传递给父类,父类解析泛型参数实例化这个ViewModel

public abstract class AbsListFragment<T, M extends AbsViewModel<T>> extends Fragment
 private void genericViewModel() {
        //利用 子类传递的 泛型参数实例化出absViewModel 对象。
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Type[] arguments = type.getActualTypeArguments();
        if (arguments.length > 1) {
            Type argument = arguments[1];
            Class modelClaz = ((Class) argument).asSubclass(AbsViewModel.class);
            mViewModel = (M) ViewModelProviders.of(this).get(modelClaz);

            //触发页面初始化数据加载的逻辑
            mViewModel.getPageData().observe(this, pagedList -> submitList(pagedList));

            //监听分页时有无更多数据,以决定是否关闭上拉加载的动画
            mViewModel.getBoundaryPageData().observe(this, hasData -> finishRefresh(hasData));
        }
    }

然后我们改造下我们的HomeFragment

@FragmentDestination(pageUrl = "main/tabs/home" ,asStarter = true)
public class HomeFragment extends AbsListFragment<Feed,HomeViewModel> {
    private static final String TAG = "HomeFragment";

    private String mFeedType;
    
    @Override
    public PagedListAdapter getAdapter() {
        mFeedType = getArguments() == null ? "all" : getArguments().getString("feedType");
        return new FeedAdapter(getContext(), mFeedType);
    }
}

我们运行下看看效果

你可能感兴趣的:(Jetpack)