在Android开发过程中,有个基本的技能就是自定义adapter,给列表填充数据。如果列表里有两种或者两种以上的不同的item,那么我们可以用adapter.getItemViewType(position)拿到该item的视图类型,再根据这个类型在adapter.getView(position, convertView, parent)方法里创建不同的view添加进去。比如简单的聊天记录,大概就是这个样子:
我是这么做的。
定义一个Model类ChatRecord.java
用来存储每条消息记录
public class ChatRecord {
private String content;//消息内容
private int type; //类型 0代表你的发言,1代表别人发言
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;
}
}
自定义一个Adapter,给listView用来添加数据
public class ChatRecordAdapter extends BaseAdapter {
public static final int TYPE_YOUR = 0; //你的发言
public static final int TYPE_OTHERS = 1; //其他人的发言
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>
编写主界面代码
并且在聊天记录底部加一个View作为广告展示(没有点击跳转,只作展示),每条聊天记录可以点击,点击时Toast提示是谁的发言。代码大概是这样子:
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());//这句代码应该在setAdapter()之前调用
}
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();
}
});
}
/**
* 创建列表底部View
* @return
*/
private View getFooterView() {
LayoutInflater inflater = LayoutInflater.from(this);
return inflater.inflate(R.layout.bottom_advertisement, null);
}
/**
* 用来生成简单的聊天记录
* @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>
运行代码,查看结果
运行结果如文章开始那张图,但是有个问题,也是我写这个的目的,那就是:
当点击到footerView的时候,程序崩溃了!!!
可以看到,报错的原因是IndexOutOfBoundsException
~
断点调试可以看到,是进入到了ListView的onItemClick事件当中~
我们根本没有设置footerView的点击事件啊,为什么会有呢?我们看一下跟footerView相关的代码
lvBank.addFooterView(getFooterView());
点进入看一下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);
}
这里看到,是调用了其三个参数的重载方法,再进入看看:
/**
* 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();
}
}
}
仔细看一下这里的注释@param isSelectable true if the footer view can be selected
原来问题出在这里~~~
所以,ListView添加列表头或者列表尾时,一定注意调用哪个方法,如果不期望其可点击,则需要调用三参数重载方法public void addFooterView(View v, Object data, boolean isSelectable)
,并且isSelectable
传false
,不然会出现IndexOutOfBoundsException
写在最后
其实写这篇文章就只是为了说明在添加列表头或列表尾时要注意调用的方法,跟自定义adapter没什么太大关系,花那么多时间写自定义adapter只得算是练练手。
以上全部基于自己的理解 ,如有问题,欢迎批评指正~