ListView布局之View复用原理举例
1.简单介绍:
ListView是android开发中经常使用的控件,系统自带的那些样式,我就不列举了。
今天主要看一下。一个模仿系统历史通话记录的ListView。
效果例如以下:
上面ListView的样式还能够更复杂。首先看一下这个简单的ListView的Item的布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/contacts_items" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ffffff" android:orientation="vertical" > <View android:id="@+id/topLine" android:layout_width="fill_parent" android:layout_height="1dp" android:background="#ff474745" /> <RelativeLayout android:layout_width="fill_parent" android:layout_height="60dp" android:gravity="center_vertical" android:paddingRight="1.0dip" > <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:gravity="center_vertical" android:orientation="horizontal" > <ImageView android:id="@+id/imgHead" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_marginLeft="15dp" android:layout_marginRight="10dp" android:contentDescription="" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:gravity="center_vertical" android:orientation="vertical" > <TextView android:id="@+id/tvName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:ellipsize="end" android:singleLine="true" android:textSize="14.0sp" /> <TextView android:id="@+id/tvTelephone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginTop="4.0dip" android:ellipsize="end" android:singleLine="true" android:textColor="#ffcccccc" android:textSize="12sp" /> <TextView android:id="@+id/tvDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="0.2dip" android:layout_marginTop="0dip" android:ellipsize="end" android:singleLine="true" android:textColor="#ffcccccc" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:gravity="center_vertical" android:orientation="horizontal" > <Button android:id="@+id/btnCall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="0.2dip" android:layout_marginTop="0dip" android:ellipsize="end" android:singleLine="true" android:textColor="#ff0000ff" android:focusable="false" android:textSize="12sp" /> </LinearLayout> </RelativeLayout> <View android:id="@+id/bottomLine" android:layout_width="fill_parent" android:layout_height="1dp" android:background="#ff1c1c1b" /> <View android:id="@+id/lastLine" android:layout_width="fill_parent" android:layout_height="1dp" android:background="#ff474745" android:visibility="gone" /> </LinearLayout>
没什么问题吧?可是你一定要注意Button的一个属性:android:focusable="false",假设不加这个属性,会使得ListView的OnItemClick被屏蔽。
因为是模仿通话记录,那么Item里面的这个属性。我们还是封装到一个类里面吧。
/* * $filename: Model.java,v $ * $Date: 2014-4-27 $ * Copyright (C) ZhengHaibo, Inc. All rights reserved. * This software is Made by Zhenghaibo. */ package com.example.testaa; import org.androidannotations.annotations.EBean; /* *@author: ZhengHaibo *web: http://blog.csdn.net/nuptboyzhb *mail: zhb931706659@126.com *2014-4-27 Nanjing,njupt,China */ @EBean public class Model { private int imgHead;//头像资源ID private String name;//姓名 private String telephone;//电话号码 private String date;//日期 public int getImgHead() { return imgHead; } public void setImgHead(int imgHead) { this.imgHead = imgHead; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this.telephone = telephone; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } }
接下来。思路非常清晰。就是继承BaseAdapter类,重写它的几个重要方法:
@Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return 0; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return null; } @Override public int getCount() { // TODO Auto-generated method stub return 0; }
依照我们的需求,我们必须在getView类中,为Item布局中的每个View进行关联。设置对应的參数。而对于Button,还要设置对应的事件监听器。我们必须注意的是:在设置事件监听器的时候。我们必须将当前的Item的位置信息position传递给监听器。否则的话,onClick方法无法知道当前按下的是哪个button。因此,我们写了一个内部类,实现OnClickListener接口,这个类的须要有一个属性来保存Item的位置。
因此,我们的BaseAdapter1代码例如以下:
/* * $filename: BaseAdapter1.java,v $ * $Date: 2014-4-27 $ * Copyright (C) ZhengHaibo, Inc. All rights reserved. * This software is Made by Zhenghaibo. */ package com.example.testaa; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.BitmapFactory; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; /* *@author: ZhengHaibo *web: http://blog.csdn.net/nuptboyzhb *mail: zhb931706659@126.com *2014-4-27 Nanjing,njupt,China */ public class BaseAdapter1 extends BaseAdapter { private Context context; private List<Model> listViewData; private int layoutResId;//ListView每个Item的布局文件 public BaseAdapter1(Context context,int layoutResId) { this.context = context; this.layoutResId = layoutResId; listViewData = new ArrayList<Model>(); } @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = LayoutInflater.from(context).inflate(layoutResId,null); Model model = listViewData.get(position); ImageView imageView = (ImageView)convertView.findViewById(R.id.imgHead); imageView.setImageBitmap(BitmapFactory.decodeResource(context.getResources(), model.getImgHead())); TextView tvName = (TextView)convertView.findViewById(R.id.tvName); tvName.setText(model.getName()); TextView tvTelephone = (TextView)convertView.findViewById(R.id.tvTelephone); tvTelephone.setText(model.getTelephone()); TextView tvDate = (TextView)convertView.findViewById(R.id.tvDate); tvDate.setText(model.getDate()); Button btnCall = (Button) convertView.findViewById(R.id.btnCall); btnCall.setText("拨打电话"); btnCall.setOnClickListener(new ListViewButtonOnClickListener(position) ); return convertView; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return listViewData.get(position); } @Override public int getCount() { // TODO Auto-generated method stub if(null == listViewData){ return 0; } return listViewData.size(); } /** * 加入一条记录 * @param model */ public void addModel(Model model){ listViewData.add(model); } /** * 获取一条记录 * @param i * @return */ public Model getModel(int i){ if(i<0||i>listViewData.size()-1){ return null; } return listViewData.get(i); } /** * 清除全部数据 */ public void clear(){ listViewData.clear(); } class ListViewButtonOnClickListener implements OnClickListener{ private int position;//记录ListView中Button所在的Item的位置 public ListViewButtonOnClickListener(int position) { this.position = position; } @Override public void onClick(View v) { Toast.makeText(context,listViewData.get(position).getTelephone(), Toast.LENGTH_SHORT).show(); } } }
由此可见,假设我们须要定制Item的布局,我们仅仅须要改动的地方除了Item的布局文件以外,还要将Adapter里的getView方法进行对应的改动。
接下来看一下Activity的測试代码
package com.example.testaa; import java.text.SimpleDateFormat; import java.util.Date; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.ViewById; import android.app.Activity; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast; @EActivity(R.layout.activity_list_1) public class ActivityList1 extends Activity implements OnItemClickListener{ @ViewById ListView listView; //ListView的设配器 private BaseAdapter1 baseAdapter1; @AfterViews void afterViewInitList(){ baseAdapter1 = new BaseAdapter1(this,R.layout.listview1); listView.setAdapter(baseAdapter1); listView.setOnItemClickListener(this); for(int i=0;i<10;i++){ Model model = new Model(); model.setImgHead(R.drawable.ic_launcher); model.setName("Name"+i); model.setTelephone("手机 1311111111"+i); model.setDate(new SimpleDateFormat().format(new Date()).toString()); baseAdapter1.addModel(model); } baseAdapter1.notifyDataSetChanged(); } @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) { // TODO Auto-generated method stub Log.d("ItemClick", "pos="+position); String string = "clicked item"+position+"content="+baseAdapter1.getModel(position).getTelephone(); Toast.makeText(this, string, Toast.LENGTH_SHORT).show(); } }
此时。就完毕了我们想要的功能。
Item的点击事件和Button的点击事件互不冲突。
问题二:
向微信,QQ,易信等聊天界面的ListView则有所不同。
我们上述的样例是:Item仅仅有一个布局。而聊天界面其中ListView的Item布局有多种,比方显示文字的布局,显示图片的布局,显示语音的布局等等。除此之外。我们还要依据消息的发送者,将其左右分开。在这里,仅仅演示左右的文本。原理都是都是一样的。
先看一下布局文件
左右文本的布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/imgHead" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_margin="5dip" android:src="@drawable/ic_launcher" /> <Button android:id="@+id/btn_left_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/imgHead" android:layout_marginTop="5dip" android:layout_marginBottom="5dip" android:textColor="#404040" android:textSize="16sp" android:gravity="center" android:focusable="false" android:background="@drawable/chatfrom_bg_normal" /> </RelativeLayout>
右边的布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/imgHead" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_margin="5dip" android:src="@drawable/ic_launcher" /> <Button android:id="@+id/btn_right_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@+id/imgHead" android:layout_marginTop="5dip" android:layout_marginBottom="5dip" android:textColor="#404040" android:textSize="16sp" android:gravity="center" android:focusable="false" android:background="@drawable/chatto_bg_normal" /> </RelativeLayout>
再看一下适配器,基本和上一个样例一样,不一样无非就是getView方法的差异。
/* * $filename: BaseAdapter1.java,v $ * $Date: 2014-4-27 $ * Copyright (C) ZhengHaibo, Inc. All rights reserved. * This software is Made by Zhenghaibo. */ package com.example.testaa; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.BitmapFactory; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; /* *@author: ZhengHaibo *web: http://blog.csdn.net/nuptboyzhb *mail: zhb931706659@126.com *2014-4-27 Nanjing,njupt,China */ public class ChatBaseAdapter extends BaseAdapter { private Context context; private List<Msg> listViewData; public ChatBaseAdapter(Context context) { this.context = context; listViewData = new ArrayList<Msg>(); } /** * 依据发送消息的类型进行分类,不同的消息类型不同的布局 */ @Override public View getView(int position, View convertView, ViewGroup parent) { Msg msg = listViewData.get(position); if(msg.isSelf()){//自己发送的消息 convertView = LayoutInflater.from(context).inflate(R.layout.list_item_right_text,null); ImageView imgHead =(ImageView) convertView.findViewById(R.id.imgHead); imgHead.setImageBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher)); Button btn = (Button)convertView.findViewById(R.id.btn_right_text); btn.setText(msg.getContent()); btn.setOnClickListener(new ListViewButtonOnClickListener(position)); }else {//对方发送的消息 convertView = LayoutInflater.from(context).inflate(R.layout.list_item_left_text,null); ImageView imgHead =(ImageView) convertView.findViewById(R.id.imgHead); imgHead.setImageBitmap(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher)); Button btn = (Button)convertView.findViewById(R.id.btn_left_text); btn.setText(msg.getContent()); btn.setOnClickListener(new ListViewButtonOnClickListener(position)); } return convertView; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return listViewData.get(position); } @Override public int getCount() { // TODO Auto-generated method stub if(null == listViewData){ return 0; } return listViewData.size(); } /** * 加入一条记录 * @param Msg */ public void addMsg(Msg Msg){ listViewData.add(Msg); } /** * 获取一条记录 * @param i * @return */ public Msg getMsg(int i){ if(i<0||i>listViewData.size()-1){ return null; } return listViewData.get(i); } /** * 清除全部数据 */ public void clear(){ listViewData.clear(); } class ListViewButtonOnClickListener implements OnClickListener{ private int position;//记录ListView中Button所在的Item的位置 public ListViewButtonOnClickListener(int position) { this.position = position; } @Override public void onClick(View v) { Toast.makeText(context,listViewData.get(position).getContent(), Toast.LENGTH_SHORT).show(); } } }
Activity的代码为:
package com.example.testaa; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.ViewById; import android.app.Activity; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast; @EActivity(R.layout.activity_list_2) public class ActivityList2 extends Activity implements OnItemClickListener{ @ViewById ListView listView; private ChatBaseAdapter chatBaseAdapter; @AfterViews void afterViewInitList(){ chatBaseAdapter = new ChatBaseAdapter(this); listView.setAdapter(chatBaseAdapter); listView.setOnItemClickListener(this); for(int i=0;i<10;i++){ Msg msg = new Msg(); if(i%2==0){ msg.setSelf(false); }else { msg.setSelf(true); } msg.setContent("abc"+i); chatBaseAdapter.addMsg(msg); } chatBaseAdapter.notifyDataSetChanged(); } @Override public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) { // TODO Auto-generated method stub Log.d("ItemClick", "pos="+position); String string = "clicked item"+position+"content="+chatBaseAdapter.getMsg(position).getContent(); Toast.makeText(this, string, Toast.LENGTH_SHORT).show(); } }
效果:
关于聊天界面的很多其它内容可參考博文:http://blog.csdn.net/xyz_lmn/article/details/13745489
原理基本一样。
注意:整个项目的代码使用的是AndroidAnnotation框架,本博客的代码下载:http://download.csdn.net/detail/nuptboyzhb/7260915