前言
项目地址
我们这一篇将使用Paging
做分页加载和网络请求数据展示
分页加载
我们上一篇已经封装完Fragment
的基类,这一次我们一样,先封装相关的ViewModel
/**
* ViewModel基类
*/
public abstract class AbsViewModel<T> extends ViewModel {
}
我们先新建一个AbsViewModel
继承自ViewModel
,然后我们在它的构造方法中初始化一些数据,主要是PagedList
和LiveData
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
,这个mFactory
是DataSource.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
这里我们使用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
方法中加载第一页数据
![](/qrcode.jpg)
@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
中触发数据加载,也就是需要HomeModel
的PageData
触发,但是每个列表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);
}
}
我们运行下看看效果