Android—ListView 的各種使用方法


一、 AdapterView 及其子類

  AdapterView 是一組重要的組件,它的主要作用是通過列表的形式顯示數據。

  AdapterView 本身是一個抽象類,常用的 ListView(列表)、Spinner(下拉列表)、Gallery(縮略圖),GridView(網格圖)都是 AdapterView 的子類。子類的用法相似,知識在顯示上略有不同。

  AdapterView 的子類繼承關系

  

  下面是 AdapterView 的類定義(成員變量及方法省略)

/**
 * An AdapterView is a view whose children are determined by an {@link Adapter}. 
*  AdapterView是一個視圖,其子視圖由{@link Adapter}確定 * See {
@link ListView}, {@link GridView}, {@link Spinner} and {@link Gallery} for commonly used subclasses of AdapterView.
*  提示AdapterView 的常用子類
*/ public abstract class AdapterView<T extends Adapter> extends ViewGroup { }

  AdapterView 具有如下特征:

  1. AdapterView 繼承了 ViewGroup ,說明它本質是容器。

  2. AdapterView 可以包含多個 ”列表項“ (即子視圖),子視圖由與之關聯的 Adapter 確定,以合適的方式顯示出來。

二、AdapterView 的子類 ListView

  以垂直列表的形式顯示所有的列表項,並且能夠根據數據的長度自適應顯示。下面就是使用 AdapterView 實現的效果

  

   ListView 的使用大致上可以分為四個步驟:添加 ListView 組件、存儲數據、設置列表項item的布局文件、加載數據/資源進行顯示、添加監聽。下面講解幾個 ListView 的使用方法:

  1. 當整個Activity中只有一個ListView組件時,可以使用ListActivity。

  ListActivity類繼承於Activity類,默認綁定了一個ListView組件,並提供一些與ListView處理相關的操作。

  ListActivity類常用的方法為getListView(),該方法返回綁定的ListView組件。

  一旦在程序中獲得了ListView之后,接下來就需要為ListView設置它要顯示的列表項了。在這一點上,ListView顯示出了AdapterView的特征:通過setAdapter(Adapter)方法為之提供Adapter,並由Adapter提供列表項即可。

  2. 最簡單的方法——通過 android:entried 調用加載數組資源

  a. 在布局文件中添加 ListView 組件 ( ListView 的 id 號為  android:list 

  使用特定 id 號的好處:在 listView 中可以直接通過 getListView() 方法得到 ListView 實例

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

  b. 在 values 文件夾中添加或設置 string.xml 文件用來存儲數據(為了體現滾輪效果,可以適量增加 item 數目)

 <string-array name="fruit_array">
        <item>Apple</item>
        <item>Banana</item>
        <item>Orange</item>
        <item>Watermelon</item>
        <item>Pear</item>
        <item>Grape</item>
        <item>Pineapple</item>
        <item>Strawberry</item>
        <item>Cherry</item>
        <item>Mango</item>
    </string-array>

  c. 通過 android:entried 調用加載數組資源

   <ListView
        android:id="@+id/android:list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/fruit_array"  />

  由於這個方法只用於顯示文本類型的數據,不經常使用,添加監聽的部分在這里就不展開講解了

  3. AdapterView + Adapter

  使用數組資源創建 ListView 是一種非常簡單的方式,但是這種 ListView 能定制的內容很少。如果想豐富 ListView 的外觀、內容,添加組件的行為,就需要把 ListView 當作 AdapterView 來使用,通過 Adapter 自定義每一個 列表項的 外觀、內容、行為動作等。

  AdapterView + Adapter 的工作原理

  

   類似於 MVC 框架,數據源(Model)存放數據,利用控制器(Controller)將數據顯示在視圖(View)上。

  ListView 相當於 V(View),用於顯示視圖;Adapter 相當於 控制器(Controller)

  當需要數據時,ListView 會從 Adapter 中取出數據進行顯示。

  3-1 基於ArrayAdapter 使用 ListView

  a. 添加 ListView 組件(同上)

  b. 存儲數據。存儲數據的方式可以多種多樣的,可以將數據存儲在 string.xml 文件中,可以將其存儲為 string[] 數組形式,也可以將其存儲為 List 列表形式。

  c. 設置列表項item的布局文件

  item 中包含多種組件,例如 ImageView,Button 等,我們可以用 ArrayList 進行加載,將數據資源存儲在一個類中,接着創建一個類繼承 ArrayAdapter ,在 getView() 方法中將數據資源與 item 布局中的組件聯系在一起即可,這在《第一行代碼》有非常詳細的講解,在這篇博客中,我使用 SimpleAdapter 進行加載,會在下面進行講解。

  該 LIstView 只顯示最簡單的當行文本內容,因此選擇一個android 自帶的簡單布局

android.R.layout.simple_list_item_1

 

  d. 利用 adapter 將數據顯示在 ListView 上。也就是實例化 ArrayAdapter 類,在 ListView 中添加 Adapter 兩個步驟。

  實例化 ArrayAdapter 類

  ArrayAdapter 有很多的構造方法,例如

    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource) {
        this(context, resource, 0, new ArrayList<>());
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId) {
        this(context, resource, textViewResourceId, new ArrayList<>());
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) {
        this(context, resource, 0, Arrays.asList(objects));
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId, @NonNull T[] objects) {
        this(context, resource, textViewResourceId, Arrays.asList(objects));
    }
    public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @NonNull List<T> objects) {
        this(context, resource, 0, objects);
    }

