您的位置:首页 > 移动开发 > Android开发

android群英传笔记——ListView常用优化技巧(一、使用ViewHolder模式提高效率)

2016-10-28 19:29 633 查看
一段时间没有更新博客了,最近开始了JSP的课程,同时也要学习Android,所以进度有所下降,但是不影响笔者Android群英传的学习。


一、使用ViewHolder模式提高效率

使用ViewHolder模式可以提高50%以上的效率,其使用方法也是比较简单的。只要在自定义的Adapter中定义一个内部类,再将子项的控件获取到并存储进内部类即可。其作用就是优化代码,减少加载控件的时间。

其内部类的定义方法如下所示:


/**
* 用于缓存已经加载过的子项控件
*/
public final class ViewHolder {
public ImageView img;
public TextView title;
}


以下是测试过程:

程序运行如图所示:




笔者在自定义Adapter内执行ViewHolder时,添加日志输出,以下是第一次运行时的log日志:


10-28 11:58:30.561 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.634 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.669 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.689 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.707 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.712 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!


读者们可以看出其中的关系吗?对,log缓存次数其实就是屏幕上加载的子项数量,可以看出第一次加载时的速度并没有太大提升,因为都需要进行相同的加载次数。

于是笔者向下滑动了ListView,日志如下所示:


10-28 11:58:30.561 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.634 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.669 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.689 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.707 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:30.712 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!
10-28 11:58:33.006 14886-14886/com.example.listviewcontrol3 I/HolderAdapter: HolderAdapterGetView: 执行缓存!


其实笔者滑到了底部,笔者在ListView中设置了10个子项,但是log日志只加载了7次,不是10次吗?不是,在加载完屏幕的子项后,只加载了一次,也就意味着除了屏幕上的加载以外,未显示的子项在第一次加载后,不进行重复加载。也就是说直接调用了ViewHolder内存储的已加载的控件,以此优化了ListView。

笔者在ListView的上方加上了删除和添加子项的按钮,以此测试当ListView添加新的子项时是否直接调用缓存好的子项控件。结果发现日志没有显示执行缓存,意味着在后期添加子项时都无需再加载,直接调用。可见,在子项比较多的时候,此方法可大幅度优化ListView的滑动。


代码

以下是测试代码:

activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context="com.example.listviewcontrol3.MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">

<!--用于添加子项的按钮-->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_add"
android:id="@+id/btn_add"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/btn_div"
android:id="@+id/btn_div" />

</LinearLayout>

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<!--可以通过 android:divider="@null" 来设置透明分割线-->
<!--android:scrollbars="none" 可以设置隐藏滚动条-->
<!--android:listSelector="@android:color/transparent" 用来取消item的点击效果-->
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/red"
android:dividerHeight="3dp"
android:scrollbars="none"
android:listSelector="@android:color/transparent"
android:id="@+id/list_view" />

<include
layout="@layout/empty_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/empty_view"/>

</FrameLayout>

</LinearLayout>


viewholder_item.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/imageView"/>

<TextView
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="20dp"
android:textSize="24sp"
android:id="@+id/textView"/>

</LinearLayout>


empty_item.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/list_item_empty"
android:gravity="center"
android:textSize="30sp"/>

</LinearLayout>


MainActivity.java


package com.example.listviewcontrol3;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

private ArrayList<String> list;
private HolderAdapter adapter;
private ListView listView;
private String TAG = "HolderAdapter";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 随意加入数据,在适配器内已经设置了图片,故只用向适配器传递一个字符串集合即可
list = new ArrayList<>();
for(int i = 0; i < 10; i++){
list.add(getResources().getString(R.string.list_item_text));
}
adapter = new HolderAdapter(this, list);

listView = (ListView) findViewById(R.id.list_view);
// 假设控件存在
assert listView != null : "未找到listVie!!!";
listView.setAdapter(adapter);
// 给listView 设置没有数据时显示的视图
listView.setEmptyView(findViewById(R.id.empty_view));
//listView.setOnTouchListener(new OnTouchListViewListener());
listView.setOnScrollListener(new OnListScrollListener());

Button btnAdd = (Button) findViewById(R.id.btn_add);
assert btnAdd != null : "未找到btn_add按钮!!!";
btnAdd.setOnClickListener(new OnAddClickListener());

Button btnDiv = (Button) findViewById(R.id.btn_div);
assert btnDiv != null : "未找到btn_div按钮!!!";
btnDiv.setOnClickListener(new OnDivClickListener());
}

