簡單的Post
請求,以及RecycelrView
添加FooterView
螃壤,上拉加載更多練習(xí)抗果,本打算是練習(xí)post
請求,但寫著寫著奸晴,成了RecyclerView
的練習(xí)
1. 完整的Acitivity代碼
添加FooterView
冤馏,思路是Android 優(yōu)雅的為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"));
//第一次進(jìn)來有自動(dòng)刷新的效果
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);
}
}
});
//設(shè)置點(diǎn)擊事件
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());
}
});
}
/**
* 網(wǎng)絡(luò)請求
*/
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);//子線程,計(jì)算差異
//成功回調(diào)
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() {
//對應(yīng)在onCreate()中的創(chuàng)建方式
//關(guān)閉刷新小圓圈
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的結(jié)果蚁滋,關(guān)聯(lián)到Adapter
//記得將新的數(shù)據(jù)宿接,存進(jìn)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);
}
}
代碼不多,關(guān)鍵地方有注釋
在成功請求到結(jié)果后辕录,使用了DiffUtil
代替adapter.notifyDataSetChanged()
上拉加載更多睦霎,用的recyclerView.canScrollVertically(RecyclerView.VERTICAL)
,返回結(jié)果代表是否可以向上垂直滑動(dòng)走诞,false
就意味著到了RecycelrView
的底部
當(dāng)RecycelrView
到達(dá)底部時(shí)副女,就讓原本處于View.GONE
的FooterView
,顯示出來蚣旱,當(dāng)加載完成時(shí)碑幅,再View.GONE
使用HeaderAndFooterAdapter裝飾者
這樣的方式戴陡,就可以不考慮各種Position
問題,不需要擔(dān)心添加了FooterView
后沟涨,點(diǎn)擊事件會(huì)錯(cuò)亂恤批,RecyclerView
的adapter
不會(huì)受啥影響,擴(kuò)展也不會(huì)影響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
直接使用了一個(gè)CardView
喜庞,內(nèi)部是一個(gè)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
來顯示一個(gè)白色的圓
1.2 OkHttp Post請求
- 創(chuàng)建
okHttpClient
對象
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.build();
connectTimeout(long timeout,TimeUnit unit)
,設(shè)置連接超時(shí)的時(shí)間
cache(Cache cache)
棋返,設(shè)置緩存
cookieJar(CookieJar jar)
湘换,存儲(chǔ)巡雨,使用Cookie
addInterceptor(Interceptor i)
吹零,添加攔截器
- 創(chuàng)建
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
是一個(gè)abstract
類柠新,FormBody
是Requestbody
的子類。在建造者模式中射沟,我個(gè)人理解FormBody
屬于產(chǎn)品
,而FormBody
的內(nèi)部類Builder
屬于具體建造者
add(String name, String value)
殊者,添加查詢條件
- Request 和 Call
Request
,默認(rèn)是GET
請求验夯,通過post(RequestBody)
幽污,進(jìn)行POST
請求
Call
,GET
和POST
請求一樣簿姨,都是enqueue(Callback)
在子線程進(jìn)行
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();
}
/**
*根據(jù)圖片地址,比較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());
}
/**
*只有當(dāng) areItemsTheSame 方法返回 true 時(shí)簸搞,才會(huì)調(diào)用此方法
* 比較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 ,存進(jìn)了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
個(gè)方法
使用很方便趁俊,偽碼:
//在 OkHttp 執(zhí)行網(wǎng)絡(luò)請求的子線程中
DiffUtilCallback callback = new DiffUtilCallback();
callback.setOldLists(oldsList);
callback.setNewLists(newsList);
//在子線程中域仇,計(jì)算差異
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);
//回調(diào),在UI線程中
diffResult.dispatchUpdatesTo(footerAdapter);//將DiffUtil的結(jié)果寺擂,關(guān)聯(lián)到Adapter
//記得將新的數(shù)據(jù)暇务,存進(jìn)adapter的List中
adapter.setData(listt);
對應(yīng)的,需要重寫adapter
中的onBindViewHolder(BaseViewHolder holder, int position, List<Object> payloads)
1.4 Adapter
CommonBaseAdapter
是一個(gè)很簡單的適配器封裝
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個(gè)參數(shù)的onBindViewHolde()
方法中怔软,先對payloads
進(jìn)行判斷isEmpty()
如果payloads
不為empty
垦细,就取出數(shù)據(jù)進(jìn)行加載
在show()
方法中,根據(jù)布局中ImageView
的大小進(jìn)行加載
2. 最后
寫的跑題了挡逼,哈哈
有錯(cuò)誤請指出
共勉 :)