在Android開(kāi)發(fā)過(guò)程中朦促,有個(gè)基本的技能就是自定義adapter,給列表填充數(shù)據(jù)浪汪。如果列表里有兩種或者兩種以上的不同的item,那么我們可以用adapter.getItemViewType(position)拿到該item的視圖類型判没,再根據(jù)這個(gè)類型在adapter.getView(position, convertView, parent)方法里創(chuàng)建不同的view添加進(jìn)去。比如簡(jiǎn)單的聊天記錄隅茎,大概就是這個(gè)樣子:
我是這么做的澄峰。
定義一個(gè)Model類ChatRecord.java
用來(lái)存儲(chǔ)每條消息記錄
public class ChatRecord {
private String content;//消息內(nèi)容
private int type; //類型 0代表你的發(fā)言,1代表別人發(fā)言
public String getContent() {
return content;
}
public ChatRecord setContent(String content) {
this.content = content;
return this;
}
public int getType() {
return type;
}
public ChatRecord setType(int type) {
this.type = type;
return this;
}
}
自定義一個(gè)Adapter辟犀,給listView用來(lái)添加數(shù)據(jù)
public class ChatRecordAdapter extends BaseAdapter {
public static final int TYPE_YOUR = 0; //你的發(fā)言
public static final int TYPE_OTHERS = 1; //其他人的發(fā)言
private Context mContext;
private ArrayList<ChatRecord> mData;
public ChatRecordAdapter(Context mContext, ArrayList<ChatRecord> mData) {
this.mContext = mContext;
this.mData = mData;
}
@Override
public int getViewTypeCount() {
return mData.size();
}
@Override
public int getItemViewType(int position) {
return mData.get(position).getType();
}
@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) {
int viewType = getItemViewType(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = createView(viewType);
viewHolder = new ViewHolder();
viewHolder.ivHeadPortrait = (ImageView) convertView.findViewById(R.id.ivHeadPortrait);
viewHolder.tvChatContent = (TextView) convertView.findViewById(R.id.tvChatContent);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.ivHeadPortrait.setImageResource(getImageResource(viewType));
viewHolder.tvChatContent.setText(mData.get(position).getContent());
return convertView;
}
private View createView(int type) {
LayoutInflater inflater = LayoutInflater.from(mContext);
switch(type) {
case TYPE_YOUR:
return inflater.inflate(R.layout.chat_record_item_yours, null);
case TYPE_OTHERS:
return inflater.inflate(R.layout.chat_record_item_others, null);
}
return null;
}
private int getImageResource(int type) {
switch(type) {
case TYPE_YOUR:
return R.mipmap.ic_launcher;
case TYPE_OTHERS:
return R.color.colorPrimaryDark;
}
return R.color.colorAccent;
}
class ViewHolder {
public ImageView ivHeadPortrait;
public TextView tvChatContent;
}
}
自定義adapter涉及到的資源文件
chat_record_item_yours.xml
<?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">
<ImageView
android:id="@+id/ivHeadPortrait"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@mipmap/ic_launcher" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/ivHeadPortrait">
<TextView
android:id="@+id/tvChatContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
</RelativeLayout>
</RelativeLayout>
chat_record_item_others.xml
<?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">
<ImageView
android:id="@+id/ivHeadPortrait"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@mipmap/ic_launcher"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"/>
<TextView
android:id="@+id/tvChatContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/ivHeadPortrait"/>
</RelativeLayout>
編寫(xiě)主界面代碼
并且在聊天記錄底部加一個(gè)View作為廣告展示(沒(méi)有點(diǎn)擊跳轉(zhuǎn)俏竞,只作展示),每條聊天記錄可以點(diǎn)擊堂竟,點(diǎn)擊時(shí)Toast提示是誰(shuí)的發(fā)言魂毁。代碼大概是這樣子:
public class MainActivity extends Activity {
private ListView lvBank;
private ChatRecordAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView() {
lvBank = (ListView) findViewById(R.id.lvBank);
lvBank.addFooterView(getFooterView());//這句代碼應(yīng)該在setAdapter()之前調(diào)用
}
private void initData() {
mAdapter = new ChatRecordAdapter(this, getData());
lvBank.setAdapter(mAdapter);
lvBank.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int viewType = mAdapter.getItemViewType(position);
String toastMsg = viewType == ChatRecordAdapter.TYPE_YOUR ? "your speck" : "others' speck";
Toast.makeText(MainActivity.this, toastMsg, Toast.LENGTH_SHORT).show();
}
});
}
/**
* 創(chuàng)建列表底部View
* @return
*/
private View getFooterView() {
LayoutInflater inflater = LayoutInflater.from(this);
return inflater.inflate(R.layout.bottom_advertisement, null);
}
/**
* 用來(lái)生成簡(jiǎn)單的聊天記錄
* @return
*/
private ArrayList<ChatRecord> getData() {
ArrayList<ChatRecord> data = new ArrayList<>();
ChatRecord record;
for (int i = 0; i < 20; i++) {
record = new ChatRecord();
if (i % 2 == 0) {
record.setType(ChatRecordAdapter.TYPE_YOUR).setContent("honey, what r u doing? " + i);
} else {
record.setType(ChatRecordAdapter.TYPE_OTHERS).setContent("- - - - - " + i);
}
data.add(record);
}
return data;
}
}
涉及到的資源文件
bottom_advertisement.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">
<TextView
android:layout_width="match_parent"
android:layout_height="45dp"
android:text="this is bottom advertisement"
android:gravity="center"/>
</LinearLayout>
運(yùn)行代碼,查看結(jié)果
運(yùn)行結(jié)果如文章開(kāi)始那張圖出嘹,但是有個(gè)問(wèn)題席楚,也是我寫(xiě)這個(gè)的目的,那就是:
當(dāng)點(diǎn)擊到footerView的時(shí)候疚漆,程序崩潰了:ㄕ汀!娶聘!
可以看到闻镶,報(bào)錯(cuò)的原因是IndexOutOfBoundsException
~
斷點(diǎn)調(diào)試可以看到,是進(jìn)入到了ListView的onItemClick事件當(dāng)中~
我們根本沒(méi)有設(shè)置footerView的點(diǎn)擊事件啊丸升,為什么會(huì)有呢铆农?我們看一下跟footerView相關(guān)的代碼
lvBank.addFooterView(getFooterView());
點(diǎn)進(jìn)入看一下addFooterView到底做了什么:
/**
* Add a fixed view to appear at the bottom of the list. If addFooterView is
* called more than once, the views will appear in the order they were
* added. Views added using this call can take focus if they want.
* <p>
* Note: When first introduced, this method could only be called before
* setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
* {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
* called at any time. If the ListView's adapter does not extend
* {@link HeaderViewListAdapter}, it will be wrapped with a supporting
* instance of {@link WrapperListAdapter}.
*
* @param v The view to add.
*/
public void addFooterView(View v) {
addFooterView(v, null, true);
}
這里看到,是調(diào)用了其三個(gè)參數(shù)的重載方法狡耻,再進(jìn)入看看:
/**
* Add a fixed view to appear at the bottom of the list. If addFooterView is
* called more than once, the views will appear in the order they were
* added. Views added using this call can take focus if they want.
* <p>
* Note: When first introduced, this method could only be called before
* setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
* {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
* called at any time. If the ListView's adapter does not extend
* {@link HeaderViewListAdapter}, it will be wrapped with a supporting
* instance of {@link WrapperListAdapter}.
*
* @param v The view to add.
* @param data Data to associate with this view
* @param isSelectable true if the footer view can be selected
*/
public void addFooterView(View v, Object data, boolean isSelectable) {
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mFooterViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
// In the case of re-adding a footer view, or adding one later on,
// we need to notify the observer.
if (mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
}
仔細(xì)看一下這里的注釋@param isSelectable true if the footer view can be selected
原來(lái)問(wèn)題出在這里~~~
所以墩剖,ListView添加列表頭或者列表尾時(shí),一定注意調(diào)用哪個(gè)方法夷狰,如果不期望其可點(diǎn)擊岭皂,則需要調(diào)用三參數(shù)重載方法public void addFooterView(View v, Object data, boolean isSelectable)
,并且isSelectable
傳false
,不然會(huì)出現(xiàn)IndexOutOfBoundsException
寫(xiě)在最后
其實(shí)寫(xiě)這篇文章就只是為了說(shuō)明在添加列表頭或列表尾時(shí)要注意調(diào)用的方法沼头,跟自定義adapter沒(méi)什么太大關(guān)系爷绘,花那么多時(shí)間寫(xiě)自定義adapter只得算是練練手。
以上全部基于自己的理解 进倍,如有問(wèn)題土至,歡迎批評(píng)指正~