在Android开发中,我们经常会用到ListView 这个组件,为了将ListView 的内容展示出来,我们会去实现一个Adapter来适配,将Layout中的布局以列表的形式展现到组件中。
比如,像 GGTalk 安卓版的查找用户功能,会把符合条件的用户都列在下面:
为了达到这个效果,我们需要实现一个自定义的Adapter,而其它地方的ListView也要实现一个Adapter,这些Adapter会有很多重复的代码,非常繁琐,现在我就将重复代码封装到了一个通用的适配器CommonAdapter中,这样,在使用时只要继承CommonAdapter就可以了,如此就避免了大段代码的重复,让代码更简洁易懂。我们先来看看CommonAdapter的定义。
一.CommonAdapter 实现
public abstract class CommonAdapter<T> extends BaseAdapter { private List<T> dataList; protected Context context; protected int item_layoutId=0; protected HashMap<Integer,Integer> layoutIdMap; //多种itemView时用到。 第一个int对应type类型,第二个int对应 itemlayoutId /** * 设置单个子模版VIew, * @param context * @param datas 绑定的对象集合 * @param item_layoutId 被绑定的视图layoutID * */ public CommonAdapter(Context context, List<T> datas, int item_layoutId) { this.context=context; this.dataList=datas; this.item_layoutId=item_layoutId; } /** *设置多个子模版VIew, 并配合重写 getItemViewType(int position)来确定是设置哪个模版 * @param context * @param datas 绑定的对象集合 * @param layoutIdMap 多种itemViewID Map 第一个int对应type类型,第二个int对应 itemlayoutId */ public CommonAdapter(Context context, List<T> datas, HashMap<Integer,Integer> layoutIdMap) { this.context=context; this.dataList=datas; this.layoutIdMap=layoutIdMap; } @Override public int getViewTypeCount() { if(this.layoutIdMap==null) { return 1; } return this.layoutIdMap.size(); } @Override public int getCount() { return this.dataList.size(); } @Override public Object getItem(int position) { return this.dataList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { int type = getItemViewType(position); return getConvertView(position,convertView,type); } private View getConvertView(int position,View convertView,int itemViewType) { if(convertView==null) { int layoutId =0; if(this.item_layoutId!=0) { layoutId=this.item_layoutId; } if (this.layoutIdMap != null) { layoutId = this.layoutIdMap.get(itemViewType); } convertView = LayoutInflater.from(context).inflate(layoutId, null); } T t = this.dataList.get(position); this.setConvertView(convertView,t,itemViewType); return convertView; } /** * 局部更新数据,调用一次getView()方法;Google推荐的做法 * * @param parent 要更新的listview * @param position 要更新的位置 */ public void onOneItemChanged(ListView parent, int position) { /**第一个可见的位置**/ int firstVisiblePosition = parent.getFirstVisiblePosition(); /**最后一个可见的位置**/ int lastVisiblePosition = parent.getLastVisiblePosition(); /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/ if ((position >= firstVisiblePosition && position <= lastVisiblePosition)) { /**获取指定位置view对象**/ View view = parent.getChildAt(position - firstVisiblePosition); getView(position, view, parent); } } /** * 需要去实现的对item中的view的设置操作 * * @param convertView 转换后的试图 * @param t Model对象 * @param itemViewType 试图类型。对应layoutIdMap中的key */ protected abstract void setConvertView(View convertView, T t,int itemViewType); }
说明如下:
(1)泛型参数<T>就是我们要绑定数据Model对象的class类型。
(2)getViewTypeCount()方法会根据HashMap是否为空来识别是传入的单一模版还是多模版.
(3)CommonAdapter还重写了getCount(),getItem(int position),getView,getConvertView等BaseAdapter 的方法,这些方法的代码都基本每个Adapter都是一样的,我们只需关心 setConvertView 这个抽象方法,在子类中重写它,将具体的逻辑和数据的绑定在此即可。
二.使用CommonAdapter
- 先建一个类继承CommonAdapter,在构造函数中调用父类的构造方法以初始化数据。实现 setConvertView 方法,将对象的数据绑定到模版上 即可
public class SearchFriendAdapter extends CommonAdapter<OrayUser> { /** * 设置单个子模版VIew, * @param context * @param datas 绑定的对象集合 * @param item_layoutId 被绑定的视图layoutID * */ public SearchFriendAdapter(Context context,List<OrayUser> datas,int item_layoutId) { super(context,datas,item_layoutId); } @Override protected void setConvertView(View view, OrayUser orayUser,int itemViewType) { ...具体将对象的数据绑定到模版上 } }
- Activity中使用时new 一个Adapter,然后将adapter绑定到 ListView
adapter = new SearchFriendAdapter(this,this.userList,R.layout.friend_child); listView.setAdapter(adapter);
- 更新数据源中的数据同步到View
//有2种方式将数据源同步到View adapter.notifyDataSetChanged();//全部刷新 adapter.onOneItemChanged(ListView parent, int position);//刷新指定位置的数据
三. 让CommonAdapter支持多模板
当然CommonAdapter还支持多模版的应用,我们只需将第一步中的Adapter实现稍稍做改动即可
public class ListViewAdapter extends CommonAdapter<ListViewItemModel> { /** *设置多个子模版VIew, 并配合重写 getItemViewType(int position)来确定是设置哪个模版 * @param context * @param datas 绑定的对象集合 * @param layoutIdMap 多种itemViewID Map 第一个int对应type类型,第二个int对应 itemlayoutId */ public ListViewAdapter(Context context, List<ListViewItemModel> datas, HashMap<Integer,Integer> layoutIdMap) { super(context,datas,layoutIdMap); } @Override public int getItemViewType(int position) { ListViewItemModel model= (ListViewItemModel)super.getItem(position); if(model.floatRight) { return 1; } return 0; } @Override protected void setConvertView(View view, ListViewItemModel listViewItemModel,int itemViewType) { //根据itemViewType 的值来识别是哪个模版,以对应给模版绑定数据 } }
如上图我们将构造函数的最后一个参数从单一的layoutID 换成了 模版集合的HashMap,再就是多加了 getItemViewType(int position) 方法来返回 具体Model对应的是哪一个模版类型(即hashMap 中的key值),使用时更新数据源中的数据同步到View和上面的单一模版一样使用。(同上2种更新方法)
HashMap<Integer,Integer> layoutMap=new HashMap<>(); layoutMap.put(0,R.layout.listview_item);//key值最好从0开始向上递增,否则可能会找不到key的BUG layoutMap.put(1,R.layout.listview_right_item); adapter=new ListViewAdapter(this,models,layoutMap); listView.setAdapter(adapter);
四.综合实例
最后我们还是以本文开头的查找用户的例子,来更全面地说明CommonAdapter的使用。页面截图如下:
1.模版布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/selector_item" android:descendantFocusability="blocksDescendants" android:gravity="center_vertical" android:orientation="horizontal" android:padding="4dp"> <ImageView android:id="@+id/ct_photo" android:layout_width="@dimen/headImageSize" android:layout_height="@dimen/headImageSize" android:layout_margin="5dp" android:src="@drawable/plus" /> <ImageView android:id="@+id/ct_status" android:layout_width="11dip" android:layout_height="11dip" android:layout_marginLeft="@dimen/headImageSize" android:layout_marginTop="@dimen/headImageSize" android:src='@drawable/status_2' /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" android:layout_alignTop="@id/ct_photo" android:layout_toRightOf="@id/ct_photo" > <TextView android:id="@+id/ct_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="5dp" android:paddingRight="5dp" android:paddingBottom="5dp" android:paddingTop="0dp" android:text="name" android:textColor="@color/dimgrey" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/ct_describe" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/ct_name" android:paddingLeft="5dp" android:paddingRight="5dp" android:paddingBottom="5dp" android:paddingTop="0dp" android:text="describe" android:textColor="@color/dimgrey" android:textSize="14sp" android:visibility="gone" /> </LinearLayout> <TextView android:id="@+id/ct_sign" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@id/ct_photo" android:layout_toRightOf="@id/ct_photo" android:paddingLeft="5dp" android:text="sign" android:singleLine="true" android:ellipsize="start" android:textColor="@color/dimgrey" android:textSize="12sp" /> </RelativeLayout>
2.具体继承CommonAdapter实现子类
public class SearchFriendAdapter extends CommonAdapter<OrayUser> { public SearchFriendAdapter(Context context,List<OrayUser> datas,int item_layoutId) { super(context,datas,item_layoutId); } @Override protected void setConvertView(View view, OrayUser orayUser,int itemViewType) { try{ SearchFriendAdapter.ViewHolder holder; if(view.getTag()==null) { holder = new SearchFriendAdapter.ViewHolder(view); view.setTag(holder); } else { holder = (SearchFriendAdapter.ViewHolder) view.getTag(); } HeadImgHelper.setUserHeadImg(holder.headImg,orayUser); if(orayUser.getName().equals(orayUser.getCommentName())) { holder.userName.setText(orayUser.getName()); } else { holder.userName.setText(orayUser.getCommentName()+"("+ orayUser.getName()+")"); } String catalogName= ClientGlobalCache.getInstance().getCurrentUser().getCatalogNameByFriend(orayUser.getUserID()); if(!StringHelper.isNullOrEmpty(catalogName)) { holder.describe.setText("[ "+ catalogName+" ]"); holder.describe.setVisibility(View.VISIBLE); } else { holder.describe.setText(""); holder.describe.setVisibility(View.GONE); } holder.orgName.setText(orayUser.getUserID()); }catch (Exception ex){ex.printStackTrace();} } private class ViewHolder { public ImageView headImg; public TextView userName; public TextView describe; public TextView orgName; public ViewHolder(View view) { this.headImg = (ImageView) view.findViewById(R.id.ct_photo); ImageView statusImg=(ImageView) view.findViewById(R.id.ct_status); statusImg.setVisibility(View.GONE); this.userName=(TextView) view.findViewById(R.id.ct_name); this.orgName=(TextView) view.findViewById(R.id.ct_sign); this.describe=(TextView) view.findViewById(R.id.ct_describe); } } }
3. Activity类 核心代码
public class SearchFriendActivity extends Activity implements TextView.OnEditorActionListener{ private DrawableEditText input; private ListView listView; private TextView search_resultStr; private List<OrayUser> userList=new ArrayList<OrayUser>() ; private SearchFriendAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search_friend); this.initView(); } private void initView() { TextView pageTitle=(TextView)findViewById(R.id.headerText); pageTitle.setText("查找用户"); search_resultStr =(TextView) findViewById(R.id.search_resultStr); listView=(ListView)findViewById(R.id.listview_user); listView.setOnItemClickListener(this); adapter = new SearchFriendAdapter(this,this.userList,R.layout.friend_child); listView.setAdapter(adapter); } /** * 执行点击搜索指令 * */ private void action_search(String idOrName) { List<OrayUser> temp_users=Manager.getInstance().doSearchUser(idOrName); search_resultStr.setText("没有找到符合条件的用户或群组"); this.setSearchResult(temp_users); } private void setSearchResult(List<OrayUser> temp_users) { this.userList.clear(); if(temp_users==null||temp_users.size()==0) { search_resultStr.setVisibility(View.VISIBLE); } else { for (OrayUser orayUser :temp_users) { if(orayUser.getUserID().equals(ClientGlobalCache.getInstance().getCurrentUser().getUserID())) { temp_users.remove(orayUser); break; } } this.userList.addAll(temp_users) ; search_resultStr.setVisibility(View.GONE); } this.adapter.notifyDataSetChanged(); } }