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(3) OnUiCallbackmap,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); } }
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{ 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)。