/**
* 添加add按钮监听事件类
*/
private class OnAddClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
// 给数据源添加数据
list.add("new!!!");
// 刷新界面
adapter.notifyDataSetChanged();
// 设置初始时显示的位置
listView.setSelection(list.size() - 1);
Log.i(TAG, "btnAddOnClick: 添加成功!");
}
}

/**
* 添加div按钮监听事件类
*/
private class OnDivClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
if(list.size() > 0) {
// 删除数据源中的数据
list.remove(list.size() - 1);
// 刷新界面
adapter.notifyDataSetChanged();
// 设置初始时显示的位置
listView.setSelection(list.size() - 1);
Log.i(TAG, "btnDivOnClick: 删除成功!");
} else {
Toast.makeText(MainActivity.this,R.string.item_not_clear, Toast.LENGTH_SHORT).show();
Log.i(TAG, "btnDivOnClick: list为空!");
}
}
}

/**
* listView的触摸监听类
*/
private class OnTouchListViewListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 触碰到时的操作
Log.i(TAG, "onTouch: ACTION_DOWN!");
break;
case MotionEvent.ACTION_MOVE:
// 移动时的操作
Log.i(TAG, "onTouch: ACTION_MOVE!");
break;
case MotionEvent.ACTION_UP:
// 离开时操作
Log.i(TAG, "onTouch: ACTION_UP!");
break;
}
return false;
}
}

/**
* 滑动事件监听类
*/
private class OnListScrollListener implements AbsListView.OnScrollListener {
/**
* 监听滑动状态
* @param view 监听的控件
* @param scrollState 监听控件的状态
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState){
case OnListScrollListener.SCROLL_STATE_IDLE:
// 滑动停止时
Log.d(TAG, "onScrollStateChanged: scrollStop!");
break;
case OnListScrollListener.SCROLL_STATE_TOUCH_SCROLL:
// 正在滑动
Log.d(TAG, "onScrollStateChanged: scrolling!");
break;
case OnListScrollListener.SCROLL_STATE_FLING:
// 手指抛动时,即手指用力滑动在离开后ListView由于惯性继续滑动
Log.d(TAG, "onScrollStateChanged: scrollFling!");
break;
}

}

/**
* 监听listView的滚动状态
* @param view 当前view
* @param firstVisibleItem 第一个显示的子项id
* @param visibleItemCount 可见的子项总数,包括显示部分的子项
* @param totalItemCount 子项的总数
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 滚动时一直调用
Log.d(TAG, "onScroll: onScroll!!!");
if(firstVisibleItem+visibleItemCount==totalItemCount && totalItemCount>0){
// 滚动到最后一行
Log.d(TAG, "onScroll: OnLastItem!");
}
}
}
}


HolderAdapter.java


package com.example.listviewcontrol3;

import android.content.Context;
import android.util.Log;
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 java.util.List;

/**
* 利用ViewHolder提高效率
* Created by shize on 2016/10/24.
*/
public class HolderAdapter extends BaseAdapter {

private List<String> mData;
private LayoutInflater mInflater;
private String TAG = "HolderAdapter";

public HolderAdapter(Context context, List<String> data) {
this.mData = data;
mInflater = LayoutInflater.from(context);
}

@Override
public int getCount() {
return mData.size();
}

@Override
public Object getItem(int position) {
return mData.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
// 判断是否缓存,若控件已经加载过了,就跳过加载,直接对控件进行样式参数的设置
if (convertView == null) {
holder = new ViewHolder();
// 通过LayoutInflater实例化布局
convertView = mInflater.inflate(R.layout.viewholder_item, null);
// 加载控件,缓存进holder内
holder.img = (ImageView) convertView.findViewById(R.id.imageView);
holder.title = (TextView) convertView.findViewById(R.id.textView);
convertView.setTag(holder);
Log.i(TAG, "HolderAdapterGetView: 执行缓存!");
} else {
// 通过tag找到缓存的布局
holder = (ViewHolder) convertView.getTag();
}
// 设置子项控件的图片和文字
holder.img.setImageResource(R.drawable.ic_launcher);
holder.title.setText(mData.get(position));
return convertView;
}

/** * 用于缓存已经加载过的子项控件 */ public final class ViewHolder { public ImageView img; public TextView title; }
}


感谢阅读,学习重在坚持,贵在坚持,那么下次再见。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android listview 优化