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

来源:互联网 发布:哪个fm软件好 知乎 编辑:程序博客网 时间:2024/06/05 20:17
    一段时间没有更新博客了,最近开始了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;    }}
感谢阅读,学习重在坚持,贵在坚持,那么下次再见。
0 0
原创粉丝点击