OkHttp基础学习(二),Post请求

2017-01-09  本文已影响274人  英勇青铜5

简单的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.GONEFooterView,显示出来,当加载完成时,再View.GONE

使用HeaderAndFooterAdapter装饰者这样的方式,就可以不考虑各种Position问题,不需要担心添加了FooterView后,点击事件会错乱,RecyclerViewadapter不会受啥影响,扩展也不会影响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>

RecyclerViewitem直接使用了一个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 = 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 = 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类,FormBodyRequestbody的子类。在建造者模式中,我个人理解FormBody属于产品,而FormBody的内部类Builder属于具体建造者

add(String name, String value),添加查询条件


Request,默认是GET请求,通过post(RequestBody),进行POST请求
CallGETPOST请求一样,都是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. 最后

写的跑题了,哈哈

有错误请指出

共勉 :)

上一篇下一篇

猜你喜欢

热点阅读