1.自定義BaseAdapter,然後綁定ListView的最簡單例子
先看看我們要實現的效果圖:
一個很簡單的ListView,自己寫下Item,然後加載點數據這樣~ 下面貼下關鍵代碼:
Animal.java:
/**
* Created by Jay on 2015/9/18 0018.
*/
public class Animal {
private String aName;
private String aSpeak;
private int aIcon;
public Animal() {
}
public Animal(String aName, String aSpeak, int aIcon) {
this.aName = aName;
this.aSpeak = aSpeak;
this.aIcon = aIcon;
}
public String getaName() {
return aName;
}
public String getaSpeak() {
return aSpeak;
}
public int getaIcon() {
return aIcon;
}
public void setaName(String aName) {
this.aName = aName;
}
public void setaSpeak(String aSpeak) {
this.aSpeak = aSpeak;
}
public void setaIcon(int aIcon) {
this.aIcon = aIcon;
}
}
AnimalAdapter.java:自定義的BaseAdapter:
/**
* Created by Jay on 2015/9/18 0018.
*/
public class AnimalAdapter extends BaseAdapter {
private LinkedList<Animal> mData;
private Context mContext;
public AnimalAdapter(LinkedList<Animal> mData, Context mContext) {
this.mData = mData;
this.mContext = mContext;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal,parent,false);
ImageView img_icon = (ImageView) convertView.findViewById(R.id.img_icon);
TextView txt_aName = (TextView) convertView.findViewById(R.id.txt_aName);
TextView txt_aSpeak = (TextView) convertView.findViewById(R.id.txt_aSpeak);
img_icon.setBackgroundResource(mmData.get(position).getaIcon());
txt_aName.setText(mData.get(position).getaName());
txt_aSpeak.setText(mData.get(position).getaSpeak());
return convertView;
}
}
最後是MainActivity.java:
public class MainActivity extends AppCompatActivity {
private List<Animal> mData = null;
private Context mContext;
private AnimalAdapter mAdapter = null;
private ListView list_animal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = MainActivity.this;
list_animal = (ListView) findViewById(R.id.list_animal);
mData = new LinkedList<Animal>();
mData.add(new Animal("狗說", "你是狗麼?", R.mipmap.ic_icon_dog));
mData.add(new Animal("牛說", "你是牛麼?", R.mipmap.ic_icon_cow));
mData.add(new Animal("鴨說", "你是鴨麼?", R.mipmap.ic_icon_duck));
mData.add(new Animal("魚說", "你是魚麼?", R.mipmap.ic_icon_fish));
mData.add(new Animal("馬說", "你是馬麼?", R.mipmap.ic_icon_horse));
mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
list_animal.setAdapter(mAdapter);
}
}
好的,自定義BaseAdapter以及完成數據綁定就是這麼簡單~
別問我拿示例的代碼,剛開始學就會寫出這些代碼,我只是演示下流程,讓大家熟悉 熟悉而已。另外,也是爲下面的屬性驗證做準備~
2.表頭表尾分割線的設置:
listview作爲一個列表控件,他和普通的列表一樣,可以自己設置表頭與表尾: 以及分割線,可供我們設置的屬性如下:
- footerDividersEnabled:是否在footerView(表尾)前繪製一個分隔條,默認爲true
- headerDividersEnabled:是否在headerView(表頭)前繪製一個分隔條,默認爲true
- divider:設置分隔條,可以用顏色分割,也可以用drawable資源分割
- dividerHeight:設置分隔條的高度
翻遍了了API發現並沒有可以直接設置ListView表頭或者表尾的屬性,只能在Java中寫代碼 進行設置了,可供我們調用的方法如下:
- addHeaderView(View v):添加headView(表頭),括號中的參數是一個View對象
- addFooterView(View v):添加footerView(表尾),括號中的參數是一個View對象
- addHeaderView(headView, null, false):和前面的區別:設置Header是否可以被選中
- addFooterView(View,view,false):同上
對了,使用這個addHeaderView方法必須放在listview.setAdapter前面,否則會報錯。
使用示例:
運行效果圖:
代碼實現:
先編寫下表頭與表尾的佈局:
view_header.xml(表頭),表尾一樣,就不貼了:
<?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"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:textSize="18sp"
android:text="表頭"
android:gravity="center"
android:background="#43BBEB"
android:textColor="#FFFFFF"/>
</LinearLayout>
MainActivty.java:
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
private List<Animal> mData = null;
private Context mContext;
private AnimalAdapter mAdapter = null;
private ListView list_animal;
private LinearLayout ly_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = MainActivity.this;
list_animal = (ListView) findViewById(R.id.list_animal);
//動態加載頂部View和底部View
final LayoutInflater inflater = LayoutInflater.from(this);
View headView = inflater.inflate(R.layout.view_header, null, false);
View footView = inflater.inflate(R.layout.view_footer, null, false);
mData = new LinkedList<Animal>();
mData.add(new Animal("狗說", "你是狗麼?", R.mipmap.ic_icon_dog));
mData.add(new Animal("牛說", "你是牛麼?", R.mipmap.ic_icon_cow));
mData.add(new Animal("鴨說", "你是鴨麼?", R.mipmap.ic_icon_duck));
mData.add(new Animal("魚說", "你是魚麼?", R.mipmap.ic_icon_fish));
mData.add(new Animal("馬說", "你是馬麼?", R.mipmap.ic_icon_horse));
mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
//添加表頭和表尾需要寫在setAdapter方法調用之前!!!
list_animal.addHeaderView(headView);
list_animal.addFooterView(footView);
list_animal.setAdapter(mAdapter);
list_animal.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(mContext,"你點擊了第" + position + "項",Toast.LENGTH_SHORT).show();
}
}
好的,代碼還是比較簡單的,從上面我們看出來一個要注意的問題,就是:
添加表頭表尾後,我們發現positon是從表頭開始算的,就是你添加的第一個數據本來的 postion 是 0,但是此時卻變成了 1,因爲表頭也算!!
3.列表從底部開始顯示:stackFromBottom
如果你想讓列表顯示你列表的最下面的話,那麼你可以使用這個屬性,將stackFromBottom 屬性設置爲true即可,設置後的效果圖如下:
4.設置點擊顏色cacheColorHint
如果你爲ListView設置了一個圖片作爲Background的話,當你拖動或者點擊listView空白位置會發現 item都變成黑色了,這是時候我們可以通過這個cacheColorHint將顏色設置爲透明:#00000000
5.隱藏滑動條
我們可以通過設置:android:scrollbars=“none” 或者 setVerticalScrollBarEnabled(true); 解決這個問題!
本節小結:
好的,關於ListView的基本用法大概就這些,當然除了上述的這些屬性外還有其他的, 實際遇到再查查吧~這裏知道如何去重寫BaseAdapter和完成數據綁定就好,下節我們來 教大家如何來優化這個BaseAdapter的編寫~
補充:如何重寫BaseAdapter
首先學習Android還是秉持從先會使用SDK提供的代碼框架開始,掌握了方法的使用,如果有需要再去針對於某一個框架實現要點學習源碼方面的知識,對於一個系統級別的框架來說,廣撒網全都學可能精力的確不夠。
對於一個BaseAdapter的子類來說,我們需要重寫BaseAdapter中的幾個抽象方法,但是抽象方法是被如何調用的,我們在此不妨先不去深究,只需知道被重寫的方法是用於何種目的,我們只需秉持:“調用邏輯是由組件負責,你只要按照規定實現方法即可”的觀念即可。
引言:
Adapter用來把數據綁定到擴展了AdapterView類的視圖組。系統自帶了幾個原生的Adapter。
由於原生的Adapter視圖功能太少,有時需要有自己的視圖格式。並且在開發中經常用到。
重寫的四種方法:
方法名 | 方法用途 |
---|---|
public int getCount() | 適配器中數據集中的數據個數 |
public Object getItem(int arg0) | 獲取數據集中與索引對應的數據項 |
public long getItemId(int arg0) | 獲取指定行對應的ID |
View getView(int arg0, View arg1, ViewGroup arg2) | 獲取每一個Item的顯示內容 |
ListView繪製的過程如下:
- 首先,系統在繪製ListView之前,將會先調用getCount方法來獲取Item的個數。
- 之後每繪製一個Item就會調用一次getView方法,在此方法內就可以引用事先定義好的xml來確定顯示的效果並返回一個View對象作爲一個Item顯示出來。也正是在這個過程中完成了適配器的主要轉換功能,把數據和資源以開發者想要的效果顯示出來。也正是getView的重複調用,使得ListView的使用更爲簡單和靈活。
這兩個方法是自定ListView顯示效果中最爲重要的,同時只要重寫好了就兩個方法,ListView就能完全按開發者的要求顯示。
- 而getItem和getItemId方法將會在調用ListView的響應方法的時候被調用到。所以要保證ListView的各個方法有效的話,這兩個方法也得重寫。比如:沒有完成getItemId方法的功能實現的話,當調用ListView的getItemIdAtPosition方法時將會得不到想要的結果,因爲該方法就是調用了對應的適配器的getItemId方法。
以下給出此類四個方法重寫的相關一個常見的例子,再做解釋:
/**
* @return 適配器中數據集中的數據個數
*/
@Override
public int getCount() {
return mData.size();
}
/**
* @param position
* @return 獲取數據集中與索引對應的數據項
*/
@Override
public Object getItem(int position) {
return null;
}
/**
* @param position
* @return 獲取指定行對應的ID
* 這裏需要注意的一點是對於一個元素的ID實際上和position不能認爲是一個東西,實際上可以是完全不同的值
*/
@Override
public long getItemId(int position) {
return position;
}
/**
* @param position
* @param convertView
* @param parent
* @return 獲取每一個Item的顯示內容
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal, parent, false);
ImageView img_icon = convertView.findViewById(R.id.img_icon);
TextView text_aName = convertView.findViewById(R.id.txt_aName);
TextView text_aSpeak = convertView.findViewById(R.id.txt_aSpeak);
img_icon.setBackgroundResource(mData.get(position).getaIcon());
text_aName.setText(mData.get(position).getaName());
text_aSpeak.setText(mData.get(position).getaSpeak());
return convertView;
}
注意事項:
- 可能你會對getItemId()方法有所疑問:爲什麼可以直接返回position,那麼這個方法的意義是什麼?實際上這裏只是一個特殊的例子,一個元素的ID號和position其不一定相同!此處特殊就特殊在將iD號和positon設爲相同了,所以我們在2.中的MainActivity.java文件中完全可以將語句:
Toast.makeText(mContext, "你點擊了第"+position+"項", Toast.LENGTH_SHORT).show();
修該爲:
Toast.makeText(mContext, "你點擊了第"+mAdapter.getItemId(position)+"項", Toast.LENGTH_SHORT).show();
-
爲什麼方法
getItem()
可以返回null,難道BaseAdapter內部通過此方法去找對應的元素不會報出空指針異常嗎?事實上沒有報,這是因爲BaseAdapter方法並沒有內部調用此方法,此方法修飾爲public就是爲了方便程序員進行調用,而BaseAdapter內部早就集成了訪問列表元素的方法。證明方法:將上述Toast的方法改寫:
Toast.makeText(mContext, "你點擊了第"+mAdapter.getItem(position).getClass(), Toast.LENGTH_SHORT).show();
如果你在app中點擊某一行的item直接會造成程序閃退,並在Android Studio上拋出空指針異常:
如果將getItem
方法重寫爲以下版本:@Override public Object getItem(int position) { return mData.get(position); }
就不會再報出空指針異常,而是正常打出類信息的相關提示,如下圖所示:
我們從主動調用不同定義的
getItem()
方法來說明此例,BaseAdapter真的沒有調用此方法,否則一定會爆出空指針異常。 -
BaseAdapter內部爲何使用LinkedList而不是其他數據結構?實際上說明類型的數據結構並不重要,BaseAdapter對此並沒有規定,你可以使用ArrayList代替,甚至可以選擇使用數組代替。但是爲何不選擇數組呢,主要原因還是我們再MainActivity.java中加入每行的數據只需要調用
add
方法即可,而數組就麻煩的很多,後者需要確定個數,尾指針等等額外的理解開銷;
補充2: 方法 getItem 以及 getItemId 的用途
對於這兩個方法的理解對於Andoird中關於Adapter設計模式會有很大的幫助:
可以參考的網址:
What is the intent of the methods getItem and getItemId in the Android class BaseAdapter?
如果你嫌英語太麻煩,那麼可以直接看我對此的理解:
首先我們要理解一點,爲何使用Adapter適配器,Adapter就是給我們視圖資源在調用數據的時候能夠相當程度上方便,視圖只需要直接和Adapter交互,而不需要和數據本身交互,總結一句話:Adapter對象將資源訪問進行了封裝。首先getItem()
方法就是出於這個目的。假設我們沒有Adapter,那麼我們讀取數據就需要使用例如:myListData.get(position)
的方法,這裏我們就直接調用了數據資源myListData
對象,封裝程度不高。但是如果利用Adapter對象,那麼我們將資源訪問就簡化爲:adapter.get(position)
。
簡單地說,Android允許將一個long類型的數據附加到任何ListView對象的元素上,對,這是附加的,實際上你可以選擇忽略此值。當你選擇一個ListView所存的元素時,適配器可以提供給我三個有關的特性值:
- 一個元素對象自身的引用
- 此元素在ArrayList所存的下標索引position
- 返回此元素上所附着的long類型值
實際上這三個特性值分別對應我們需要重寫BaseAdapter的2個抽象get方法:
- getItem
- 第二個position本身就是自帶的
- getItemId
而這些值的計算以及設定完全區別於我們打算對讀取這些值後做什麼操作,自然我們可以選擇不做任何操作。比方說每個Adapter對象都提供了以getItemId()
方法,我們可以選擇用或者不用,但是我們不用也無法避免重寫此方法,因爲BaseAdapter被設置爲抽象類,而這些方法是抽象方法,所以我們直接就簡單地寫爲:
@Override
public long getItemId(int position) {
return position;
}
我們本來就能通過position直接讀出下標索引,所以用getItemId()
這個方法單純來獲取position值反而更加麻煩了,但是我們本身如此重寫定義目的單純只是爲了重寫規則而重寫。這樣的寫法已經成爲了Android世界中的一個通常的做法(慣例)。
補充3 :方法getView()的代碼解釋
/**
* @param position
* @param convertView
* @param parent
* @return 獲取每一行Item的顯示內容,每有一行Adapter對象都需要通過此方法向ListView傳遞控件的屬性以及資源的取值
*
*
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//通過infalte方法返回ListView對象每一行的佈局item對象
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal, parent, false);
/**
* 以下三個方法是通過方法convertView.findViewById()方法返回R文件中所導入的,構成每一行佈局文件的控件
*
* 注意這裏和Activity中所用的findViewById是有所區別的,這裏需要主動地寫出調用此方法的對象:convertView
*/
ImageView img_icon = convertView.findViewById(R.id.img_icon);
TextView text_aName = convertView.findViewById(R.id.txt_aName);
TextView text_aSpeak = convertView.findViewById(R.id.txt_aSpeak);
/***
* 通過不同控件的不同set方法,將從數據源中所存的資源寫入到不同控件中去
*/
img_icon.setBackgroundResource(mData.get(position).getaIcon());
text_aName.setText(mData.get(position).getaName());
text_aSpeak.setText(mData.get(position).getaSpeak());
return convertView;//返回對應於ListView一行對應的佈局View對象
}
看到這個方法,我們就要想我們在MainActivity.java中應當以何種方式來方便的處理Adapter對象呢?我們需要做以下3件事:
- 確定傳遞給Adapter對象數據源,數據源可以是在MainActivity中創建的,也可以是創建在values文件夾中的;
- 調用Adapter構造方法,需要輸入數據源以及上下文
- 通過findViewById找到listView對象
- 調用listView對象的setAdapter方法將適配器對象傳遞給ListView控件
一個最簡單的控件利用適配器來進行佈局資源的調配就是如此,對於控件而言是相當輕鬆的,和數據源相關的甚至直接就簡化爲setAdapter()
方法,而且不是直接相關,只是有間接的關係。