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

简单的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 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类,FormBodyRequestbody的子类。在建造者模式中,我个人理解FormBody属于产品,而FormBody的内部类Builder属于具体建造者

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


  • Request 和 Call

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. 最后

写的跑题了,哈哈

有错误请指出

共勉 :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容