zoukankan      html  css  js  c++  java
  • Android教你怎样一步步打造通用适配器

    前言

    在Android开发中ListView是最为经常使用的控件之中的一个,基本每一个应用都会涉及到它,要使用ListView列表展示,就不可避免地涉及到另外一个东西——Adapter,我们都知道,Adapter是连接数据和列表界面的一个桥梁,一般项目中一个listview就会有一个Adapter与之相应。然后就是一堆方法的重写,包含getCount,getItem,getView等等。遇到自己定义布局时还需重写getView方法,重写getView的时候逻辑不复杂还好。遇到代码逻辑复杂的时候adapter简直臃肿,而且还须要写非常多次反复的代码,比方推断convertView是否为空,findViewById无数次停不下来睡觉



    写了这么多。你是否想过,可否有一个公用的自己定义Adapter基类,将这些经常反复的代码和逻辑封装起来。方便我们调用,降低getView中的代码逻辑,下面就来一步步将其“包装”起来成为我们想要的效果。




    先走一遍我们之前写ListView和Adapter的方式:

    activity_main.xml:

    <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"
        >
    
        <ListView 
            android:id="@+id/listview"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            
        </ListView>
    
    </RelativeLayout>



    MainActivity:

    public class MainActivity extends Activity {
    	
    	private ListView listview;
    	
    	private MyAdapter adapter;
    	
    	private List<String> data;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		listview = (ListView)this.findViewById(R.id.listview);
    		
    		getData();
    		
    		adapter = new MyAdapter(this.getApplicationContext(), data);
    		
    		listview.setAdapter(adapter);
    	}
    	
    	public void getData(){
    		data = new ArrayList<String>();
    		for(int i=0; i<20; i++){
    			data.add("数据"+i);
    		}
    	}
    }



    自己定义适配器 MyAdapter:

    public class MyAdapter extends BaseAdapter{
    	
    	private Context mContext;
    	
    	private List<String> list;
    	
    	public MyAdapter(Context context, List<String> list){
    		this.mContext = context;
    		this.list = list;
    	}
    	
    	@Override
    	public int getCount() {
    		// TODO Auto-generated method stub
    		return list.size();
    	}
    	
    	@Override
    	public Object getItem(int position) {
    		// TODO Auto-generated method stub
    		return list.get(position);
    	}
    	
    	@Override
    	public long getItemId(int position) {
    		// TODO Auto-generated method stub
    		return position;
    	}
    	
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		// TODO Auto-generated method stub
    		ViewHolder viewholder = null;
    		if(convertView == null){
    			convertView = View.inflate(mContext, R.layout.item_listview, null);
    			viewholder = new ViewHolder();
    			viewholder.titleTv = (TextView)convertView.findViewById(R.id.titleTv);
    			convertView.setTag(viewholder);
    		}
    		else{
    			viewholder = (ViewHolder)convertView.getTag();
    		}
    		viewholder.titleTv.setText(list.get(position));
    		
    		return convertView;
    	}
    	
    	public static class ViewHolder{
    		TextView titleTv;
    	}
    
    }



    每一个列表项的布局文件 item_listview.xml:

    <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:id="@+id/titleTv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingTop="15dp"
            android:paddingBottom="15dp"
            android:paddingLeft="15dp"
            android:textSize="20sp"
            android:textColor="#000"
            />
        
    </LinearLayout>



    对于以上这部分代码不太明确能够參考我之前的文章ListView基础篇ListView优化篇,通过以上代码就能够完成一个简单的列表界面和数据展示:




    接下来我们就在这个基础上进行一步步封装:

    能够看到。我们每次调用getView时候,都会新建一个ViewHolder,然后推断convertView是否为空。以及用

    viewholder存储我们每一个列表项的子控件,再通过setTag和getTag来复用Viewholder,这一部分逻辑是每次getView

    都会调用的,所以首先能够想到将ViewHolder对象的逻辑给封装起来,封装ViewHolder首先要考虑下面几点:

    封装ViewHolder

    1.首先这个封装来肯定要有一个ViewHolder的构造方法。另外,从上面能够看出每次getView都须要初始化convertView,那么我们能够将convertView的初始化搬到ViewHold的构造方法中来进行。既然convertView要在ViewHolder的构造方法中初始化。那么必然还须要inflate所须要的參数,以及每一个Item的下标。即context、layoutId、ViewGroup、position:

    public class CommonViewHolder {
    	
    	public View mConvertView;
    
    	public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
    		mConvertView = View.inflate(context, layoutId, null);
    		mConvertView.setTag(this);
    	}
    }



    2.注意到曾经的方式每次都须要推断convertView是否为null,是则new一个新的convertView和ViewHolder实例而且setTag,否则採用getTag重用之前的ViewHolder:

    public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
    	if(convertView == null){
    		return new CommonViewHolder(context, position, layoutId, parent);
    	}
    	else{
    		return (CommonViewHolder)convertView.getTag();
    	}
    }



    3.不同场景下列表项的元素是不确定的。数量和类型都不一致,既然是打造通用Adapter。那肯定要兼容多种情况,数量上我们能够想到使用Map来存储我们的子控件,类型上能够使用Java的泛型来构造。例如以下:

    private HashMap<Integer, View> map = new HashMap<Integer, View>();
    
    public <T extends View> T getView(int viewId){
    	View view = map.get(viewId);
    	//假设view为空,则findId找到。并放进map中
    	if(view == null){
    		view = mConvertView.findViewById(viewId);
    		map.put(viewId, view);
    	}
    	//假设view不会空,则直接返回
    	return (T)view;
    }



    4.当然,曾经我们getView方法最后返回的是一个convertView,所以还能够提供一个getConvertView的方法返回每一行相应的convertView:

    public View getConvertView(){
    	return mConvertView;
    }



    至此,完整的通用ViewHolder已打造完成,完整代码例如以下:

    public class CommonViewHolder {
    	
    	public HashMap<Integer, View> map;
    	
    	public View mConvertView;
    	
    	public CommonViewHolder(Context context, int position, int layoutId, ViewGroup parent){
    		map = new HashMap<Integer, View>();
    		mConvertView = View.inflate(context, layoutId, null);
    		mConvertView.setTag(this);
    	}
    	
    	public static CommonViewHolder get(Context context, View convertView, int position, int layoutId, ViewGroup parent){
    		if(convertView == null){
    			return new CommonViewHolder(context, position, layoutId, parent);
    		}
    		else{
    			return (CommonViewHolder)convertView.getTag();
    		}
    	}
    	
    	public <T extends View> T getView(int viewId){
    		View view = map.get(viewId);
    		if(view == null){
    			view = mConvertView.findViewById(viewId);
    			map.put(viewId, view);
    		}
    		return (T)view;
    	}
    
    	public View getConvertView(){
    		return mConvertView;
    	}
    }



    现在,我们Adapter中的getView也就变成了这个样子:

    public View getView(int position, View convertView, ViewGroup parent) {
    	// TODO Auto-generated method stub
    	CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
    	TextView titleTv = holder.getView(R.id.titleTv);
    	titleTv.setText(list.get(position));
    	return holder.getConvertView();
    }



    它的代码执行流程例如以下:
    1.首先进入get方法获取一个ViewHolder实例,假设convertView为空,则进入到构造方法,new一个用来存放这一行的map集合,inflate一个新的View,而且给它setTag,假设convertView不为空。则直接通过getTag获得ViewHolder实例

    2.接着调用holder.getView。传入控件ID。假设在该map中还未有过,则通过findViewById找到控件,并存放进该行的View集合中。假设已经存在,则能够进行View的复用,即直接map.get(viewId);

    3.最后调用getConvertView,获得我们已经处理好的convertView实例


    封装Adapter

    上面我们对ViewHolder进行了封装。让adapter的getView方法大大简化。接下来開始封装我们的Adapter
    封装Adapter成为公共类。我们须要注意下面问题:
    平时我们写Adapter的时候数据类型总是不一样的,比方一会儿是一个User列表,一会儿是一个Car列表。传进来的数据源的类型通常是不一样的,那怎样做到无论传进来什么类型都能使用呢?是的没错,又是通过泛型来解决:

    public abstract class CommonAdapter<T> extends BaseAdapter{
    	
    	public Context mContext;
    	
    	public List<T> list;
    	
    	public CommonAdapter(Context context, List<T> list){
    		this.mContext = context;
    		this.list = list;
    	}
    	
    	@Override
    	public int getCount() {
    		// TODO Auto-generated method stub
    		return list.size();
    	}
    	
    	@Override
    	public Object getItem(int position) {
    		// TODO Auto-generated method stub
    		return list.get(position);
    	}
    	
    	@Override
    	public long getItemId(int position) {
    		// TODO Auto-generated method stub
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		// TODO Auto-generated method stub
    		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
    		TextView titleTv = holder.getView(R.id.titleTv);
    		titleTv.setText(list.get(position));
    		return holder.getConvertView();
    	}	
    	
    }



    能够看到。我们将Adapter的数据类型取代为泛型的形式,而且我们定义的CommonAdapter为一个抽象类。这样做的原因是在基类先将getCount、getItem等方法给实现了,然后以后的具体Adapter类就仅仅须要继承该CommonAdapter,可是事实上开发中我们的getView是每次的操作都是各有所异的,不可能定死,而且细致看你会发现getView中的生成holder实例的代码和返回convertView实例的代码是千篇一律,差别仅仅在于传进来的item的布局id以及控件的生成不一样罢了,所以能够将这部分不一样的提取出来放在一个抽象方法中,留给子类去实现。将

    MainActivity中:CommonAdapter改动例如以下:

    public abstract class CommonAdapter<T> extends BaseAdapter{
    	
    	public Context mContext;
    	
    	public List<T> list;
    	
    	public int layoutId;
    	
    	public CommonAdapter(Context context, List<T> list, int layoutId){
    		this.mContext = context;
    		this.list = list;
    		this.layoutId = layoutId;
    	}
    	
    	@Override
    	public int getCount() {
    		// TODO Auto-generated method stub
    		return list.size();
    	}
    	
    	@Override
    	public Object getItem(int position) {
    		// TODO Auto-generated method stub
    		return list.get(position);
    	}
    	
    	@Override
    	public long getItemId(int position) {
    		// TODO Auto-generated method stub
    		return position;
    	}
    	
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		// TODO Auto-generated method stub
    		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, layoutId, parent);
    		convert(holder, list.get(position), position);
    		return holder.getConvertView();
    	}
    	//这个就是留给具体Adapter实现的方法
    	public abstract void convert(CommonViewHolder viewHolder, T data, int position);
    	
    }



    至此,我们的通用Adapter打造完成,接下来我们来看看实践效果:

    样例1

    先定义一个用于纯文本显示的ListView的Adapter类:

    public class TextListViewAdapter extends CommonAdapter<String>{
    
    	public TextListViewAdapter(Context context, List<String> list) {
    		super(context, list);
    		// TODO Auto-generated constructor stub
    	}
    	
    	public View getView(int position, View convertView, ViewGroup parent) {
    		// TODO Auto-generated method stub
    		CommonViewHolder holder = CommonViewHolder.get(mContext, convertView, position, R.layout.item_listview, parent);
    		TextView titleTv = holder.getView(R.id.titleTv);
    		titleTv.setText(list.get(position));
    		return holder.getConvertView();
    	}	
    	
    }



    MainActivity中:

    public class MainActivity extends Activity {
    	
    	private ListView listview;
    	private CommonAdapter adapter;
    	private List<String> data;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		listview = (ListView)this.findViewById(R.id.listview);
    		initData();
    		adapter = new TextListViewAdapter(this.getApplicationContext(), data);
    		listview.setAdapter(adapter);
    	}
    	
    	public void initData(){
    		data = new ArrayList<String>();
    		for(int i=0; i<20; i++){
    			data.add("数据"+i);
    		}
    	}
    }



    能够看到,Adapter的代码比曾经省去了好多,执行后效果:




    样例2

    我们再试试多控件的情况,将item_listview布局文件更改例如以下:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginTop="15dp"
        android:layout_marginBottom="15dp"
        android:orientation="horizontal"
        >
        
        <ImageView 
            android:id="@+id/item_iv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:src="@drawable/ic_launcher"/>
        
        <LinearLayout 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:orientation="vertical">
            
            <TextView 
    	        android:id="@+id/titleTv"
    	        android:layout_width="match_parent"
    	        android:layout_height="0dp"
    	        android:layout_weight="2"
    	        android:gravity="bottom"
    	        android:paddingTop="10dp"
    	        android:paddingBottom="0dp"
    	        android:paddingLeft="15dp"
    	        android:textSize="15sp"
    	        android:textColor="#000"
    	        android:text="标题"
    	        />
            <TextView 
    	        android:id="@+id/detailTv"
    	        android:layout_width="wrap_content"
    	        android:layout_height="0dp"
    	        android:layout_weight="2"
    	        android:gravity="top"
    	        android:paddingTop="0dp"
    	        android:paddingBottom="10dp"
    	        android:paddingLeft="15dp"
    	        android:textSize="12sp"
    	        android:textColor="#676767"
    	        android:text="具体内容"
    	        />
        </LinearLayout>
        
    </LinearLayout>



    MainActivity中:

    public class MainActivity extends Activity {
    	
    	private ListView listview;
    	
    	private CommonAdapter adapter;
    	
    	private List<ItemBean> data;
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		listview = (ListView)this.findViewById(R.id.listview);
    		initData();
    		adapter = new TextImgAdapter(this.getApplicationContext(), data, R.layout.item_listview);
    		listview.setAdapter(adapter);
    	}
    	
    	public void initData(){
    		data = new ArrayList<ItemBean>();
    		for(int i=0; i<20; i++){
    			ItemBean bean = new ItemBean(R.drawable.ic_launcher, "标题"+i, "具体内容"+i);
    			data.add(bean);
    		}
    	}
    }

    能够看到,做了些更改,数据类型更改为了我们自己定义的bean,bean中有三个属性,分别每一个ListViewItem中的头像、标题、内容



    ItemBean类:

    public class ItemBean {
    	
    	private int imgid;
    	private String title;
    	private String detail;
    	
    	public ItemBean() {
    		super();
    		// TODO Auto-generated constructor stub
    	}
    	public ItemBean(int imgid, String title, String detail) {
    		super();
    		this.imgid = imgid;
    		this.title = title;
    		this.detail = detail;
    	}
    	public int getImgid() {
    		return imgid;
    	}
    	public void setImgid(int imgid) {
    		this.imgid = imgid;
    	}
    	public String getTitle() {
    		return title;
    	}
    	public void setTitle(String title) {
    		this.title = title;
    	}
    	public String getDetail() {
    		return detail;
    	}
    	public void setDetail(String detail) {
    		this.detail = detail;
    	}
    
    }



    最后再看看我们的Adapter。因为现在的item布局多了一个ImageView以及一个TextView,所以我们的Adapter相应得变成例如以下:

    public class TextImgAdapter extends CommonAdapter<ItemBean>{
    	
    	public TextImgAdapter(Context context, List<ItemBean> list, int layoutId) {
    		super(context, list, layoutId);
    		// TODO Auto-generated constructor stub
    	}
    
    	@Override
    	public void convert(CommonViewHolder viewHolder, ItemBean data, int position) {
    		// TODO Auto-generated method stub
    		ImageView item_iv = viewHolder.getView(R.id.item_iv);
    		TextView titleTv = viewHolder.getView(R.id.titleTv);
    		TextView detailTv = viewHolder.getView(R.id.detailTv);
    		item_iv.setBackgroundResource(R.drawable.ic_launcher);
    		titleTv.setText(data.getTitle());
    		detailTv.setText(data.getDetail());
    	}
    
    }



    仅仅是多了几行控件的生成以及设置值,清晰了非常多有木有~~以后有再多的元素,依旧仅仅需先生成相应的实例,然后set值。一目了然。

    执行结果:



    成功实现我们的效果。妈妈再也不用操心我写Adapter写到废寝忘食.........

  • 相关阅读:
    设计模式 --> (3)策略模式
    设计模式 --> (2)单例模式
    设计模式 --> (1)工厂模式
    Algorithm --> 十大排序算法
    Algorithm --> 求阶乘末尾0的个数
    网络通信 --> epoll用法
    数据库 --> sqlite3之api使用
    网络通信 --> select()用法
    网络通信 --> TCP三次握手和四次挥手
    『转载』周立功:专注的力量成就梦想
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8909426.html
Copyright © 2011-2022 走看看