简单的Post
请求,以及RecycelrView
添加FooterView
,上拉加载更多练习,本打算是练习post
请求,但写着写着,成了RecyclerView
的练习
1. 完整的Acitivity代码
添加FooterView
,思路是Android 优雅的为RecyclerView添加HeaderView和FooterView
public class PostActivity extends AppCompatActivity implements ResultCallback2<List<ResponseBean.ShowapiResBodyBean.NewslistBean>> {
private Platform mPlatform;
private int page = 1;
private RecyclerAdapter adapter;
private SwipeRefreshLayout swipeRefreshLayout;
private boolean isLoading = false;
private List<ResponseBean.ShowapiResBodyBean.NewslistBean> oldsList = new ArrayList<>();
private HeaderAndFooterAdapter footerAdapter;
private RecyclerView recyclerView;
private boolean isFirst = true;
private View footerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_post);
mPlatform = Platform.get();
initView();
}
/**
* 初始化recyclerView
*/
private void initView() {
//RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.activity_post_rv);
GridLayoutManager layoutManager = new GridLayoutManager(PostActivity.this, 2);
layoutManager.setOrientation(GridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
adapter = new RecyclerAdapter(recyclerView, R.layout.rv_item_layout);
//使用HeaderAndFooterAdapter
footerAdapter = new HeaderAndFooterAdapter(adapter);
recyclerView.addItemDecoration(new RVItemDecoration(16));
//FooterView
footerView = this.getLayoutInflater().inflate(R.layout.footer_view_layout, recyclerView, false);
footerView.setVisibility(View.GONE);
footerAdapter.addFootView(footerView);
recyclerView.setAdapter(footerAdapter);
//SwipeRefreshLayout
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.activity_post_srl);
swipeRefreshLayout.setColorSchemeColors(Color.parseColor("#FF4081"));
//第一次进来有自动刷新的效果
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
}
});
swipeRefreshLayout.setEnabled(false);
//请求第一页
request(page);
//上拉加载更多
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!recyclerView.canScrollVertically(RecyclerView.VERTICAL) && !isLoading && newState == RecyclerView.SCROLL_STATE_IDLE) {
footerView.setVisibility(View.VISIBLE);
request(++page);
}
}
});
//设置点击事件
adapter.setItemListener(new CommonBaseAdapter.onRecyclerItemClickerListener() {
@Override
public void onRecyclerItemClick(View view, Object data, int position) {
ResponseBean.ShowapiResBodyBean.NewslistBean bean = (ResponseBean.ShowapiResBodyBean.NewslistBean) data;
ToastUtils.show(PostActivity.this, bean.getTitle());
}
});
}
/**
* 网络请求
*/
private void request(int page) {
isLoading = true;
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.build();
RequestBody requestBody = new FormBody.Builder()
.add(Urls.KEY_APPID, Urls.APPID)
.add(Urls.KEY_SIGN, Urls.SIGN)
.add(Urls.KEY_NUM, Urls.NUM)
.add(Urls.KEY_PAGE, page + "")
.build();
Request request = new Request.Builder().url(Urls.POST_URL).post(requestBody).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
sendFailResultCallback(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
ResponseBody responseBody = null;
try {
if (call.isCanceled()) {
sendFailResultCallback(new IOException("Request Canceled"));
return;
}
if (response.isSuccessful()) {
responseBody = response.body();
String json = responseBody.string();
ResponseBean responseBean = new Gson().fromJson(json, ResponseBean.class);
DiffUtilCallback callback = new DiffUtilCallback();
callback.setOldLists(oldsList);
oldsList.addAll(responseBean.getShowapi_res_body().getNewslist());
callback.setNewLists(oldsList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程,计算差异
//成功回调
sendSuccessResultCallback(diffResult, oldsList);
} else {
sendFailResultCallback(new IOException("Request Failed"));
}
} catch (Exception e) {
sendFailResultCallback(e);
} finally {
if (null != responseBody) {
responseBody.close();
}
}
}
});
}
@Override
public void sendFailResultCallback(final Exception e) {
doSomething(new Runnable() {
@Override
public void run() {
//对应在onCreate()中的创建方式
//关闭刷新小圆圈
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(false);
}
});
footerView.setVisibility(View.GONE);
String info = "Fail Message --> " + e.getMessage();
ToastUtils.show(PostActivity.this, info);
footerView.setVisibility(View.GONE);
}
});
}
@Override
public void sendSuccessResultCallback(final DiffUtil.DiffResult diffResult, final List<ResponseBean.ShowapiResBodyBean.NewslistBean> listt) {
isLoading = false;
doSomething(new Runnable() {
@Override
public void run() {
diffResult.dispatchUpdatesTo(footerAdapter);//将DiffUtil的结果,关联到Adapter
//记得将新的数据,存进adapter的List中
adapter.setData(listt);
footerView.setVisibility(View.GONE);
if (isFirst) {
recyclerView.scrollToPosition(0);
isFirst = false;
}
if (swipeRefreshLayout.isRefreshing()) {
swipeRefreshLayout.setRefreshing(false);
}
}
});
}
private void doSomething(Runnable runnable) {
mPlatform.execute(runnable);
}
}
代码不多,关键地方有注释
在成功请求到结果后,使用了DiffUtil
代替adapter.notifyDataSetChanged()
上拉加载更多,用的recyclerView.canScrollVertically(RecyclerView.VERTICAL)
,返回结果代表是否可以向上垂直滑动,false
就意味着到了RecycelrView
的底部
当RecycelrView
到达底部时,就让原本处于View.GONE
的FooterView
,显示出来,当加载完成时,再View.GONE
使用HeaderAndFooterAdapter装饰者
这样的方式,就可以不考虑各种Position
问题,不需要担心添加了FooterView
后,点击事件会错乱,RecyclerView
的adapter
不会受啥影响,扩展也不会影响HeaderAndFooterAdapter
,耦合度比较高
1.1 布局代码
1.1.1 Item布局
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="250dp"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="4dp"
app:cardElevation="8dp"
app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/rv_item_iv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="8.5"
android:contentDescription="@string/app_name"
android:maxHeight="320dp"
android:maxWidth="180dp"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/rv_item_tv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.5"
android:gravity="center"
android:textSize="15sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
RecyclerView
的item
直接使用了一个CardView
,内部是一个ImageView
1.1.2 FooterView的布局代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="45dp"
android:gravity="center"
android:orientation="vertical"
android:visibility="visible">
<android.support.v7.widget.CardView
android:layout_width="40dp"
android:layout_height="40dp"
app:cardCornerRadius="20dp"
app:cardPreventCornerOverlap="true">
<android.support.v4.widget.ContentLoadingProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center" />
</android.support.v7.widget.CardView>
</LinearLayout>
使用了CardView
来显示一个白色的圆
1.2 OkHttp Post请求
- 创建
okHttpClient
对象
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.build();
connectTimeout(long timeout,TimeUnit unit)
,设置连接超时的时间
cache(Cache cache)
,设置缓存
cookieJar(CookieJar jar)
,存储,使用Cookie
addInterceptor(Interceptor i)
,添加拦截器
- 创建
RequestBody
对象
RequestBody requestBody = new FormBody.Builder()
.add(Urls.KEY_APPID, Urls.APPID)
.add(Urls.KEY_SIGN, Urls.SIGN)
.add(Urls.KEY_NUM, Urls.NUM)
.add(Urls.KEY_PAGE, page + "")
.build();
RequestBody
是一个abstract
类,FormBody
是Requestbody
的子类。在建造者模式中,我个人理解FormBody
属于产品
,而FormBody
的内部类Builder
属于具体建造者
add(String name, String value)
,添加查询条件
- Request 和 Call
Request
,默认是GET
请求,通过post(RequestBody)
,进行POST
请求
Call
,GET
和POST
请求一样,都是enqueue(Callback)
在子线程进行
1.3 DiffUtilCall
使用DiffUtilCall
代替notifyDataSetChanged()
public class DiffUtilCallback extends DiffUtil.Callback {
private List<ResponseBean.ShowapiResBodyBean.NewslistBean> newLists = new ArrayList<>();
private List<ResponseBean.ShowapiResBodyBean.NewslistBean> oldLists = new ArrayList<>();
public void setNewLists(List<ResponseBean.ShowapiResBodyBean.NewslistBean> newLists) {
if (null != newLists) {
this.newLists.clear();
this.newLists.addAll(newLists);
}
}
public void setOldLists(List<ResponseBean.ShowapiResBodyBean.NewslistBean> oldLists) {
if (null != oldLists) {
this.oldLists.clear();
this.oldLists.addAll(oldLists);
}
}
@Override
public int getOldListSize() {
return oldLists.size();
}
@Override
public int getNewListSize() {
return newLists.size();
}
/**
*根据图片地址,比较NewsListBean是否相同
*/
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
ResponseBean.ShowapiResBodyBean.NewslistBean newBean = newLists.get(newItemPosition);
ResponseBean.ShowapiResBodyBean.NewslistBean oldBean = oldLists.get(oldItemPosition);
return oldBean.getPicUrl().equals(newBean.getPicUrl());
}
/**
*只有当 areItemsTheSame 方法返回 true 时,才会调用此方法
* 比较Title是否相同
*/
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
ResponseBean.ShowapiResBodyBean.NewslistBean newBean = newLists.get(newItemPosition);
ResponseBean.ShowapiResBodyBean.NewslistBean oldBean = oldLists.get(oldItemPosition);
return oldBean.getTitle().equals(newBean.getTitle());
}
/**
* 得到差异的对象,最终通过 Bundle ,存进了List<Object> payloads
*/
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
ResponseBean.ShowapiResBodyBean.NewslistBean newBean = newLists.get(newItemPosition);
ResponseBean.ShowapiResBodyBean.NewslistBean oldBean = oldLists.get(oldItemPosition);
Bundle diffBundle = new Bundle();
if (!newBean.getPicUrl().equals(oldBean.getPicUrl())) {
diffBundle.putSerializable(Strings.NEWLISTBEAN_KEY, newBean);
}
return diffBundle;
}
}
主要就是重写3
个方法
使用很方便,伪码:
//在 OkHttp 执行网络请求的子线程中
DiffUtilCallback callback = new DiffUtilCallback();
callback.setOldLists(oldsList);
callback.setNewLists(newsList);
//在子线程中,计算差异
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);
//回调,在UI线程中
diffResult.dispatchUpdatesTo(footerAdapter);//将DiffUtil的结果,关联到Adapter
//记得将新的数据,存进adapter的List中
adapter.setData(listt);
对应的,需要重写adapter
中的onBindViewHolder(BaseViewHolder holder, int position, List<Object> payloads)
1.4 Adapter
CommonBaseAdapter
是一个很简单的适配器封装
public class RecyclerAdapter extends CommonBaseAdapter<ResponseBean.ShowapiResBodyBean.NewslistBean> {
public RecyclerAdapter(RecyclerView rv, @LayoutRes int itemLayoutId) {
super(rv, itemLayoutId);
}
@Override
public void bindViewData(BaseViewHolder holder, ResponseBean.ShowapiResBodyBean.NewslistBean item, int position) {
show(holder, item);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position, List<Object> payloads) {
super.onBindViewHolder(holder, position, payloads);
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
Bundle bundle = (Bundle) payloads.get(0);
ResponseBean.ShowapiResBodyBean.NewslistBean bean =
(ResponseBean.ShowapiResBodyBean.NewslistBean) bundle.getSerializable(Strings.NEWLISTBEAN_KEY);
if (null != bean) {
show(holder, bean);
}
}
}
private void show(BaseViewHolder holder, final ResponseBean.ShowapiResBodyBean.NewslistBean item) {
final ImageView iv = holder.getView(R.id.rv_item_iv);
final Activity mActivity = (Activity) mContext;
iv.post(new Runnable() {
@Override
public void run() {
ImgSize imgSize = getImgSize(iv);
Glide.with(mActivity).load(item.getPicUrl()).override(imgSize.width, imgSize.height).centerCrop().into(iv);
}
});
holder.setText(R.id.rv_item_tv, item.getTitle());
}
/**
* 获取 ImageView 宽高
*/
private ImgSize getImgSize(ImageView iv) {
ImgSize imgSize = new ImgSize();
DisplayMetrics displayMetrics = iv.getContext().getResources()
.getDisplayMetrics();
ViewGroup.LayoutParams lp = iv.getLayoutParams();
int width = iv.getWidth();
if (width <= 0) {
width = lp.width;
}
if (width <= 0) {
width = iv.getMaxWidth();
}
if (width <= 0) {
width = displayMetrics.widthPixels / 2;
}
int height = iv.getHeight();
if (height <= 0) {
height = lp.height;
}
if (height <= 0) {
height = iv.getMaxHeight();
}
if (height <= 0) {
height = displayMetrics.heightPixels / 2;
}
imgSize.width = width;
imgSize.height = height;
return imgSize;
}
private static class ImgSize {
int width;
int height;
}
}
在3个参数的onBindViewHolde()
方法中,先对payloads
进行判断isEmpty()
如果payloads
不为empty
,就取出数据进行加载
在show()
方法中,根据布局中ImageView
的大小进行加载
2. 最后
写的跑题了,哈哈
有错误请指出
共勉 :)