MVP+OKhttp,拦截器+XRecyclerView+ImageLoader(Glide)

来源:互联网 发布:java 局部变量 编辑:程序博客网 时间:2024/06/05 12:40

1 . 效果图

 

 

 

(图1)    

                            

  (图2)

 

一、技术选型:

1. 项目框架:MVP;注意:避免内存泄漏;

2. 图片加载: Universal-Image-Loader或Glide图片加载框架

3.网络加载框架:OkHttp

4. 自定义应用拦截器,封装公共请求参数(注意:必须通过拦截器封装公共请求参数,否则无法请求数据

公共请求参数,在我们项目研发过程中,作用非常大。封装公共请求承参数之后,那么所有的接口都会默认携带这些公共参数,达到复用的效果。

公共请求参数:source=android

参数名称:source

参数值:android

类型:String

是否必传:是

5. 数据展示使用RecylerView

二、业务逻辑需求:

1. 搜索框输入关键字(笔记本、手机),其他关键词没有数据,点击搜索实现如图效果;

2. 1是列表布局,图2是网格布局;

3. 通过右上角按钮点击切换列表布局和网格布局,按钮随着切换;

4. 实现下拉刷新展示第一页数据、上拉加载更多实现分页功能。

三、接口:

http://120.27.23.105/product/searchProducts

keywords=笔记本&page=1

参数说明:

keywords 关键字字段 String类型 必传

page  页码数  String类型  必传

公共参数:source 来源字段String类型 (通过自定义拦截器封装


1.  导包:gson包,ImageLoader(Glide)包,


2. 添加依赖:

(1)butterknife自动生成控件id和点击事件:

     compile 'com.jakewharton:butterknife:7.0.0'
(2)okhttp依赖:     compile 'com.squareup.okhttp3:okhttp:3.9.0'
(3)xRecyclerView依赖:
     compile 'com.jcodecraeer:xrecyclerview:1.3.2'
一 . 首先编写OKhttp的封装类

(1)OkHttpUtils

package com.okhttp;import android.os.Handler;import java.util.concurrent.TimeUnit;import okhttp3.Callback;import okhttp3.OkHttpClient;import okhttp3.Request;public class OkHttpUtils {    private Handler handler=new Handler();    public Handler getHandler(){        return handler;    }    //单例    private static OkHttpUtils okHttpUtils = null;    private OkHttpUtils(){};    public static OkHttpUtils getInstance(){        if(okHttpUtils == null){            //不适用拦截器时,可以把if判断内的代码去除            okHttpUtils = new OkHttpUtils();            client = new OkHttpClient.Builder()                    .readTimeout(20, TimeUnit.SECONDS)                    .writeTimeout(20,TimeUnit.SECONDS)                    .connectTimeout(20, TimeUnit.SECONDS)                    .addInterceptor(new LoggingInterceptor())                    .build();        }        return okHttpUtils;    }    private static OkHttpClient client;    private void initOkHttpClient(){        if (client==null){            client=new OkHttpClient.Builder().build();        }    }    //公用的get请求方法  完成的功能不确定    public void doGet(String url, Callback callback){        initOkHttpClient();        Request request=new Request.Builder().url(url).build();        client.newCall(request).enqueue(callback);    }}
(2)OkHttp3Utils

package com.okhttp;import android.util.Log;import java.io.File;import java.io.IOException;import java.util.Map;import okhttp3.Call;import okhttp3.Callback;import okhttp3.FormBody;import okhttp3.MediaType;import okhttp3.MultipartBody;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.RequestBody;import okhttp3.Response;/** * 1. 类的用途 * 2. @author  *  */public class OkHttp3Utils {    private static OkHttpClient okHttpClient = null;    public OkHttp3Utils() {    }    private static OkHttpClient getOkHttpClient() {        synchronized (OkHttp3Utils.class) {            if (okHttpClient == null) {                okHttpClient = new OkHttpClient();            }        }        return okHttpClient;    }    //上传文件    public static void loadFile(String url, File file,String fileName){        OkHttpClient okHttpClient = getOkHttpClient();        //设置文件类型        RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"),file);        //设置请求体        RequestBody body = new MultipartBody.Builder()                .setType(MultipartBody.FORM)                .addFormDataPart("image",fileName,requestBody)                .build();        //请求方式        Request request = new Request.Builder().url(url).post(body).build();        okHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Call call, IOException e) {            }            @Override            public void onResponse(Call call, Response response) throws IOException {                Log.i("成功","成功");            }        });    }    /**     * 1.接口地址     * 2.接口回调     */    public static void doGet(String url,Callback callback){        OkHttpClient okHttpClient = getOkHttpClient();        Request request = new Request.Builder().url(url).build();        okHttpClient.newCall(request).enqueue(callback);    }    /**     * 1.地址     * 2.接口回调     * 3.请求体     */    public static void doPost(String url, Map<String,String> map,Callback callback){        OkHttpClient okHttpClient = getOkHttpClient();        FormBody.Builder builder = new FormBody.Builder();        //遍历map集合   设置请求体        for (String mapKey : map.keySet()){           builder.add(mapKey,map.get(mapKey));        }        //设置请求方式        Request request = new Request.Builder().url(url).post(builder.build()).build();        //执行请求方式    接口回调        okHttpClient.newCall(request).enqueue(callback);    }    /**     *1.下载地址     */    public static void doDown(String url,Callback callback){        OkHttpClient okHttpClient = getOkHttpClient();        Request build = new Request.Builder().url(url).build();        okHttpClient.newCall(build).enqueue(callback);    }}
(3)OnUiCallback

package com.okhttp;import android.os.Handler;import java.io.IOException;import okhttp3.Call;import okhttp3.Callback;import okhttp3.Response;public abstract class OnUiCallback implements Callback {    private Handler handler=OkHttpUtils.getInstance().getHandler();    public abstract void onFailed(Call call,IOException e);    public abstract void onSuccess(String result);    @Override    public void onFailure(final Call call, final IOException e) {        //该方法就是把  线程post到handler所在的线程        handler.post(new Runnable() {            @Override            public void run() {//                e.getMessage()                onFailed(call,e);            }        });    }    @Override    public void onResponse(Call call, Response response) throws IOException {        final String result=response.body().string();        handler.post(new Runnable() {            @Override            public void run() {                onSuccess(result);            }        });    }}
(4)LoggingInterceptor(拦截器类)

package com.okhttp;import java.io.IOException;import okhttp3.Interceptor;import okhttp3.Request;import okhttp3.Response;/** * 拦截器类 *///自定义应用拦截器public class LoggingInterceptor implements Interceptor {    @Override public Response intercept(Chain chain) throws IOException {        Request request = chain.request();        long t1 = System.nanoTime();//    logger.info(String.format("Sending request %s on %s%n%s",//        request.url(), chain.connection(), request.headers()));        Response response = chain.proceed(request);        long t2 = System.nanoTime();//    logger.info(String.format("Received response for %s in %.1fms%n%s",//        response.request().url(), (t2 - t1) / 1e6d, response.headers()));        System.out.println("t2 = " + (t2-t1));        return response;    }}
. model层
import com.bwie.duhongwang.okhttp.OkHttp3Utils;import okhttp3.Callback;/** * model层,查询数据的接口实现类 */public class MyDataModel {    //传三个参数(公共请求参数) 一个是分页加载时用到的 page 还有  输入框内搜索的值,还有callback 用来在presenter内拿出bean    public void getData(String keywords, String page, Callback callback ){        OkHttp3Utils.doGet("http://120.27.23.105/product/searchProducts?keywords="+keywords+"&page="+page+"&source=android",callback);    }}
model层实现的接口

//model层,数据的查询接口public interface SearchModel {        public void getData(String keywords, String page, okhttp3.Callback callback);}

三. view层

import com.bwie.duhongwang.bean.ProductsBean;/** * view层,UI界面的搭建 */public interface DataView {    public void showView(ProductsBean productsBean);   //成功获取数据}
四. presenter层
import android.content.Context;import android.util.Log;import com.bwie.duhongwang.bean.ProductsBean;import com.bwie.duhongwang.model.MyDataModel;import com.bwie.duhongwang.okhttp.OnUiCallback;import com.bwie.duhongwang.view.DataView;import com.google.gson.Gson;import java.io.IOException;import okhttp3.Call;/** * presenter层,进行model和view层之间数据的交互 */public class DataPresenter {    Context context;    private DataView dataView;    private MyDataModel dataModel;    //构造方法中声明view层,初始化model层数据,将 mvp三层关联    public DataPresenter(Context context, DataView dataView) {        this.context = context;        this.dataView = dataView;        dataModel = new MyDataModel();    }    //调用model层接口,上拉加载下拉刷新    public void getData(String keyword,String page){        dataModel.getData(keyword, page, new OnUiCallback() {            @Override            public void onFailed(Call call, IOException e) {                //数据获取//失败方法            }            @Override            public void onSuccess(String result) {                //获取数据成功时将放回的json 变成bean                Log.i("返回数据", "结果: "+result.toString());                ProductsBean bean = new Gson().fromJson(result, ProductsBean.class);                dataView.showView(bean);            }        });    }    //用来防止内存溢出    public void destory(){        this.dataView = null;    }}

五. imageloader工具类及其全局初始化配置

(1)全局初始化配置类:

package com.bwie.secondweek.util;import android.app.Application;//全局初始化Application类public class BaseApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        //配置imageLoader        ImageLoaderUtil.init(this);    }}

(2)imageloader工具类:

package com.bwie.secondweek.util;import android.content.Context;import android.graphics.Bitmap;import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator;import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache;import com.nostra13.universalimageloader.core.DisplayImageOptions;import com.nostra13.universalimageloader.core.ImageLoader;import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;import com.nostra13.universalimageloader.core.assist.ImageScaleType;import com.nostra13.universalimageloader.core.assist.QueueProcessingType;import com.nostra13.universalimageloader.core.decode.BaseImageDecoder;import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer;import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;import com.nostra13.universalimageloader.core.download.BaseImageDownloader;import com.nostra13.universalimageloader.utils.StorageUtils;import java.io.File;public class ImageLoaderUtil {    /**     * 初始化imageLoader     * @param context     */    public static void init(Context context) {        //1.获取配置config对象        File cacheDir = StorageUtils.getCacheDirectory(context);  //缓存文件夹路径        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)                .threadPoolSize(3) // default  线程池内加载的数量                .threadPriority(Thread.NORM_PRIORITY - 2) // default 设置当前线程的优先级                .tasksProcessingOrder(QueueProcessingType.FIFO) // default                .denyCacheImageMultipleSizesInMemory()                .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) //可以通过自己的内存缓存实现                .memoryCacheSize(2 * 1024 * 1024)  // 内存缓存的最大值                .memoryCacheSizePercentage(13) // default                .diskCache(new UnlimitedDiscCache(cacheDir)) // default 可以自定义缓存路径                .diskCacheSize(50 * 1024 * 1024) // 50 Mb sd卡(本地)缓存的最大值                .diskCacheFileCount(100)  // 可以缓存的文件数量                // default为使用HASHCODE对UIL进行加密命名, 还可以用MD5(new Md5FileNameGenerator())加密                .diskCacheFileNameGenerator(new HashCodeFileNameGenerator())                .imageDownloader(new BaseImageDownloader(context)) // default                .imageDecoder(new BaseImageDecoder(true)) // default                .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default                .writeDebugLogs() // 打印debug log                .build(); //开始构建        //2.初始化配置...ImageLoader.getInstance()图片加载器的对象,单例模式        ImageLoader.getInstance().init(config);    }    /**     * imageLoader加载图片的默认选项     * @return     */    public static DisplayImageOptions getDefaultOption(){        DisplayImageOptions options = new DisplayImageOptions.Builder()                .showImageOnLoading(R.mipmap.ic_launcher) // 设置图片下载期间显示的默认图片                .showImageForEmptyUri(R.mipmap.ic_launcher) // 设置图片Uri为空或是错误的时候显示的图片                .showImageOnFail(R.mipmap.ic_launcher) // 设置图片加载或解码过程中发生错误显示的图片                .resetViewBeforeLoading(true)  // default 设置图片在加载前是否重置、复位                .delayBeforeLoading(1000)  // 下载前的延迟时间                .cacheInMemory(true) // default  设置下载的图片是否缓存在内存中                .cacheOnDisk(true) // default  设置下载的图片是否缓存在SD卡中                .considerExifParams(true) // default                .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default 设置图片以如何的编码方式显示                .bitmapConfig(Bitmap.Config.RGB_565) // default 设置图片的解码类型                .displayer(new SimpleBitmapDisplayer()) // default  还可以设置圆角图片new RoundedBitmapDisplayer(20)                .build();        return options;    }    /**     * imageLoader加载圆角图片....指定圆角的大小     * @return     */    public static DisplayImageOptions getRoundedOption(int corner){        DisplayImageOptions options = new DisplayImageOptions.Builder()                .showImageOnLoading(R.mipmap.ic_launcher) // 设置图片下载期间显示的图片                .showImageForEmptyUri(R.mipmap.ic_launcher) // 设置图片Uri为空或是错误的时候显示的图片                .showImageOnFail(R.mipmap.ic_launcher) // 设置图片加载或解码过程中发生错误显示的图片                .resetViewBeforeLoading(true)  // default 设置图片在加载前是否重置、复位                .delayBeforeLoading(1000)  // 下载前的延迟时间                .cacheInMemory(true) // default  设置下载的图片是否缓存在内存中                .cacheOnDisk(true) // default  设置下载的图片是否缓存在SD卡中                .considerExifParams(true) // default                .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default 设置图片以如何的编码方式显示                .bitmapConfig(Bitmap.Config.RGB_565) // default 设置图片的解码类型                .displayer(new RoundedBitmapDisplayer(corner)) // default  还可以设置圆角图片new RoundedBitmapDisplayer(20)                .build();        return options;    }}


六. 数据接口封装的bean类(根据自己需要定义)
七. 主功能代码;MainActivity.java

import android.os.Bundle;import android.os.Handler;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.LinearLayoutManager;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ImageView;import com.bwie.duhongwang.adapter.MyAdapter;import com.bwie.duhongwang.bean.ProductsBean;import com.bwie.duhongwang.presenter.DataPresenter;import com.bwie.duhongwang.view.DataView;import com.jcodecraeer.xrecyclerview.XRecyclerView;import butterknife.Bind;import butterknife.ButterKnife;import butterknife.OnClick;//列表页public class MainActivity extends AppCompatActivity implements DataView {    @Bind(R.id.grid_icon)    ImageView gridIcon;    @Bind(R.id.find)    Button find;    @Bind(R.id.xRecyclerView)    XRecyclerView xRecyclerView;    @Bind(R.id.editWords)    EditText editWords;    private int num = 1;    //判断变量改变布局    private int page = 1;   //初始化页数数据    private MyAdapter adapter;    private Handler handler = new Handler();    private DataPresenter dataPresenter = new DataPresenter(this,this);    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);        //默认显示一页数据        dataPresenter.getData("笔记本","1");    }    //view 层 的方法 用来更新ui    @Override    public void showView(ProductsBean productsBean) {        //设置线性布局管理器,加载数据        if (num % 2 == 1) {            //设置线性布局管理器,加载数据            LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false);            xRecyclerView.setLayoutManager(layoutManager);            gridIcon.setBackgroundResource(R.drawable.lv_icon);        }        //设置布局适配器,,,线性布局管理器,加载数据        adapter = new MyAdapter(MainActivity.this,  productsBean);        xRecyclerView.setAdapter(adapter);        //XRecyclerview的上拉下拉方法        xRecyclerView.setLoadingListener(new XRecyclerView.LoadingListener() {            @Override            public void onRefresh() {                handler.postDelayed(new Runnable() {                    @Override                    public void run() {                        //在子线程内上拉刷新数据                        dataPresenter.getData(editWords.getText().toString(),"1");                        adapter.notifyDataSetChanged();                        xRecyclerView.refreshComplete();                    }                },800);            }            @Override            public void onLoadMore() {                //在子线程内下拉加载数据                handler.postDelayed(new Runnable() {                    @Override                    public void run() {                        page++;                        dataPresenter.getData(editWords.getText().toString(),page+"");                        adapter.notifyDataSetChanged();                        xRecyclerView.loadMoreComplete();                    }                },800);            }        });    }    @OnClick({R.id.grid_icon, R.id.find})    public void onViewClicked(View view) {        switch (view.getId()) {            case R.id.grid_icon:                //根据num变量是奇数还是偶数来判断加载哪种布局,并返回对应布局的图片                num++;                if (num % 2 == 0) {                    //设置网格布局管理器,加载数据                    GridLayoutManager layoutManager = new GridLayoutManager(this, 2);                    xRecyclerView.setLayoutManager(layoutManager);                    gridIcon.setBackgroundResource(R.drawable.grid_icon);                }                if (num % 2 == 1) {                    //设置线性布局管理器,加载数据                    LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL, false);                    xRecyclerView.setLayoutManager(layoutManager);                    gridIcon.setBackgroundResource(R.drawable.lv_icon);                }                break;            case R.id.find:                //点击搜索按钮时触发presenter的获取数据方法                dataPresenter.getData(editWords.getText().toString(),"1");                break;            default:                break;        }    }    //实现presenter内部的防止内存溢出方法    @Override    protected void onDestroy() {        super.onDestroy();        dataPresenter.destory();    }}

八. 适配器类

import android.content.Context;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.TextView;import com.bwie.duhongwang.R;import com.bwie.duhongwang.bean.ProductsBean;import com.bwie.duhongwang.util.ImageLoaderUtil;import com.nostra13.universalimageloader.core.ImageLoader;import butterknife.Bind;import butterknife.ButterKnife;//自定义适配器public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {    private Context context;    private ProductsBean productsBean;    public MyAdapter(Context context, ProductsBean productsBean) {        this.context = context;        this.productsBean = productsBean;    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        View view = LayoutInflater.from(context).inflate(R.layout.item, null);        return new ItemViewHolder(view);    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        //设置条目信息        ItemViewHolder viewHolder = (ItemViewHolder) holder;        viewHolder.title.setText("标题: "+productsBean.getData().get(position).getTitle() );        viewHolder.price.setText("价格: "+productsBean.getData().get(position).getPrice() );        String images = productsBean.getData().get(position).getImages();        String[] split = images.split("\\|");        //使用Glide加载图片容易闪烁,错位,效果不如imageloader好        //Glide.with(context).load(split[0]).into(viewHolder.image);        ImageLoader.getInstance().displayImage(split[0], viewHolder.image, ImageLoaderUtil.getDefaultOption());    }    @Override    public int getItemCount() {        return productsBean.getData() == null ? 0 : productsBean.getData().size();    }    static class ItemViewHolder extends RecyclerView.ViewHolder {        @Bind(R.id.image)        ImageView image;        @Bind(R.id.title)        TextView title;        @Bind(R.id.price)        TextView price;        public ItemViewHolder(View itemView) {            super(itemView);            ButterKnife.bind(this, itemView);        }    }}
. 页面布局
1. activity_main.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="50dp"        android:id="@+id/relative01">        <TextView            android:layout_centerInParent="true"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textSize="21sp"            android:text="搜索商品" />        <ImageView            android:background="@drawable/lv_icon"            android:layout_alignParentRight="true"            android:layout_centerVertical="true"            android:layout_marginRight="10dp"            android:layout_height="35dp"            android:layout_width="35dp"            android:id="@+id/grid_icon" />    </RelativeLayout>    <View        android:id="@+id/view01"        android:background="#000"        android:layout_height="1dp"        android:visibility="visible"        android:layout_width="match_parent"        android:layout_below="@+id/relative01"></View>    <RelativeLayout        android:layout_width="match_parent"        android:layout_below="@+id/view01"        android:layout_height="50dp"        android:id="@+id/relative02">        <EditText            android:layout_centerVertical="true"            android:layout_height="wrap_content"            android:layout_marginLeft="38dp"            android:layout_width="218dp"            android:id="@+id/editWords"            android:hint="请输入关键词"            android:textSize="21sp" />        <Button            android:text="搜索"            android:id="@+id/find"            android:layout_marginRight="38dp"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentRight="true"/>    </RelativeLayout>    <View        android:id="@+id/view02"        android:background="#000"        android:visibility="visible"        android:layout_height="1dp"        android:layout_width="match_parent"        android:layout_below="@+id/relative02"></View>    <com.jcodecraeer.xrecyclerview.XRecyclerView        android:id="@+id/xRecyclerView"        android:layout_below="@+id/view02"        android:layout_width="match_parent"        android:layout_height="match_parent" ></com.jcodecraeer.xrecyclerview.XRecyclerView></RelativeLayout>
2. item.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:padding="5dp"    android:orientation="horizontal"    android:layout_width="match_parent"    android:layout_height="match_parent">    <ImageView        android:id="@+id/image"        android:layout_width="100dp"        android:layout_height="100dp"        android:src="@mipmap/ic_launcher"/>    <LinearLayout        android:layout_height="100dp"        android:orientation="vertical"        android:layout_marginLeft="5dp"        android:layout_marginTop="10dp"        android:layout_width="match_parent"  >        <TextView            android:id="@+id/title"            android:layout_weight="1"            android:layout_height="0dp"            android:layout_width="wrap_content"/>        <TextView            android:id="@+id/price"            android:layout_weight="1"            android:layout_height="0dp"            android:layout_width="wrap_content"/>    </LinearLayout></LinearLayout>

十. 清单文件AndrodiManifest.xml

1. 配置ImageLoader的全局初始化类(name属性);

2. 添加请求网络数据的权限(INTENT)。

阅读全文
0 0