標籤:
Android學習筆記二十四之ListView列表視圖二
前面一篇我們介紹了常用的幾種適配器的簡單實現和ListView的簡單使用,這一篇中,我們介紹一下ListView的最佳化和一些其它的問題。
ListView最佳化方法一
在ListView中,我們最常用的就是自訂Adapter,在我們自訂Adapter中,需要實現兩個比較重要的方法getCount()和getView(),前者是負責計算ListView的總Item數,後者是產生Item,有多少個Item就會調用getView()方法多少次。getView()方法每次調用的時候都會重新inflate一個View出來返回去,但是對於ListView,只需要保留能夠顯示的最大的View的數目即可,而新的View可以複用消失的View。ListView給我們提供了可複用的View對象,在getView()方法裡面,有一個參數View,這個就是可以複用的View對象。當參數View為null的時候,我們需要inflate一個View,當它不為null的時候,我們可以直接將他返回。例如:
@Overridepublic View getView(int i, View view, ViewGroup viewGroup) { ViewHolder viewHolder; //view為空白的時候,inflate一個新的view if (view == null) { view = LayoutInflater.from(context).inflate(R.layout.item_base_adapter, null); viewHolder = new ViewHolder(); viewHolder.tv_base_adapter = (TextView) view.findViewById(R.id.tv_base_adapter); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); } viewHolder.tv_base_adapter.setText(datas[i]); //不為空白,複用view return view;}
ListView最佳化方法二
上面介紹的是對View複用的最佳化,這樣我們可以不必每一item都inflate一個新的view,可以通過複用,減小記憶體開銷。下面我們介紹一個每一個AndroidAPP中都必不可少的操作,擷取控制項控制代碼,簡單的說就是拿到id。在ListView中,我們inflate一個View,裡面也有需要擷取到組件id的,我們可以用ViewHolder來實現最佳化:
具體的思路就是,我們在ViewHolder中存放我們需要的控制項,在View為null的時候,需要inflate一個新的view,同時我們還new一個ViewHolder類的對象,並將findviewById的結果賦值給ViewHolder中對應的成員變數,我們可以調用View中setTag()方法,將ViewHolder和View綁定起來。當view不為null的時候,通過getTag()方法取出ViewHolder對象,這樣就可以獲得ViewHolder中的成員變數,也不再需要調用findViewById方法了。例如:
@Overridepublic View getView(int i, View view, ViewGroup viewGroup) { ViewHolder viewHolder; if (view == null) { view = LayoutInflater.from(context).inflate(R.layout.item_base_adapter, null); //view為空白,new一個ViewHolder對象 viewHolder = new ViewHolder(); //擷取到ViewHolder對象中成員變數的id viewHolder.tv_base_adapter = (TextView) view.findViewById(R.id.tv_base_adapter); //調用setTag方法,將ViewHolder綁定到中 view.setTag(viewHolder); } else { //view不為空白,調用getTag方法,取出儲存的ViewHolder對象 viewHolder = (ViewHolder) view.getTag(); } viewHolder.tv_base_adapter.setText(datas[i]); return view;}static class ViewHolder { TextView tv_base_adapter;}
ListView最佳化方法三
上面介紹了兩種ListView的最佳化方法,第二種最佳化效率根據google官方文檔的解析,可以最佳化5%左右的效率。下面介紹一下第三種最佳化方法。
在我們實際開發中,ListView顯示的資料都是在網路中載入,假如網路比較好,能一次將所有的資料載入出來,這樣使用者體驗還好,如果網路不好,那麼載入資料需要時間比較久,使用者體驗就不好。另外,我們知道,虛擬機器為每一個進程分配的記憶體是有限的,如果一下載入太多的資料就會出現記憶體溢出的情況。為瞭解決這兩個問題,我們可以用分批載入的方法,但是分批載入還是不能完全解決問題。假如我有10萬條資料需要載入,分批載入任然可能會出現OOM問題,這時,我們需要將資料分頁載入,先分頁載入,然後在分批載入,這樣使用者體驗會好一些。
ListView出現的一些問題和解決ListView焦點問題
在一些情況中,我們需要在ListView的Item中添加Button、EditText、CheckBox等控制項,這就涉及到了焦點擷取的問題。我們在ListView的Item中添加了Button按鈕,點擊發現,我們不能觸發onItemClick和onItemLongClick方法,這就是ListView的焦點被攔截了。解決辦法也很簡單:
給攔截ListView焦點的控制項設定android:focusable=”false”屬性或者代碼調用setFocusable(false) 防範即可。還有一種方法就是在Item布局的根節點設定android:descendantFocusability=”blocksDescendants” 屬性,這個屬性有三個可選值
- beforeDescendants:viewgroup會優先其子類控制項而擷取到焦點
- afterDescendants:viewgroup只有當其子類控制項不需要擷取焦點時才擷取焦點
- blocksDescendants:viewgroup會覆蓋子類控制項而直接獲得焦點
這是ListView焦點問題的解決方案。
ListView資料更新問題
首先實現一個簡單的ListView
布局代碼:
<?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="match_parent"android:orientation="vertical"><LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" android:orientation="horizontal"> <Button android:id="@+id/btn_add_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="插入一條資料" /> <Button android:id="@+id/btn_add_on_position" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="在特定位置插入一條資料" /></LinearLayout><LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginLeft="10dp" android:orientation="horizontal"> <Button android:id="@+id/btn_delete_one" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="根據對象刪除" /> <Button android:id="@+id/btn_delete_on_position" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="根據位置刪除" /> <Button android:id="@+id/btn_delete_all" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="刪除所有資料" /></LinearLayout><ListView android:id="@+id/lv_data" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>
Item布局代碼:
<?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="wrap_content"android:layout_margin="10dp"android:gravity="center_vertical"android:orientation="horizontal"><ImageView android:id="@+id/iv_icon" android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/icon" /><TextView android:id="@+id/tv_data_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="5dp" android:layout_toRightOf="@id/iv_icon" android:text="載入出來的資料" android:textSize="18sp" /></RelativeLayout>
這裡的Item比較簡單,就直接是一張圖片和一個文字
Activity代碼:
package com.example.listviewdemo.activity;import android.os.Bundle;import android.support.annotation.Nullable;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.ListView;import com.example.listviewdemo.MYData;import com.example.listviewdemo.R;import com.example.listviewdemo.adapter.DataAdapter;import java.util.ArrayList;import java.util.List;/** * Created by Devin on 2016/7/11. */public class DataUpActivity extends AppCompatActivity {private Button btn_add_one;private Button btn_add_on_position;private Button btn_delete_one;private Button btn_delete_on_position;private Button btn_delete_all;private ListView lv_data;private DataAdapter adapter;private List<MYData> datas;private int flag = 1;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_data); btn_add_one = (Button) findViewById(R.id.btn_add_one); btn_add_on_position = (Button) findViewById(R.id.btn_add_on_position); btn_delete_one = (Button) findViewById(R.id.btn_delete_one); btn_delete_on_position = (Button) findViewById(R.id.btn_delete_on_position); btn_delete_all = (Button) findViewById(R.id.btn_delete_all); lv_data = (ListView) findViewById(R.id.lv_data); datas = new ArrayList<>(); adapter = new DataAdapter(this, datas); lv_data.setAdapter(adapter); }}
自訂配接器代碼:
package com.example.listviewdemo.adapter;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 com.example.listviewdemo.MYData;import com.example.listviewdemo.R;import java.util.ArrayList;import java.util.List;/** * Created by Devin on 2016/7/11. */public class DataAdapter extends BaseAdapter {private List<MYData> datas;private Context mContext;public DataAdapter(Context mContext, List<MYData> datas) { this.mContext = mContext; this.datas = datas;}@Overridepublic int getCount() { return datas.size();}@Overridepublic Object getItem(int i) { return i;}@Overridepublic long getItemId(int i) { return i;}@Overridepublic View getView(int i, View view, ViewGroup viewGroup) { ViewHolder viewHolder; if (view == null) { view = LayoutInflater.from(mContext).inflate(R.layout.item_data, null); viewHolder = new ViewHolder(); viewHolder.tv_data_text = (TextView) view.findViewById(R.id.tv_data_text); viewHolder.iv_icon = (ImageView) view.findViewById(R.id.iv_icon); view.setTag(viewHolder); } else { viewHolder = (ViewHolder) view.getTag(); } viewHolder.iv_icon.setImageResource(datas.get(i).getIconId()); viewHolder.tv_data_text.setText(datas.get(i).getContent()); return view;}private static class ViewHolder { TextView tv_data_text; ImageView iv_icon;}}
還有就是一個普通的bean
package com.example.listviewdemo;/** * Created by Devin on 2016/7/11. */public class MYData {private int iconId;private String content;public MYData() {}public MYData(int iconId, String content) { this.iconId = iconId; this.content = content;}public int getIconId() { return iconId;}public void setIconId(int iconId) { this.iconId = iconId;}public String getContent() { return content;}public void setContent(String content) { this.content = content;}@Overridepublic String toString() { return "Data{" + "iconId=" + iconId + ", content=‘" + content + ‘\‘‘ + ‘}‘;}}
這樣,我們啟動並執行效果是:
沒有顯示有Item,因為我們在適配器中添加的是一個空的list,接下來我們實現按鈕的事件和具體更新ListView
插入一條資料
在適配器中添加這個方法:
/** * 添加資料 * * @param myData */public void addData(MYData myData) { if (datas == null) { datas = new ArrayList<>(); } datas.add(myData); notifyDataSetChanged();}
在activity中實現點擊事件:
btn_add_one.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { MYData data = new MYData(R.drawable.icon, "這是添加的資料~~~~~~X" + flag); adapter.addData(data); flag++; } });
就可以實現在ListView中添加一條資料,具體的會在後面統一附上
在指定位置插入一條資料
在適配器中添加如下方法:
/** * 在指定位置添加資料 * * @param position * @param myData */public void addData(int position, MYData myData) { if (datas == null) { datas = new ArrayList<>(); } datas.add(position, myData); notifyDataSetChanged();}
實現按鈕的點擊事件:
btn_add_on_position.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { MYData data = new MYData(R.drawable.icon, "這是添加的資料~~~~~~X" + flag); adapter.addData(3, data); } });
就可以完成在指定位置添加一條資料,這裡只是簡單的實現,如果list中沒有那麼多資料,會出現數組下標越界的錯誤
根據對象刪除資料
在適配器中添加如下方法:
/** * 根據對象刪除資料 * * @param myData */public void removeData(MYData myData) { if (datas != null) { datas.remove(myData); } notifyDataSetChanged();}
實現按鈕的點擊事件:
btn_delete_one.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { MYData data = datas.get(2); adapter.removeData(data); } });
這裡只是簡單的刪除下標為3的資料,如果listview的長度小於3,會出現數組下標越界的錯誤
根據位置刪除資料
在適配器中添加如下方法:
/** * 根據位置刪除資料 * * @param position */public void removeData(int position) { if (datas != null && position <= datas.size()) { datas.remove(position); } notifyDataSetChanged();}
實現按鈕的點擊事件:
btn_delete_on_position.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { adapter.removeData(3); } });
刪除指定位置的資料,需要判定是否存在不然會出現錯誤
刪除所有的資料
在適配器中添加如下方法:
/** * 清除所有的資料 */public void removeAll() { if (datas != null) { datas.clear(); } notifyDataSetChanged();}
實現按鈕的點擊事件:
btn_delete_all.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { adapter.removeAll(); } });
最後附上:
這裡實現ListView的資料更新問題,但是任然存在一些小問題,比如,沒有資料的時候顯示一片空白,使用者體驗不是很好。可以重寫,根據伺服器返回的資料更新介面。這裡就不做實現了。下一節我們介紹一下ListView多布局的實現,類似QQ等的聊天介面。
Android學習筆記二十四之ListView列表視圖二