public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @IdRes int textViewResourceId, @NonNull List<T> objects) { this(context, resource, textViewResourceId, objects, false); }

 

  我們可以發現,上面五個構造方法都是調用最后一個構造方法,而最后構造方法調用了另一個方法,這個方法是私有的。我們來詳細看看這個構造方法

    /**
     * Constructor
     * @param context 當前上下文。
     * @param resource 子項布局id:布局文件的資源ID,其中包含在實例化視圖時要使用的布局。
     * @param textViewResourceId 布局資源中要填充的TextView的ID
     * @param objects  要在ListView中表示的對象
     */
private ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
            @IdRes int textViewResourceId, @NonNull List<T> objects, boolean objsFromResources) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mResource = mDropDownResource = resource;
        mObjects = objects;
        mObjectsFromResources = objsFromResources;
        mFieldId = textViewResourceId;
    }

  在上面的構造方法中,我們最經常使用的是第三個

    /**
     * Constructor
     * @param context 當前上下文。
     * @param resource 子項布局id:布局文件的資源ID,其中包含在實例化視圖時要使用的布局。
     * @param objects  要在ListView中表示的對象
     */
public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) {
  
this(context, resource, 0, Arrays.asList(objects));
}

 

  可以看到,在這個方法中,使用 泛型 來表示傳進來的要在 ListView 中顯示的對象,着說明你可以傳入許多種數據類型,但是最終會會使用 Arrays.asList(Objects)) 進行類型轉換,返回 MutableList

  

   d. 添加監聽——這里只添加 列表項的點擊事件。使用

  總的代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//獲取 ListView 控件 ListView listView = (ListView)findViewById(R.id.list_view);
//獲取數據 final String[] fruitArray = getResources().getStringArray(R.array.fruit_array);
// 為 ListView 添加控制器 ArrayAdapter ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1,fruitArray); listView.setAdapter(adapter);
//為 ListView 的列表項添加鼠標點擊事件 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { /** * @param adapterView 發生單擊事件的列表項 ListView * @param view 被單擊控件 view * @param i 在列表項中的位置 position * @param l 被單擊列表項的行ID */ @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { String Tag = "onItemClick======"; Log.d(Tag,"position="+i); Log.d(Tag,"行 ID"+l); Toast.makeText(MainActivity.this,fruitArray[i],Toast.LENGTH_SHORT); } }); } }

  3-2 基於 SimpleAdapter 使用 ListView

  SimpleAdapter的擴展性最好,可以定義各種各樣的布局出來,可以放上ImageView(圖片),還可以放上Button(按鈕),CheckBox(復選框)等等

  a. 添加 ListView 組件(同上)

  b. 設置列表項 item 的布局文件

