-
效果
-
引入
api 'com.alibaba:fastjson:1.2.70'
//material组件
api 'com.google.android.material:material:1.2.0-alpha06'
//页面刷新组件
api 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.2'
api 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.2'
//paging分页组件
api 'androidx.paging:paging-runtime:2.1.2'
- 构建列表布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.scwang.smartrefresh.layout.footer.ClassicsFooter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.scwang.smartrefresh.layout.header.ClassicsHeader
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
<com.paging.study.EmptyView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="100dp" />
</FrameLayout>
<data>
</data>
</layout>
- pading 通用配置ViewModel
package com.paging.study.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.paging.DataSource;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
/**
* pading 通用配置
*
* @param <T>
*/
public abstract class AbsViewModel<T> extends AndroidViewModel {
protected PagedList.Config config;
private DataSource dataSource;
private LiveData<PagedList<T>> pageData;
/**
* 分页,当前第一页开始
*/
protected int pageCurrent = 1;
private MutableLiveData<Boolean> boundaryPageData = new MutableLiveData<>();
DataSource.Factory factory = new DataSource.Factory() {
@NonNull
@Override
public DataSource create() {
if (dataSource == null || dataSource.isInvalid()) {
dataSource = createDataSource();
}
return dataSource;
}
};
public AbsViewModel(@NonNull Application application) {
super(application);
config = new PagedList.Config.Builder()
//每次分页时加载的数量
.setPageSize(10)
//第一次加载的数量
.setInitialLoadSizeHint(12)
//这个列表一共有多少数据(一般不用)
// .setMaxSize(100);
//数据未出现用占位符代替
// .setEnablePlaceholders(false)
//距离底部多少个item的时候开始加载下一页数据(默认pageSize大小)
//.setPrefetchDistance(2)
.build();
pageData = new LivePagedListBuilder(factory, config)
//初始化数据时需要传入的key
.setInitialLoadKey("0")
//.setFetchExecutor()
//监听pading数据加载的状态
.setBoundaryCallback(callback)
.build();
}
/**
* paging分页监听
*
* @return
*/
public LiveData<PagedList<T>> getPageData() {
return pageData;
}
/**
* 数据源
*
* @return
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* 判断列表内容状态,是否有数据
*
* @return
*/
public MutableLiveData<Boolean> getBoundaryPageData() {
return boundaryPageData;
}
//PagedList数据被加载 情况的边界回调callback
//但 不是每一次分页 都会回调这里,具体请看 ContiguousPagedList#mReceiver#onPageResult
//deferBoundaryCallbacks
PagedList.BoundaryCallback<T> callback = new PagedList.BoundaryCallback<T>() {
@Override
public void onZeroItemsLoaded() {
//新提交的PagedList中没有数据
boundaryPageData.postValue(false);
}
@Override
public void onItemAtFrontLoaded(@NonNull T itemAtFront) {
//新提交的PagedList中第一条数据被加载到列表上
boundaryPageData.postValue(true);
}
@Override
public void onItemAtEndLoaded(@NonNull T itemAtEnd) {
//新提交的PagedList中最后一条数据被加载到列表上
}
};
protected abstract DataSource createDataSource();
//可以在这个方法里 做一些清理 的工作
@Override
protected void onCleared() {
super.onCleared();
}
}
- 创建列表加载页面父类(这里Fragment为例)
package com.paging.study;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.paging.PagedList;
import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.paging.study.viewmodel.AbsViewModel;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import cn.yumakeji.jetpackroomstudy.databinding.LayoutRefreshViewBinding;
public abstract class AbsListFragment<T, M extends AbsViewModel<T>> extends Fragment implements OnRefreshListener, OnLoadMoreListener {
protected Context mContext;
protected Activity mActivity;
private LayoutRefreshViewBinding binding;
protected RecyclerView mRecyclerView;
protected SmartRefreshLayout mRefreshLayout;
protected EmptyView mEmptyView;
protected PagedListAdapter<T, RecyclerView.ViewHolder> adapter;
protected M mViewModel;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mContext = getContext();
mActivity = getActivity();
/**
* DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
* 区别
* LayoutRefreshViewBinding直接可以拿到当前布局的填充器
*/
binding = LayoutRefreshViewBinding.inflate(inflater, container, false);
mRecyclerView = binding.recyclerView;
mRefreshLayout = binding.refreshLayout;
mEmptyView = binding.emptyView;
//配置
mRefreshLayout.setEnableRefresh(true);
mRefreshLayout.setEnableLoadMore(true);
mRefreshLayout.setOnRefreshListener(this);
mRefreshLayout.setOnLoadMoreListener(this);
adapter = getAdapter();
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
mRecyclerView.setItemAnimator(null);
onCreateViewFrame(inflater, container, savedInstanceState);
genericViewModel();
return binding.getRoot();
}
protected abstract void onCreateViewFrame(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
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) new ViewModelProvider(this).get(modelClaz);
//触发页面初始化数据加载的逻辑
mViewModel.getPageData().observe(getViewLifecycleOwner(), pagedList -> submitList(pagedList));
//监听分页时有无更多数据,以决定是否关闭上拉加载的动画
mViewModel.getBoundaryPageData().observe(getViewLifecycleOwner(), hasData -> finishRefresh(hasData));
}
}
/**
* 添加数据到adapter
*
* @param result
*/
public void submitList(PagedList<T> result) {
//只有当新数据集合大于0 的时候,才调用adapter.submitList
//否则可能会出现 页面----有数据----->被清空-----空布局
if (result.size() > 0) {
adapter.submitList(result);
}
finishRefresh(result.size() > 0);
}
/**
* 刷新完成
*
* @param hasData
*/
public void finishRefresh(boolean hasData) {
PagedList<T> currentList = adapter.getCurrentList();
hasData = hasData || currentList != null && currentList.size() > 0;
RefreshState state = mRefreshLayout.getState();
if (state.isFooter && state.isOpening) {
mRefreshLayout.finishLoadMore();
} else if (state.isHeader && state.isOpening) {
mRefreshLayout.finishRefresh();
}
if (hasData) {
mEmptyView.setVisibility(View.GONE);
} else {
mEmptyView.setVisibility(View.VISIBLE);
}
}
public abstract PagedListAdapter getAdapter();
}
- 创建ViewModel处理类
package com.paging.study.viewmodel;
import android.annotation.SuppressLint;
import android.app.Application;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.paging.DataSource;
import androidx.paging.ItemKeyedDataSource;
import com.paging.study.bean.Teacher;
import com.paging.study.http.PagingHttpCallback;
import com.paging.study.http.PagingHttpClient;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public class BlankViewModel extends AbsViewModel<Teacher.RecordsBean> {
public BlankViewModel(@NonNull Application application) {
super(application);
}
/**
* 同步位置标记,防止paging和我们自己的分页重复
*/
private AtomicBoolean loadAfter = new AtomicBoolean();
@Override
protected DataSource createDataSource() {
return new TeacherDataSource();
}
/**
* DataSource<Key,Value>数据源:
* key 对应加载数据的条件信息,value对应数据实体类
* <p>
* PageKeyedDataSource<Key,Value>:
* 适用于目标数据根据页面信息请求数据的场景
* <p>
* ItemKeyedDataSource<Key,Value>:
* 适用于目标数据的加载依赖特定item的信息
* <p>
* PositionalDataSource<Key,Value>:
* 适用于目标数据总数固定,通过特定的位置加载数据
*/
class TeacherDataSource extends ItemKeyedDataSource<String, Teacher.RecordsBean> {
@Override
public void loadInitial(@NonNull LoadInitialParams<String> params,
@NonNull LoadInitialCallback<Teacher.RecordsBean> callback) {
pageCurrent = 1;
//加载初始化数据的
loadData("0", callback);
}
@Override
public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<Teacher.RecordsBean> callback) {
//向后加载分页数据的
loadData(params.key, callback);
}
@Override
public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<Teacher.RecordsBean> callback) {
callback.onResult(Collections.<Teacher.RecordsBean>emptyList());
//能够向前加载数据的(例如初始化进入加载的是第3页,向上翻的时候加载第二第一页)
}
@NonNull
@Override
public String getKey(@NonNull Teacher.RecordsBean item) {
//通过最后一条item的信息加载数据
return item.getPaintingId();
}
}
/**
* 进行网络请求
*
* @param key
* @param callback
*/
private void loadData(String key, ItemKeyedDataSource.LoadCallback<Teacher.RecordsBean> callback) {
if (!key.equals("0")) {
loadAfter.set(true);
}
/**
* 当前线程为子线程
*
* 这里有坑,一定要使用同步请求,因为方法执行完毕是,监听者就收到监听
*/
Map<String, Object> map = new ArrayMap<>();
map.put("searchContent", "");
map.put("size", config.pageSize);
map.put("current", pageCurrent);
PagingHttpClient.getInstance()
.get("home/paintingList", map, new PagingHttpCallback<Teacher>() {
@Override
protected void onNext(Teacher data) {
//标记分页页数
if (data.getRecords() != null && data.getRecords().size() > 0) {
pageCurrent = data.getCurrent() + 1;
}
callback.onResult(data.getRecords());
if (!key.equals("0")) {
//通过LiveData发送数据,告诉UI层 是否应该主动关闭上拉加载分页的动画
getBoundaryPageData().postValue(data.getRecords().size() > 0);
loadAfter.set(false);
}
Log.e("TTT", "data===>" + data.toString());
}
@Override
protected void onFail(int code, String message) {
}
});
}
/**
* 自己处理分页
*
* @param id
* @param callback
*/
@SuppressLint("RestrictedApi")
public void loadAfter(String id, ItemKeyedDataSource.LoadCallback<Teacher.RecordsBean> callback) {
if (loadAfter.get()) {
callback.onResult(Collections.emptyList());
return;
}
ArchTaskExecutor.getIOThreadExecutor().execute(new Runnable() {
@Override
public void run() {
loadData(id, callback);
}
});
}
}
- adapter适配器(注意JavaBean一定要重写equals方法)
package com.paging.study.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.paging.study.bean.Teacher;
import cn.yumakeji.jetpackroomstudy.databinding.LayoutTeacherTypeBinding;
import cn.yumakeji.lib_common.global.AppGlobals;
public class HomeAdapter extends PagedListAdapter<Teacher.RecordsBean, HomeAdapter.ViewHolder> {
protected Context mContext;
public HomeAdapter(Context context) {
super(new DiffUtil.ItemCallback<Teacher.RecordsBean>() {
@Override
public boolean areItemsTheSame(@NonNull Teacher.RecordsBean oldItem, @NonNull Teacher.RecordsBean newItem) {
//判断item是否相等(这里根据id来判断)
return oldItem.getPaintingId() == newItem.getPaintingId();
}
@Override
public boolean areContentsTheSame(@NonNull Teacher.RecordsBean oldItem, @NonNull Teacher.RecordsBean newItem) {
//判断内容是否相等(重写equals方法)
return oldItem.equals(newItem);
}
});
this.mContext = context;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutTeacherTypeBinding binding = LayoutTeacherTypeBinding.inflate(LayoutInflater.from(mContext), parent, false);
return new ViewHolder(binding.getRoot(), binding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.binData(getItem(position));
holder.itemView.setOnClickListener(v -> {
Toast.makeText(AppGlobals.getApplication(), getItem(position).getPaintingName(), Toast.LENGTH_LONG).show();
});
}
public class ViewHolder extends RecyclerView.ViewHolder {
private LayoutTeacherTypeBinding mBinding;
public ViewHolder(@NonNull View itemView, LayoutTeacherTypeBinding binding) {
super(itemView);
this.mBinding = binding;
}
public void binData(Teacher.RecordsBean item) {
mBinding.setItem(item);
}
}
}
适配器布局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/ll_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="20dp"
android:paddingBottom="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:text="@{item.paintingName}"
android:textSize="24dp"
tools:text="名称" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eee" />
</LinearLayout>
<data>
<variable
name="item"
type="com.paging.study.bean.Teacher.RecordsBean" />
</data>
</layout>
- 主页面处理
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.paging.ItemKeyedDataSource;
import androidx.paging.PagedList;
import androidx.paging.PagedListAdapter;
import com.paging.study.AbsListFragment;
import com.paging.study.adapter.HomeAdapter;
import com.paging.study.bean.Teacher;
import com.paging.study.datasource.MutablePageKeyedDataSource;
import com.paging.study.viewmodel.BlankViewModel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import java.util.List;
public class BlankFragment extends AbsListFragment<Teacher.RecordsBean, BlankViewModel> {
public static BlankFragment newInstance() {
return new BlankFragment();
}
@Override
protected void onCreateViewFrame(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
}
@Override
public PagedListAdapter getAdapter() {
//return new HomeAdapter(mContext);
return new HomeAdapter(mContext) {
@Override
public void onViewAttachedToWindow(@NonNull ViewHolder holder) {
super.onViewAttachedToWindow(holder);
//进入当前页面,看见了
Log.e("TTT", "onViewAttachedToWindow===>" + holder.getLayoutPosition());
}
@Override
public void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
//离开当前页面,看不见了
Log.e("TTT", "onViewDetachedFromWindow===>" + holder.getLayoutPosition());
}
@Override
public void onCurrentListChanged(@Nullable PagedList<Teacher.RecordsBean> previousList, @Nullable PagedList<Teacher.RecordsBean> currentList) {
super.onCurrentListChanged(previousList, currentList);
//这个方法是在我们每提交一次 pagelist对象到adapter 就会触发一次
//每调用一次 adpater.submitlist
//防止下拉刷新新加item显示不出来
if (previousList != null && currentList != null) {
if (!currentList.containsAll(previousList)) {
mRecyclerView.scrollToPosition(0);
}
}
}
};
}
/**
* paging只要有一次返回味空,就停止分页了,要手动处理
*
* @param refreshLayout
*/
@Override
public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
PagedList<Teacher.RecordsBean> currentList = adapter.getCurrentList();
if (currentList == null || currentList.size() <= 0) {
finishRefresh(false);
return;
}
Teacher.RecordsBean bean = adapter.getCurrentList().get(adapter.getItemCount() - 1);
mViewModel.loadAfter(bean.getPaintingId(), new ItemKeyedDataSource.LoadCallback<Teacher.RecordsBean>() {
@Override
public void onResult(@NonNull List<Teacher.RecordsBean> data) {
PagedList.Config config = currentList.getConfig();
if (data != null && data.size() > 0) {
//这里 咱们手动接管 分页数据加载的时候 使用MutableItemKeyedDataSource也是可以的。
//由于当且仅当 paging不再帮我们分页的时候,我们才会接管。所以 就不需要ViewModel中创建的DataSource继续工作了,所以使用
//这里主要是将list集合转化为PagedList,给paging使用,具体实现抄袭源码
MutablePageKeyedDataSource<Teacher.RecordsBean> dataSource = new MutablePageKeyedDataSource<>();
//这里要把列表上已经显示的先添加到dataSource.data中
//而后把本次分页回来的数据再添加到dataSource.data中
dataSource.data.addAll(currentList);
dataSource.data.addAll(data);
PagedList<Teacher.RecordsBean> pagedList = dataSource.buildNewPagedList(config);
submitList(pagedList);
}
}
});
}
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
//invalidate 之后Paging会重新创建一个DataSource 重新调用它的loadInitial方法加载初始化数据
//详情见:LivePagedListBuilder#compute方法
mViewModel.getDataSource().invalidate();
}
}
网络请求封装(注意paging的不同)
- 引入
可单独引入okhttp
//retrofit:okhttp封装的网络库
api 'com.squareup.retrofit2:retrofit:2.9.0'
api 'com.squareup.retrofit2:converter-gson:2.9.0'
api 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
api "com.squareup.okhttp3:logging-interceptor:4.7.2"
- PagingHttpClient 网络请求核心类
package com.paging.study.http;
import android.util.Log;
import com.yumakeji.rxjava.network.interceptor.CacheIntercepter;
import com.yumakeji.rxjava.network.interceptor.HeaderInterceptor;
import com.yumakeji.rxjava.network.uitils.manager.TrustAllCerts;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.FileNameMap;
import java.net.Proxy;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import cn.yumakeji.lib_common.BuildConfig;
import cn.yumakeji.lib_common.global.AppGlobals;
import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
public class PagingHttpClient {
protected OkHttpClient okHttpClient;
protected String sBaseUrl = "https://www.xianghua.art/applets/";
public static PagingHttpClient getInstance() {
return SingletonHolder.sInstance;
}
//静态内部类
private static class SingletonHolder {
private static final PagingHttpClient sInstance = new PagingHttpClient();
}
private PagingHttpClient() {
//缓存地址
File cacheFile = new File(AppGlobals.getApplication().getExternalCacheDir(), "Http_Cache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 50); //大小50Mb
OkHttpClient.Builder builder = new OkHttpClient
.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.pingInterval(20, TimeUnit.SECONDS)
.proxy(Proxy.NO_PROXY)
//设置缓存方式、时长、地址
.addNetworkInterceptor(new CacheIntercepter())
.addInterceptor(new HeaderInterceptor())
.cache(cache)
.sslSocketFactory(TrustAllCerts.createSSLSocketFactory())
.hostnameVerifier(new TrustAllCerts.TrustAllHostnameVerifier());
if (BuildConfig.DEBUG) { // 判断是否为debug
// 如果为 debug 模式,则添加日志拦截器
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(@NotNull String message) {
Log.i("hxg_http", message);
}
});
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
builder.addInterceptor(logging);
}
okHttpClient = builder.build();
}
private HttpEngine.TypeEnum mMediaType = HttpEngine.TypeEnum.FORM;
public void setMediaType(HttpEngine.TypeEnum mediaType) {
this.mMediaType = mediaType;
}
/**
* get请求
*
* @param url
* @param params
* @return
*/
public void get(String url, Map<String, Object> params, HttpObserver observer) {
String execute = execute(url, params, observer);
if (execute != null) {
observer.onSuccess(execute);
} else {
observer.onError(new RuntimeException("网络连接出错"));
}
}
/**
* post请求
*
* @param url
* @param params
* @return
*/
public void post(String url, Map<String, Object> params, HttpObserver observer) {
String execute = executePost(url, params);
if (execute != null) {
observer.onSuccess(execute);
} else {
observer.onError(new RuntimeException("网络连接出错"));
}
}
public void postText(String url, String stringJsonOrXml, HttpObserver observer) {
String execute = executePostText(url, stringJsonOrXml, observer);
if (execute != null) {
observer.onSuccess(execute);
} else {
observer.onError(new RuntimeException("网络连接出错"));
}
}
private String executePost(String url, Map<String, Object> params) {
try {
Response execute = postCall(sBaseUrl + url, params).execute();
ResponseBody body = execute.body();
return body.string();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String executePostText(String url, String stringJsonOrXml, HttpObserver observer) {
try {
Response execute = postCall(sBaseUrl + url, stringJsonOrXml).execute();
ResponseBody body = execute.body();
return body.string();
} catch (IOException e) {
e.printStackTrace();
observer.onError(e);
}
return null;
}
/**
* Post请求
*
* @param url
* @param params
* @return
*/
private Call postCall(String url, Map<String, Object> params) {
RequestBody requestBody = appendBody(params, mMediaType);
Request.Builder builder = new Request.Builder();
Request request = builder.post(requestBody).url(url).build();
Call call = okHttpClient.newCall(request);
return call;
}
private Call postCall(String url, String stringJsonOrXml) {
MediaType mediaType = MediaType.parse(getTypeEnum(mMediaType));//"类型,字节码"
RequestBody requestBody = RequestBody.create(mediaType, stringJsonOrXml);
Request.Builder builder = new Request.Builder();
Request request = builder.post(requestBody).url(url).build();
Call call = okHttpClient.newCall(request);
return call;
}
/**
* 组装post请求参数body
*
* @param params
* @return
*/
private RequestBody appendBody(Map<String, Object> params, HttpEngine.TypeEnum mediaType) {
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(getMediaType(mediaType));
addParams(builder, params);
return builder.build();
}
/**
* 添加参数
* 判断是否能上传文件
*/
private void addParams(MultipartBody.Builder builder, Map<String, Object> params) {
if (params != null && !params.isEmpty()) {
for (String key : params.keySet()) {
Object value = params.get(key);
if (value instanceof File) {
File file = (File) value;
builder.addFormDataPart(key, file.getName(),
RequestBody.create(MediaType
.parse(guessMineType(file.getAbsolutePath())),
file));
} else if (value instanceof List) {
//代表提交的是List集合
try {
List<File> fileList = (List<File>) value;
for (int i = 0; i < fileList.size(); i++) {
//获取文件
File file = fileList.get(i);
builder.addFormDataPart(key + i, file.getName(),
RequestBody.create(MediaType
.parse(guessMineType(file.getAbsolutePath())),
file));
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
builder.addFormDataPart(key, String.valueOf(value));
}
}
}
}
private String guessMineType(String path) {
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String contentTypeFor = fileNameMap.getContentTypeFor(path);
if (contentTypeFor == null) {
contentTypeFor = "application/octet-stream";
}
return contentTypeFor;
}
private String getTypeEnum(HttpEngine.TypeEnum typeEnum) {
switch (typeEnum) {
case XML:
return "application/xml; charset=utf-8";
case JSON:
return "application/json; charset=utf-8";
case URLENCODED:
return "application/x-www-form-urlencoded;charset=utf-8";
}
return "application/json; charset=utf-8";
}
private MediaType getMediaType(HttpEngine.TypeEnum typeEnum) {
switch (typeEnum) {
case MIXED:
return MultipartBody.MIXED;
case ALTERNATIVE:
return MultipartBody.ALTERNATIVE;
case DIGEST:
return MultipartBody.DIGEST;
case PARALLEL:
return MultipartBody.PARALLEL;
case FORM:
return MultipartBody.FORM;
}
return MultipartBody.FORM;
}
/**
* get请求
*
* @param url
* @param params
* @return
*/
private Call getCall(String url, Map<String, Object> params) {
String jointUrl = URLUtil.jointParams(sBaseUrl + url, params);
Request.Builder builder = new Request.Builder();
Request request = builder.get().url(jointUrl).build();
Call call = okHttpClient.newCall(request);
return call;
}
private String execute(String url, Map<String, Object> params, HttpObserver observer) {
try {
Response execute = getCall(url, params).execute();
ResponseBody body = execute.body();
return body.string();
} catch (IOException e) {
e.printStackTrace();
observer.onError(e);
}
return null;
}
}
- HttpEngine
public class HttpEngine {
enum TypeEnum {
XML,
JSON,
URLENCODED,
MIXED,
ALTERNATIVE,
DIGEST,
PARALLEL,
FORM
}
}
- URLUtil
public class URLUtil {
/**
* 拼接参数
*/
public static String jointParams(String url,Map<String,Object> params){
if (params==null||params.size()<=0){
return url;
}
StringBuffer stringBuffer=new StringBuffer(url);
if (!url.contains("?")){
stringBuffer.append("?");
}else {
if (!url.endsWith("?")){
stringBuffer.append("&");
}
}
for (Map.Entry<String, Object> entry : params.entrySet()) {
stringBuffer.append(entry.getKey()+"="+entry.getValue()+"&");
}
stringBuffer.deleteCharAt(stringBuffer.length()-1);
return stringBuffer.toString();
}
/**
* 解析一个类上面的class信息
*/
public static Class<?> analysisClazzInfo(Object object){
Type getType = object.getClass().getGenericSuperclass();
Type[] params = ((ParameterizedType) getType).getActualTypeArguments();
return (Class<?>) params[0];
}
}
- 回调
public interface HttpObserver {
void onError(@NonNull Throwable e);
void onSuccess(String s);
}
import com.alibaba.fastjson.JSON;
import com.yumakeji.rxjava.network.bean.Result;
import com.yumakeji.rxjava.network.error.HttpThrowable;
import com.yumakeji.rxjava.network.error.ThrowableHandler;
import com.yumakeji.rxjava.network.unify.UnifyCallback;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.ParameterizedType;
import io.reactivex.rxjava3.annotations.NonNull;
public abstract class PagingHttpCallback<T> implements HttpObserver {
@Override
public void onSuccess(String result) {
try {
JSONObject jsonObject = new JSONObject(result);
Result result1 = new Result();
result1.setCode(jsonObject.getInt("code"));
result1.setMessage(jsonObject.getString("message"));
result1.data = jsonObject.getString("data");
if (!result1.isOk()) {
UnifyCallback.handleUnifyCode(result1.getCode(), result1.getMessage());
onFail(result1.getCode(), result1.getMessage());
return;
}
//解析,获取类上面的泛型
Class<T> dataClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
T data = JSON.parseObject(result1.data.toString(), dataClass);
onNext(data);
} catch (JSONException e) {
e.printStackTrace();
}
}
protected abstract void onNext(T data);
protected abstract void onFail(int code, String message);
@Override
public void onError(@NonNull Throwable throwable) {
if (throwable instanceof Exception) {
onFail(ThrowableHandler.handleThrowable(throwable));
} else {
onFail(new HttpThrowable(HttpThrowable.UNKNOWN, "未知错误", throwable));
}
}
//应用中具体实现的是下面这个onFail方法
private void onFail(HttpThrowable httpThrowable) {
onFail(httpThrowable.errorType, httpThrowable.message);
}
}
其他
DataSource
- MutableItemKeyedDataSource
package com.paging.study.datasource;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.paging.ItemKeyedDataSource;
import androidx.paging.PagedList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 一个可变更的ItemKeyedDataSource 数据源
* <p>
* 工作原理是:我们知道DataSource是会被PagedList 持有的。
* 一旦,我们调用了new PagedList.Builder<Key, Value>().build(); 那么就会立刻触发当前DataSource的loadInitial()方法,而且是同步
* 详情见ContiguousPagedList的构造函数,而我们在当前DataSource的loadInitial()方法中返回了 最新的数据集合 data。
* 一旦,我们再次调用PagedListAdapter#submitList()方法 就会触发差分异计算 把新数据变更到列表之上了。
*
* @param <Key>
* @param <Value>
*/
@SuppressLint("RestrictedApi")
public abstract class MutableItemKeyedDataSource<Key, Value> extends ItemKeyedDataSource<Key, Value> {
private ItemKeyedDataSource mDataSource;
public List<Value> data = new ArrayList<>();
public PagedList<Value> buildNewPagedList(PagedList.Config config) {
PagedList<Value> pagedList = new PagedList.Builder<Key, Value>(this, config)
.setFetchExecutor(ArchTaskExecutor.getIOThreadExecutor())
.setNotifyExecutor(ArchTaskExecutor.getMainThreadExecutor())
.build();
return pagedList;
}
public MutableItemKeyedDataSource(ItemKeyedDataSource dataSource) {
mDataSource = dataSource;
}
@Override
public void loadInitial(@NonNull LoadInitialParams<Key> params, @NonNull LoadInitialCallback<Value> callback) {
callback.onResult(data);
}
@Override
public void loadAfter(@NonNull LoadParams<Key> params, @NonNull LoadCallback<Value> callback) {
if (mDataSource != null) {
//一旦 和当前DataSource关联的PagedList被提交到PagedListAdapter。那么ViewModel中创建的DataSource 就不会再被调用了
//我们需要在分页的时候 代理一下 原来的DataSource,迫使其继续工作
mDataSource.loadAfter(params, callback);
}
}
@Override
public void loadBefore(@NonNull LoadParams<Key> params, @NonNull LoadCallback<Value> callback) {
callback.onResult(Collections.emptyList());
}
@NonNull
@Override
public abstract Key getKey(@NonNull Value item);
}
- MutablePageKeyedDataSource
package com.paging.study.datasource;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.arch.core.executor.ArchTaskExecutor;
import androidx.paging.PageKeyedDataSource;
import androidx.paging.PagedList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 具体原理见 {@link MutableItemKeyedDataSource}
*
* @param <Value>
*/
@SuppressLint("RestrictedApi")
public class MutablePageKeyedDataSource<Value> extends PageKeyedDataSource<Integer, Value> {
public List<Value> data = new ArrayList<>();
public PagedList<Value> buildNewPagedList(PagedList.Config config) {
PagedList<Value> pagedList = new PagedList.Builder<Integer, Value>(this, config)
.setFetchExecutor(ArchTaskExecutor.getIOThreadExecutor())
.setNotifyExecutor(ArchTaskExecutor.getMainThreadExecutor())
.build();
return pagedList;
}
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Value> callback) {
callback.onResult(data, null, null);
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Value> callback) {
callback.onResult(Collections.emptyList(), null);
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Value> callback) {
callback.onResult(Collections.emptyList(), null);
}
}
- javabean
public class Teacher extends BaseResult {
private int current;
private int pages;
private int size;
private int total;
private List<RecordsBean> records;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Teacher)) return false;
Teacher teacher = (Teacher) o;
return getCurrent() == teacher.getCurrent() &&
getPages() == teacher.getPages() &&
getSize() == teacher.getSize() &&
getTotal() == teacher.getTotal() &&
Objects.equals(getRecords(), teacher.getRecords());
}
@Override
public int hashCode() {
return Objects.hash(getCurrent(), getPages(), getSize(), getTotal(), getRecords());
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
public int getPages() {
return pages;
}
public void setPages(int pages) {
this.pages = pages;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public List<RecordsBean> getRecords() {
return records;
}
public void setRecords(List<RecordsBean> records) {
this.records = records;
}
public static class RecordsBean {
private String createTime;
private int difficultyIndex;
private String direction;
private String headImgUrl;
private int isVip;
private String label;
private String paintingId;
private String paintingName;
private String readCount;
private String showNew;
private String studyAge;
private String titlePage;
private int type;
private String userName;
private String videoId;
private String videoName;
private List<LabelListBean> labelList;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof RecordsBean)) return false;
RecordsBean that = (RecordsBean) o;
return getDifficultyIndex() == that.getDifficultyIndex() &&
getIsVip() == that.getIsVip() &&
getType() == that.getType() &&
Objects.equals(getCreateTime(), that.getCreateTime()) &&
Objects.equals(getDirection(), that.getDirection()) &&
Objects.equals(getHeadImgUrl(), that.getHeadImgUrl()) &&
Objects.equals(getLabel(), that.getLabel()) &&
Objects.equals(getPaintingId(), that.getPaintingId()) &&
Objects.equals(getPaintingName(), that.getPaintingName()) &&
Objects.equals(getReadCount(), that.getReadCount()) &&
Objects.equals(getShowNew(), that.getShowNew()) &&
Objects.equals(getStudyAge(), that.getStudyAge()) &&
Objects.equals(getTitlePage(), that.getTitlePage()) &&
Objects.equals(getUserName(), that.getUserName()) &&
Objects.equals(getVideoId(), that.getVideoId()) &&
Objects.equals(getVideoName(), that.getVideoName()) &&
Objects.equals(getLabelList(), that.getLabelList());
}
@Override
public int hashCode() {
return Objects.hash(getCreateTime(), getDifficultyIndex(), getDirection(), getHeadImgUrl(), getIsVip(), getLabel(), getPaintingId(), getPaintingName(), getReadCount(), getShowNew(), getStudyAge(), getTitlePage(), getType(), getUserName(), getVideoId(), getVideoName(), getLabelList());
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public int getDifficultyIndex() {
return difficultyIndex;
}
public void setDifficultyIndex(int difficultyIndex) {
this.difficultyIndex = difficultyIndex;
}
public String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = direction;
}
public String getHeadImgUrl() {
return headImgUrl;
}
public void setHeadImgUrl(String headImgUrl) {
this.headImgUrl = headImgUrl;
}
public int getIsVip() {
return isVip;
}
public void setIsVip(int isVip) {
this.isVip = isVip;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getPaintingId() {
return paintingId;
}
public void setPaintingId(String paintingId) {
this.paintingId = paintingId;
}
public String getPaintingName() {
return paintingName;
}
public void setPaintingName(String paintingName) {
this.paintingName = paintingName;
}
public String getReadCount() {
return readCount;
}
public void setReadCount(String readCount) {
this.readCount = readCount;
}
public String getShowNew() {
return showNew;
}
public void setShowNew(String showNew) {
this.showNew = showNew;
}
public String getStudyAge() {
return studyAge;
}
public void setStudyAge(String studyAge) {
this.studyAge = studyAge;
}
public String getTitlePage() {
return titlePage;
}
public void setTitlePage(String titlePage) {
this.titlePage = titlePage;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getVideoId() {
return videoId;
}
public void setVideoId(String videoId) {
this.videoId = videoId;
}
public String getVideoName() {
return videoName;
}
public void setVideoName(String videoName) {
this.videoName = videoName;
}
public List<LabelListBean> getLabelList() {
return labelList;
}
public void setLabelList(List<LabelListBean> labelList) {
this.labelList = labelList;
}
public static class LabelListBean {
/**
* labelId : 1177937130604482562
* labelName : 线描装饰
*/
private String labelId;
private String labelName;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LabelListBean)) return false;
LabelListBean that = (LabelListBean) o;
return Objects.equals(getLabelId(), that.getLabelId()) &&
Objects.equals(getLabelName(), that.getLabelName());
}
@Override
public int hashCode() {
return Objects.hash(getLabelId(), getLabelName());
}
public String getLabelId() {
return labelId;
}
public void setLabelId(String labelId) {
this.labelId = labelId;
}
public String getLabelName() {
return labelName;
}
public void setLabelName(String labelName) {
this.labelName = labelName;
}
}
}
}
- BaseResult
package com.yumakeji.rxjava.network.bean;
public class BaseResult {
private int code;
private String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isOk() {
return 200 == code;
}
}
- Result
public class Result<T> extends BaseResult {
public Object data;
}
部分类参考
https://huangxiaoguo.blog.csdn.net/article/details/106525461