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

一段时间没有更新博客了,最近开始了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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值