<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:id="@+id/image"
        android:layout_weight="4"
        android:layout_width="0dp"
        android:layout_height="150dp"
        android:layout_gravity="left"
        android:layout_marginLeft="1dp"
        android:padding="7dp"/>
    <LinearLayout
        android:layout_weight="6"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center"
        android:layout_marginLeft="5dp">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"/>
        <TextView
            android:id="@+id/info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"/>
    <Button
        android:id="@+id/button"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="@drawable/btn"
        android:layout_gravity="right"
        android:layout_marginRight="30dp"
        android:padding="10dp"
        android:focusable="false"/>
        <!--android:focusable="false"-->
    </LinearLayout>
</LinearLayout>

 

  效果:

  c. 存儲數據。
  同樣,我們通過分析 SimplaeAdapter 構造方法來決定數據類型。SimpleAdapter 只有一個構造方法

 /**
     * Constructor
     *
     * @param context 運行與此SimpleAdapter關聯的View的上下文,即放置 ListView 的上下文環境
     * @param data 數據為一個列表 list ,list 中的數據以 Map 類型存儲列表中的每個條目對應於列表中的一行。 列表中的每個條目對應於列表中的一行。 
     * @param resource 列表項的布局文件
     * @param from A list of column names that will be added to the Map associated with each item.
     * @param to The views that should display column in the "from" parameter. These should all be
     *        TextViews. The first N views in this list are given the values of the first N columns
     *        in the from parameter.
     */
    public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
            @LayoutRes int resource, String[] from, @IdRes int[] to) {
        mData = data;
        mResource = mDropDownResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

 

  可以知道,數據存儲在 List 中,List 中每一項數據對應 ListView 的一行,以 Map 的形式進行存儲。 from 和 to 數組分別代表Map中 key 值和 Item 布局文件中組件的 ID 號,呈一一對應關系。

 public List<HashMap<String,Object>> getData(){
        List<HashMap<String,Object>> list = new ArrayList<>();

        HashMap<String,Object> map;
        map= new HashMap<>();
        map.put("image",R.drawable.i1);
        map.put("title","開心的哆啦A夢");
        map.put("info","cute cute cute cute cute cute cute cute cute cute ");
        list.add(map);

        map = new HashMap<>();
        map.put("image",R.drawable.i2);
        map.put("title","貪吃的哆啦A夢");
        map.put("info","卡哇伊  卡哇伊 卡哇伊 卡哇伊 卡哇伊 卡哇伊 卡哇伊 ");
        list.add(map);

        map = new HashMap<>();
        map.put("image",R.drawable.i3);
        map.put("title","大哭的哆啦A夢");
        map.put("info","cute cute cute cute cute cute cute cute cute cute ");
        list.add(map);
        return list;

    }

  d. 利用 adapter 加載數據/資源進行顯示

//初始化列表數據
final List<HashMap<String,Object>> list = getData();
//為ListView 添加 Adapter
String[] mapKeyArray = {"image","title","info"};
 int[] layoutIdArray = {R.id.image,R.id.title,R.id.info};
SimpleAdapter adapter = new SimpleAdapter(MainActivity.this,list,R.layout.listview_item, mapKeyArray , layoutIdArray );

  e. 添加監聽

  如果沒有重寫 Adapter 的 getView() 方法,給按鈕單獨添加監聽是比較麻煩的,所以我們可以使用 BaseAdapter 再給按鈕添加監聽,這里給 Item 列表項添加監聽。添加的方法和使用 ArrayAdapter 一樣。所以這里就不展開了。

  但是需要注意的是,由於 列表項中添加的 Button 按鈕,按鈕組件會獲取焦點,所以需要設置 button 的 focusable 為 false。可以直接設置 button 的屬性,也可以用java 方法進行設置。例如上面共參考的 item 布局文件就已經設置了。

   

   3-3 基於 BaseAdapter 使用 ListView

  添加 ListView 組件,存放數據,設置列表項的布局文件都和 SimpleAdapter 中的操作相同

  d. 創建一個 Adapter 繼承 BaseAdapter,並實現抽象方法。

   BaseAdapter 有 4 個抽象方法

 int getCount();    //返回的是數據源對象的個數,即列表項數
 Object getItem(int var1);    //返回指定位置position上的列表項
 long getItemId(int var1);    //返回指定位置處的行ID
View getView(int var1, View var2, ViewGroup var3);    //返回列表項對應的視圖

 

  繼承 BaseAdapter 時需要去實現這 4 個抽象方法,這幾個抽象方法都是 Adapter 接口中定義的方法。

  以理解為adapter先由getCount確定數量,然后循環執行getView()方法將條目一個一個繪制出來。

  必須重寫的方法是getCount和getView方法。

  前三個方法基本不需要過多修改,

    public int getCount() {
        return mData.size();
    }
   public Object getItem(int position) {
        return mData.get(position);
    }
    public long getItemId(int position) {
        return position;
    }

 

   由於新建了一個 java 文件,所以建議把 上下文 context,和 存儲數據的列表 list 傳過來

public class BabyAdapter extends BaseAdapter {

    Context context;
    List<HashMap<String,Object>> list ;
    public BabyAdapter(Context context,List list){
        super();
        this.context = context;
        this.list = list;
    }

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

    @Override
    public Object getItem(int i) {
        return list.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }
    @Override
    public View getView(final int i, View convertView, ViewGroup viewGroup) {
   /** 在不重寫getView 方法的情況下,每次調用 getView 時都要重新實例化子項 item 的布局文件,然后通過 findViewById 重新尋找 View 組件並繪畫
       着會導致兩個問題,1. 重復加載布局文件;2. 重復多次尋找 View 組件
       解決:使用 convertView 作為 View 緩存,形成 adapter 的itemView 重用機制,減少重繪 view 的次數
       形參 view 中緩存了itemView 的布局文件
  */
View view; ViewHolder holder; if(convertView == null){
      // LayoutInflater 用於加載布局的系統服務,實例化與Layout XML文件對應的View對象
      // 不
能直接使用, 需要通過getLayoutInflater( )方法或getSystemService( )方法來獲得與當前Context綁定的 LayoutInflater實例。
          LayoutInflater factory = LayoutInflater.from(context);
          // reSource:View 的layout 的ID
          // root: 生成的view 對象的父控件。若提供了 root(!null),則返回 root 作為根結點,否則,返回 view 對象的根布局作為根布局,
view
= factory.inflate(R.layout.listview_item,null); // 2. 使用 ViewHolder 實現 View 組件的緩存 // 重用 View 時就不用通過 findViewById 重新尋找 view 組件,同時減少 view 組件重繪的次數 holder = new ViewHolder(); holder.image =(ImageView)view.findViewById(R.id.image); holder.title = (TextView)view.findViewById(R.id.title); holder.info = (TextView)view.findViewById(R.id.info); holder.button = (Button)view.findViewById(R.id.button); view.setTag(holder); }else{ view = convertView; holder = (ViewHolder)view.getTag(); } // 2. 使用 ViewHolder 實現 View 組件的緩存 // 重用 View 時就不用通過 findViewById 重新尋找 view 組件,同時減少 view 組件重繪的次數 HashMap<String,Object> map = list.get(i); holder.image.setImageResource((int)map.get("image")); holder.title.setText((String)map.get("title")); holder.info.setText((String)map.get("info")); // 給按鈕添加監聽 holder.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new AlertDialog.Builder(context) .setTitle(list.get(i).get("title").toString()) .setMessage(list.get(i).get("info").toString()) .setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }).show(); } }); return view; } class ViewHolder{ ImageView image; TextView title ; TextView info; Button button; } }

 

   其中,converView 指 列表項視圖,當該 item 從未再屏幕上出現過時,convertView 為空,一旦出現過,convertView 中就緩存了View 對象,下一次就可以直接取得 列表項中視圖對象使用而不用再加載。

 三、ListView 的子項 Item 緩存原理

  1. 假設:屏幕只能顯示5個Item,那么ListView只會創建(5+1)個Item的視圖;當第1個Item完全離開屏幕后才會回收至緩存從而復用(用於顯示第6個Item)

   

 

   2. 假設:屏幕只能顯示5個Item,那么ListView只會創建(5+1)個Item的視圖;當第1個Item完全離開屏幕后才會回收至緩存從而復用(用於顯示第7個Item)

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM