【ListView优化方式有多种多样,而我们本文作为ListView优化系列文章的第一篇文章,欢迎大家持续关注ListView优化系列文章并关注Jimy的博客。有误的地方,希望大家能多多指正。】
ListView优化之一:重用convertView
重用convertVIew,很大程度上减少了Android设备内存的消耗。
(通过判断convertView是否为null,是的话就需要产生一个view出来,然后给这个视图数据,最后将这个视图返回给底层,呈现给用户)。
先上效果图:
由于本文主要是在描述如何重用contertView来优化ListView,因此本文不会过多的描述其他内容,会直接使用到Gson解析json数据、第三方框架OkHttp。给各位推荐下一个格式化json数据的网址:http://www.bejson.com
(通过判断convertView是否为null,是的话就需要产生一个view出来,然后给这个视图数据,最后将这个视图返回给底层,呈现给用户)。【注:案例是在Android Studio上运行,这样的话,添加依赖或者说导入类库相比于Eclipse就会方便快捷很多。】
首先来看一下demo的包是如何建的,如下图:
1.首先,我们来看一下需要添加的依赖包,我们需要在builde文件中添加如下两个依赖:
compile 'com.squareup.okhttp3:okhttp:3.3.0' compile 'com.google.code.gson:gson:2.6.2'
然后就可以进行代码编写了。
2.我们来看一下工具类OkHttpUtils,需要提醒大家的是本文中并没有使用到Post方式请求网络数据,而是只使用了Get请求,不过在这轮系列博文中是会使用到Post方式请求网络数据的,欢迎大家继续关注。
OkHttpUtils类代码如下:
package com.jimy.convertview.util; import android.os.Handler; import android.os.Looper; import java.io.IOException; import java.util.Map; import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.Callback; import okhttp3.FormBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; /** * OkHttp的工具类 */ public class OkHttpUtils { private static OkHttpClient okHttpClient; private static Handler handler = new Handler(Looper.getMainLooper()); /** * 初始化OkHttpUtils */ public static void initOkHttp() { okHttpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build(); } /** * 异步Get请求 * * @param url */ public static void getSubmitForm(final String url, final OnGetDataListener onGetDataListener) { Request request = new Request.Builder() .url(url) .build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { if (onGetDataListener != null) { onGetDataListener.onFailure(url, e.getMessage()); } } @Override public void onResponse(Call call, final Response response) throws IOException { final String str = response.body().string(); handler.post(new Runnable() { @Override public void run() { if (onGetDataListener != null) { onGetDataListener.onResponse(url, str); } } }); } }); } /** * Post提交键值对参数 */ public static void postSubmitForm(final String url, Map, Object> params, final OnGetDataListener onGetDataListener) { FormBody.Builder builder = new FormBody.Builder(); if (params != null && params.size() > 0) { for (String key : params.keySet()) { String value = String.valueOf(params.get(key)); builder.add(key, value); } } FormBody formBody = builder.build(); final Request request = new Request.Builder() .url(url) .post(formBody) .build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { if (onGetDataListener != null) { onGetDataListener.onFailure(url, e.getMessage()); } } @Override public void onResponse(Call call, final Response response) throws IOException { final String str = response.body().string(); handler.post(new Runnable() { @Override public void run() { if (onGetDataListener != null) { onGetDataListener.onResponse(url, str); } } }); } }); } public interface OnGetDataListener { void onResponse(String url, String strs); void onFailure(String url, String error); } }
3.为了方便初始化OkHttp并且使我们的代码复用性更强,在这里我们需要创建一个BaseApplication类来继承Application,主要是为了我们的App在启动时直接初始化更多的第三方框架,也为了方便我们在不同的Activity或Fragment等中使用这些第三方框架。
BaseApplication类代码如下:
package com.jimy.convertview.base; import android.app.Application; import com.jimy.convertview.util.OkHttpUtils; public class BaseApplication extends Application { private static BaseApplication application; @Override public void onCreate() { super.onCreate(); application = this; // 初始化OkHttpUtils OkHttpUtils.initOkHttp(); } public static BaseApplication getApplication(){ return application; } }
当然,重写了继承Application的BaseApplication类,我们需要在AndroidManifest.xml文件中添加name属性。同时OkHttp需要进行联网加载网络数据,所以还需要添加网络权限。如下:
//网络权限
android:name="android.permission.INTERNET"/>
android:name=".base.BaseApplication">
4.我们单独在这里加了一个包来放常量Url,这可以让程序一目了然、十分清晰。
RequestUrl类代码如下:(只是存了一个Url地址,请注意本文中使用的pageSize=50,在ListView优化的后续博文中,我们还会对这个数据进行修改优化,欢迎继续关注)
package com.jimy.convertview.urls; public class RequestUrl { public static final String ENTITY_URL = "http://mrobot.pcauto.com.cn/v2/cms/channels/2?pageNo=1&pageSize=50&v=4.0.0"; }有了上面的Url地址,我们可以进行联网获得json数据,然后完全复制这些原始数据,并放入到http://www.bejson.com网址下进行格式化,便于我们查看信息内容。这时,我们通过Android Studio里的GsonFormat插件(这个插件相信大家肯定都安装了吧?功能很强大,给我们写代码省了好多时间。)来自动生成ListViewEntity类,这各类就略过不详细描述咯(太简单了,都会的)。
5.有了上边所有的这些内容,相信你已经迫不及待想要继续编写出正式的代码了吧。好的,接下来我们就来叙述我们的Activity。在我们的Activity中,我们需要显示我们的ListView的item中数据,并需要使用到OkHttpUtils工具类。
MainAcity类代码如下:
package com.jimy.convertview.activity; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.ListView; import com.google.gson.Gson; import com.jimy.convertview.R; import com.jimy.convertview.adapter.ListItemLayoutAdapter; import com.jimy.convertview.entity.ListViewEntity; import com.jimy.convertview.urls.RequestUrl; import com.jimy.convertview.util.OkHttpUtils; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private ListView mListView; private List在activity_main.xml文件中我们只显示了一个ListView,十分简单,如下:data; private ListItemLayoutAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化视图控件 initView(); //加载数据 loadData(); } private void loadData() { OkHttpUtils.getSubmitForm(RequestUrl.ENTITY_URL, new OkHttpUtils.OnGetDataListener() { @Override public void onResponse(String url, String strs) { //创建一个方法来显示数据 showListItemInfoView(strs); } @Override public void onFailure(String url, String error) { //当数据加载失败时,我们在这里进行打印日志输出url,异常信息error Log.d("MainActivity", url); Log.d("MainActivity", error); } }); } private void showListItemInfoView(String strs) { Gson gson = new Gson(); //gson解析数据 ListViewEntity listViewEntity = gson.fromJson(strs, ListViewEntity.class); data.addAll(listViewEntity.getData()); //对数据进行刷新显示 mAdapter.notifyDataSetChanged(); } private void initView() { mListView = (ListView) findViewById(R.id.listView); data = new ArrayList<>(); mAdapter = new ListItemLayoutAdapter(this, data); mListView.setAdapter(mAdapter); } }
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="10dp" android:layout_marginLeft="10dp" android:orientation="vertical"> android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"/>
【以上所有代码都是为我们接下来的ListView来优化而写的代码,在这轮ListView系列博文当中,我们会重复使用这里的代码,欢迎继续关注Jimy的博客!】
6.最重要的部分马上显现了,请稍后,莫急。
让我们来先用一张图来描述一下,适配器Adapter中被重复调用的getView(int position, View convertView, ViewGrop parent)方法。相信看了这张图,你能很清晰的了解为何ListView的优化通过重用convertView也能达到目的。如下图所示:
我们需要重复调用getView()这个方法。
接下来我们来看一看我们的Adapter,在这里,我们是直接使用泛型T来写的Adapter适配器,这样就有助于我们以后能持续的重复使用这个Adapter。在这里,我们还没有把图片加载进去,因为ListView的图片加载显示也是ListView优化方式之一,我们会在后续文章中提到,暂时就不过多深究这个问题了。
ListItemLayoutAdapter类代码如下:
package com.jimy.convertview.adapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import com.jimy.convertview.R; import com.jimy.convertview.entity.ListViewEntity; import java.text.SimpleDateFormat; import java.util.List; /** * 使用泛型来适配ListView的item内容,方便重用Adapter * @param <T> */ public class ListItemLayoutAdapter<T> extends BaseAdapter { private List<T> objects; private Context context; private LayoutInflater layoutInflater; public ListItemLayoutAdapter(Context context, List<T> objects) { this.context = context; this.layoutInflater = LayoutInflater.from(context); this.objects = objects; } @Override public int getCount() { return objects.size(); } @Override public T getItem(int position) { return objects.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; //要重用convertView,首先判断convertView是否null if (convertView == null) { convertView = layoutInflater.inflate(R.layout.list_item_layout, null); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); }else { viewHolder = (ViewHolder) convertView.getTag(); } //使用的泛型T Object obj = getItem(position); //在这里需要判断泛型的具体对象类型 if (obj instanceof ListViewEntity.DataBean){ ListViewEntity.DataBean entity = (ListViewEntity.DataBean) obj; //设置标题 viewHolder.tvTitle.setText(entity.getTitle()); //设置评论次数 viewHolder.tvCount.setText(entity.getCount()+"评论"); //设置时间 /** * 从网络上加载的时间是long类型的毫秒数 * 因此需要通过SimpleDateFormat来进行转换为特定的格式 * 在这里我们直接转换为yyyy-MM-dd的格式类型 */ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); String timeStr = format.format(entity.getMtime()); viewHolder.tvTime.setText(timeStr); } return convertView; } protected class ViewHolder { private ImageView imgPic; private TextView tvTitle; private TextView tvCount; private TextView tvTime; public ViewHolder(View view) { imgPic = (ImageView) view.findViewById(R.id.img_pic); tvTitle = (TextView) view.findViewById(R.id.tv_title); tvCount = (TextView) view.findViewById(R.id.tv_count); tvTime = (TextView) view.findViewById(R.id.tv_time); } } }这里的list_item_layout.xml文件就是ListView的item要显示的内容了,主要有一个ImgeView、三个TextView排布而成的。(为了方便,我们就直接使用的LinearLayout布局方式,在这里我们暂时不考虑LinearLayout的多层嵌套而造成的影响,只需要达到我们显示的效果即可。【注:多层LinearLayout是会影响到程序运行的效率的,这时候我们应该使用RelativeLayout布局进行加载会快很多。这里就暂时不描述了】)。
list_item_layout.xml代码如下:
xml version="1.0" encoding="utf-8"?>xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="110dp" android:layout_marginBottom="10dp" android:orientation="vertical"> android:layout_width="match_parent" android:layout_height="100dp" android:orientation="horizontal"> android:id="@+id/img_pic" android:layout_width="100dp" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@mipmap/img_bg" /> android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="10dp" android:orientation="vertical"> android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:text="Jimy的博客" android:textColor="@android:color/black" android:textSize="16sp" /> android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="bottom" android:orientation="horizontal"> android:id="@+id/tv_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="200评论" /> android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:orientation="horizontal"> android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="yyyy-MM-dd" />
ok,我们的ListView优化之一:重用convertView,就暂时讲到这里了,后续的ListView优化还会持续以博文的形式分享给大家,希望多多关注Jimy的博客。