ListView是android最常用的控件之一,可以实现各种列表:例如通讯录,聊天列表,好友列表等等。这里学习一下如何使用android的ListView
1. 实现一个简单的列表
ListView使用需要与Adapter联合使用,Adapter负责生成View给ListView,最后调用ListView的SetAdapter方法把adapter实例设置进去就创建了一个列表。
下面看下adapter的用法,adapter都要继承自BaseAdapter,继承BaseAdapter要实现四个方法:getCount(返回数据的总条数),getItem(返回一个Item),getItemId(返回一个Item的Id),getView(返回List的Item的布局View,主要实现该方法),例如要实现下边这样的列表。
下边是实现简单列表的代码与注释:
public class MainActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.找到listView实例
listView = (ListView) findViewById(R.id.listView);
//2.生成listView的数据
List<String> list = new ArrayList<String>();
for (int i = 0; i < 100; i++) {
list.add(String.valueOf(new Random().nextDouble()));
}
//3.实例化adapter
TxtAdapter adapter = new TxtAdapter(this, list);
//4.把adapter设置给listView
listView.setAdapter(adapter);
}
class TxtAdapter extends BaseAdapter {
private Activity mActivity;
private List<String> mDataList;
public TxtAdapter(Activity activity, List<String> list) {
this.mActivity = activity;
this.mDataList = list;
}
@Override
public int getCount() {
return mDataList == null ? 0 : mDataList.size();
}
@Override
public Object getItem(int position) {
return mDataList == null ? null : mDataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//position参数是当前listView的position
//convertView是ListView缓存复用的view,每次getView都创建一个view,效率低而且会内存泄漏
//parent即ListView本身.
LayoutInflater inflater = LayoutInflater.from(mActivity);
if (convertView == null) {
//inflate一个item的布局
convertView = inflater.inflate(R.layout.layout_txt_item, parent, false);
}
TextView txt = (TextView) convertView.findViewById(R.id.txt);
//给布局TextView赋值
txt.setText(mDataList.get(position));
return convertView;
}
}
}
注意:
1.getView的时候一定要使用convertView,不要自己每次getView都创建新的view,这样listview滚动反复创建的view都被listView引用,造成内存泄漏。ListView的view复用,移除屏幕的view会缓存起来,然后赋值给convertView,为我们提升了效率。
2.LayoutInflater的inflate方法是获取一个布局,第一个参数是布局的resource id,第二个参数是指定该布局的父布局,第三个参数,如果为false,该布局会使用父布局的LayoutParams,为true,使用父布局的LayoutParams并attach到父布局。
2. 使用ViewHolder
为了进一步提升效率,adapter中可以定义一个ViewHolder来保存对Item布局内控件对引用,减少每次都inflate的消耗。所以上边的adapter可以写成下边的样子:
class TxtAdapter extends BaseAdapter {
private Activity mActivity;
private List<String> mDataList;
public TxtAdapter(Activity activity, List<String> list) {
this.mActivity = activity;
this.mDataList = list;
}
@Override
public int getCount() {
return mDataList == null ? 0 : mDataList.size();
}
@Override
public Object getItem(int position) {
return mDataList == null ? null : mDataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//position参数是当前listView的position
//convertView是ListView缓存复用的view,每次getView都创建一个view,效率低而且会内存泄漏
//parent即ListView本身.
ViewHolder viewHolder;
LayoutInflater inflater = LayoutInflater.from(mActivity);
if (convertView == null) {
//实例化ViewHolder
viewHolder=new ViewHolder();
//inflate一个item的布局
convertView = inflater.inflate(R.layout.layout_txt_item, parent, false);
//把布局内的控件赋值给ViewHolder
viewHolder.textView=(TextView) convertView.findViewById(R.id.txt);
//viewHolder存在convertView的tag中,可以在下边getTag中获取
convertView.setTag(viewHolder);
}else {
//convert不为空,直接get到viewHolder
viewHolder= (ViewHolder) convertView.getTag();
}
//给布局TextView赋值
viewHolder.textView.setText(mDataList.get(position));
return convertView;
}
class ViewHolder{
TextView textView;
}
}
3. 更新ListView上的item
更新ListView使用了MVC模式,adapter是controller,ListView是View,数据List就是Model。我们改变了List的数据之后,例如对传入的dataList增删改查,之后再调用adapter的notifyDataSetChange方法,ListView的item就会更新了。这里ListView使用了设计模式中的观察者模式。
4. ListView的Item多种布局
ListView中经常会用到多种布局,比如通讯录页面分组标题与具体姓名显示,微信的聊天页面左右两个气泡,等等。例如:
实现多类型布局,我们同样可以根据传入数据的类型来生成对应的view,但是我们不能一直自己创建view。为了view的重用,需要实现BaseAdapter两个方法,getViewTypeCount(顾名思义,返回有几种布局类型总数),getItemViewType(int position)该方法根据当前position返回使用哪种布局。有了这两个方法,listView就知道在getView方法中返回正确的convertView了。下边是实例代码:
public class ChatActivity extends AppCompatActivity {
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
//1.找到listView实例
listView = (ListView) findViewById(R.id.listView);
//2.生成listView的数据
List<ChatModel> list = new ArrayList<ChatModel>();
for (int i = 0; i < 100; i++) {
ChatModel model = new ChatModel();
model.setIsSelf(new Random().nextBoolean());
model.setMsg(String.valueOf(new Random().nextDouble()));
list.add(model);
}
//3.实例化adapter
ChatAdapter adapter = new ChatAdapter(this, list);
//4.把adapter设置给listView
listView.setAdapter(adapter);
}
class ChatModel {
private boolean isSelf;
private String msg;
public boolean getIsSelf() {
return isSelf;
}
public void setIsSelf(boolean isSelf) {
this.isSelf = isSelf;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
class ChatAdapter extends BaseAdapter {
private Activity mActivity;
private List<ChatModel> mDataList;
private static final int LEFT = 0;
private static final int RIGHT = 1;
public ChatAdapter(Activity activity, List<ChatModel> list) {
this.mActivity = activity;
this.mDataList = list;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
if (mDataList.get(position).getIsSelf()) {
return RIGHT;
} else {
return LEFT;
}
}
@Override
public int getCount() {
return mDataList == null ? 0 : mDataList.size();
}
@Override
public Object getItem(int position) {
return mDataList == null ? null : mDataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
LayoutInflater inflater = LayoutInflater.from(mActivity);
//获取布局类型
int type = getItemViewType(position);
if (convertView == null) {
//实例化ViewHolder
viewHolder = new ViewHolder();
//inflate一个item的布局 根据类型不同返回对应布局
if (type == LEFT) {
convertView = inflater.inflate(R.layout.chatting_item_msg_left, parent, false);
} else {
convertView = inflater.inflate(R.layout.chatting_item_msg_right, parent, false);
}
//把布局内的控件赋值给ViewHolder
viewHolder.textView = (TextView) convertView.findViewById(R.id.chatMsg);
viewHolder.headerImg = (ImageView) convertView.findViewById(R.id.headerImg);
//viewHolder存在convertView的tag中,可以在下边getTag中获取
convertView.setTag(viewHolder);
} else {
//convert不为空,直接get到viewHolder
viewHolder = (ViewHolder) convertView.getTag();
}
//根据type不同设置对应数据
if (type == LEFT) {
viewHolder.headerImg.setImageResource(R.drawable.left_header);
} else {
viewHolder.headerImg.setImageResource(R.drawable.right_header);
}
viewHolder.textView.setText(mDataList.get(position).getMsg());
return convertView;
}
class ViewHolder {
ImageView headerImg;
TextView textView;
}
}
}
5. 同时响应ListView的Item和上边按钮
当item的布局上有按钮的时候,按钮会首先获得焦点,item就不能响应点击了。但是有些场景需要同时都可以响应,比如微信表情的下载列表
点击下载按钮下载,点击item进入详情。这个时候需要在item布局内加一个descendantFocusability=“blockDescendants”.下边是官方文档的解释
android:descendantFocusability
Defines the relationship between the ViewGroup and its descendants when looking for a View to take focus.
Must be one of the following constant values.
Constant | Value | Description |
---|---|---|
beforeDescendants | 0 | The ViewGroup will get focus before any of its descendants. |
afterDescendants | 1 | The ViewGroup will get focus only if none of its descendants want it. |
blocksDescendants | 2 | The ViewGroup will block its descendants from receiving focus. |
使用了blockDescendants(ViewGroup将阻止它的后代接收焦点)则按钮就不会抢焦点了,这样就可以同时响应按钮与item了