天天看點

閱讀徐宜生《Android群英傳》的筆記——第4章 ListView 使用技巧——到4.1.7

4.1 ListView 常用優化技巧

ListView 的基本用法相信大多數的讀者都已經能夠非常熟練的使用了,下面我們就着重來學習一下使用 ListView 的技巧。

4.1.1 使用 ViewHolder 模式提高效率

ViewHolder 模式是提高 ListView 效率的一個很重要的方法。ViewHolder 模式充分利用了 ListView 的視圖緩存機制,避免了每次在調用 getView() 的時候都去通過 findViewById() 執行個體化控件。據測試,使用 ViewHolder 将提高 50% 以上的效率。使用 ViewHolder 模式來優化 ListView 非常簡單,隻需要在自定義 Adapter 中定義一個内部類 ViewHolder,并将布局中的控件作為成員變量,代碼如下所示:

public final class ViewHolder {
    public ImageView imgIcon;
    public TextView txtTitle;
}
           

接下來,隻要在 getView() 方法中通過視圖緩存機制來重用以緩存即可,完整的使用 ViewHolder 建立 ListView Adapter 的執行個體代碼如下所示:

package com.example.test;

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 java.util.List;

/**
* Created by HourGlassRemember on 2016/9/18.
*/
public class ViewHolderAdapter extends BaseAdapter {

    private List<String> mData;
    private LayoutInflater mInflater;

    public ViewHolderAdapter(Context context, List<String> mData) {
        this.mData = mData;
        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.imgIcon = (ImageView) convertView.findViewById(R.id.img_icon);
            holder.txtTitle = (TextView) convertView.findViewById(R.id.txt_title);
            convertView.setTag(holder);
        } else {
            //通過 tag 找到緩存的布局

            holder = (ViewHolder) convertView.getTag();
        }
        //設定布局中控件要顯示的視圖

        holder.imgIcon.setBackgroundResource(R.mipmap.ic_launcher);
        holder.txtTitle.setText(mData.get(position));
        return convertView;
    }

    public final class ViewHolder {
        public ImageView imgIcon;
        public TextView txtTitle;
    }

}
           

item 的布局檔案 viewholder_view 是:

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

    <ImageView
        android:id="@+id/img_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/txt_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />

</LinearLayout>
           

MainActivity 的布局檔案 activity_main 是:

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

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>
           

MainActivity 的代碼是:

package com.example.test;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private ListView listView;
    private List<String> mData = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);
        for (int i = ; i < ; i++) {
            mData.add(i + "");
        }
        listView.setAdapter(new ViewHolderAdapter(this, mData));
    }

}
           

效果很簡單如下圖所示,這就是一個簡單的 ListView。

閱讀徐宜生《Android群英傳》的筆記——第4章 ListView 使用技巧——到4.1.7

4.1.2 設定項目間分割線

ListView 的各個項目之間,可以通過設定分割線來進行區分,系統提供了 divider 和 dividerHeight 這樣兩個屬性來幫助我們實作這一功能。通過這兩個屬性,也可以控制 ListView 之間的分割線和它的高度。當然分割線不僅僅可以設定為一個顔色,同樣也可以設定為一個圖檔資源,分割線的使用代碼如下所示:

android:divider="#ccc"
android:dividerHeight="10dp"
           

以上代碼所執行的效果是:

閱讀徐宜生《Android群英傳》的筆記——第4章 ListView 使用技巧——到4.1.7

特殊情況下,當設定分割線為如下代碼時,就可以把分割線設定為透明了。

效果如下所示:

閱讀徐宜生《Android群英傳》的筆記——第4章 ListView 使用技巧——到4.1.7

4.1.3 隐藏 ListView 的滾動條

預設的 ListView 在滾動時,在右邊會顯示滾動條,訓示目前滑動的位置,我們可以設定 scrollbars 屬性,控件 ListView 的滾動條狀态。特别地,當設定 scrollbars 屬性為 none 的時候,ListView 滾動或者不滾動,就都不會出現滾動條了,代碼如下所示:

4.1.4 取消 ListView 的 Item 點選效果

當點選 ListView 中的一項時,系統預設會出現一個點選效果,在 Android 5.X 上是一個波紋效果,而在 Android 5.X 之下的版本則是一個改變背景顔色的效果,但是可以通過修改 listSelector 屬性來取消掉點選後的回饋效果,代碼如下所示:

當然,也可以直接使用 Android 自帶的透明色來實作這個效果,代碼如下所示:

4.1.5 設定 ListView 需要顯示在第幾頁

ListView 以 Item 為機關進行顯示,預設顯示在第一個 Item,當需要指定具體顯示的 Item 時,可以通過以下代碼來實作:

listView.setSelection(N);
           

其中 N 就是需要顯示的第 N 個 Item。

當然,這個方法類似 scrollTo,是瞬間完成的移動。除此之外,還可以使用如下代碼來實作平滑移動:

listView.smoothScrollBy(distance,duration);
listView.smoothScrollByOffset(offset);
listView.smoothScrollToPosition(index);
           

4.1.6 動态修改 ListView

ListView 中的資料在某些情況下是需要改變的,當然可以通過重新設定 ListView 的 Adapter來更新 ListView 的顯示,但這也就需要重新擷取一下資料,相當于重新建立 ListView,這也顯然不是非常友好,而且效率也不會太高。是以,可以使用一個更簡單的方法來實作 ListView 的動态修改,代碼如下所示:

mData.add("new");
adapter.notifyDataSetChanged();
           

當修改了傳遞給 Adapter 的映射 List 之後,隻需要通過調用 Adapter 的 notifyDataSetChanged() 方法,通知 ListView 更改資料源即可完成對 ListView的動态修改。不過使用這個方法有一點需要注意的是,在使用 adapter.notifyDataSetChanged() 方法時,必須保證傳進 Adapter 的資料 List 是同一個 List 而不能是其他對象,否則将無法實作該效果。下面這個執行個體就示範了如何動态地修改 ListView。代碼如下所示:

package com.example.test;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {

    private ListView listView;
    private List<String> mData = new ArrayList<>();
    private ViewHolderAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.list_view);
        for (int i = ; i < ; i++) {
            mData.add(i + "");
        }
        adapter = new ViewHolderAdapter(this, mData);
        listView.setAdapter(adapter);
        btnAdd(listView.getChildAt());
    }

    public void btnAdd(View view) {
        mData.add("new");
        adapter.notifyDataSetChanged();
        listView.setSelection(mData.size() - );
    }

}
           

運作效果如下所示:

閱讀徐宜生《Android群英傳》的筆記——第4章 ListView 使用技巧——到4.1.7

4.1.7 周遊 ListView 中的所有 Item

ListView 作為一個 ViewGroup,為我們提供了操縱子 View 的各種方法,最常用的就是通過 getChildAt(i) 來擷取第 i 個子 View,代碼如下所示:

for (int i = ; i < listView.getChildCount(); i++) {
    View view = listView.getChildAt(i);
}