Android上用模板方法模式实现具有自动重用View功能的Adapter

ListView和GridView简介


在Android App的开发中, ListView和GridView等控件是使用非常频繁的控件。 这两个控件的特点是使用数据适配器来显示数据, 并且在数据项较多的时候, 可以重用用于显示数据的条目(这里的条目指的是ListView或GridView中用于显示每一个数据项的子View)。本文的重点并不是讲解ListView和GridView的内部实现或使用方法,但是后文讲解的自定义Adapter是被ListView和GridView所调用的,所以在这里简单提及ListView和GridView。ListView,GridView和Adapter的关系如下图所示:




适配器的一般使用方式


由于GridView的使用方式和ListView很相似, 为了叙述的简洁, 下文只以ListView为例讲解。在开发中,ListView和Adapter的使用方式一般情况下是这样的:

1 继承SDK中的BaseAdapter, 实现和业务相关的自定义Adapter

2 创建一个Adapter的实例, 调用ListView的setAdapter方法, 将这个Adapter对象, 设置到ListView对象的内部。

在这里主要讲述实现自定义Adapter的过程。 在继承BaseAdapter后, 要实现BaseAdapter中定义的抽象方法getView。getView方法是由ListView对象调用的。 概括一下, 就是ListView对象在需要一个新的条目View的时候, 会调用这个getView方法,你必须实现这个方法,返回一个准备好的View。但是,ListView具有重用条目View的功能。 也就是不是每次需要一个条目View都从头创建。所以getView方法有一个参数convertView, 这个也是由ListView传到Adapter中的。 当ListView中存在能够重用的View时就将这个View传到getView方法中。 getView方法的实现者要判断convertView是否为空, 如果不为空, 就重用这个View。

自定义Adapter中的getView实现方式一般如下:(这里为了节省时间,直接使用项目中的代码, 读者只需关注getView 的实现形式, 不用关心业务相关的变量名称)

	private static class ScheduleAdapter extends BaseAdapter{

		private List<Schedule> meetings ;
		private Context context;
		private LayoutInflater inflator;
		
		public ScheduleAdapter(Context context ) {
			this.context = context;
			
			inflator = LayoutInflater.from(context);
		}

		public ScheduleAdapter(Context context, List<Schedule> meetings) {
			super();
			this.context = context;
			this.meetings = meetings;
			
			inflator = LayoutInflater.from(context);
		}

		public List<Schedule> getMeetings() {
			return meetings;
		}

		public void setMeetings(List<Schedule> meetings) {
			this.meetings = meetings;
		}

		@Override
		public int getCount() {
			return meetings.size();
		}

		@Override
		public Object getItem(int position) {
			return meetings.get(position);
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View v = null;
			
			if(convertView == null){
				v = inflator.inflate(R.layout.schedule_item, null);
				
				ViewHolder holder = new ViewHolder();
				holder.name = (TextView) v.findViewById(R.id.schedule_name_id);
				holder.time = (TextView) v.findViewById(R.id.schedule_time_id);
				holder.icon = (ImageView) v.findViewById(R.id.schedule_icon_id);
				
				v.setTag(holder);
				
			}else{
				v = convertView;
			}
			
			ViewHolder holder = (ViewHolder) v.getTag();
			
			Schedule data = meetings.get(position);
			holder.name.setText(data.getName());
			holder.time.setText(data.getStartTime() + "		" + data.getEndTime());
			
			return v;
		}
		
		private static class ViewHolder{
			public TextView name;
			public TextView time;
			public ImageView icon;
		}
		
	}


从上述代码可以看出, getView方法中处理的逻辑有两个:

1 判断是否可以重用View, 如果不能重用, 就创建新的条目View, 找到条目View中的各个子View, 被存放在一个ViewHolder独享中。如果可以重用View, 就重用这个view, 并且从和这个View相关的ViewHolder对象中找到已经缓存的子View。


2  将具体的数据和事件绑定到各个子View上。



实现自动重用View的Adapter


从上面的代码经常被使用到, 每次都要在getView方法中写这些重用View的逻辑, 并且还要每次都写一个业务相关的ViewHolder类,真的是非常非常恶心。那么有没有办法封装一下这些恶心的逻辑,而不用每次都从头写起呢? 本文提供了一种实现方法。

首先给出完整的实现代码, 然后对代码中的关键点进行分析。

import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

/**
 * 一个具有自动重用View功能的适配器实现
 * 
 * @author zhangjg
 * @date Feb 20, 2014 10:57:42 AM
 */
public abstract  class AutoReuseViewAdapter extends BaseAdapter {

	//Item布局资源
	private int itemLayoutRes;
	
	//Item中要操作的各个子View的Id
	private int[] childViewIds;

	//布局填充器
	private LayoutInflater inflator;

	
	public AutoReuseViewAdapter(Context context, int itemLayoutRes, int ... childViewIds) {
		
		inflator = LayoutInflater.from(context);

		this.itemLayoutRes = itemLayoutRes;
		this.childViewIds = childViewIds;

	}


	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		
		View v = null;
		
		ViewHolder holder;
		
		
		if(convertView != null){   
			v = convertView;      
			holder = (ViewHolder) v.getTag();
		}else{
			v = inflator.inflate(itemLayoutRes, null);
			
			holder = new ViewHolder();
			
			for(int childId : childViewIds ){
				holder.cacheView(v.findViewById(childId));
			}
			
			v.setTag(holder);
		}
		
		boundDataAndEventToViews(position, v, holder.getCachedViews());
		
		return v;
	}

	/**
	 * 抽象方法, 被子类覆盖, 将业务相关的数据和事件绑定到特定的子View上
	 */
	protected abstract void boundDataAndEventToViews(int position,
			View itemView, ArrayList<View> childViews);

	
	/**
	 * 内部类, 用于缓存条目View的子控件, 同样是面向抽象和业务无关的
	 * 
	 * @author zhangjg
	 * @date Feb 20, 2014 9:12:33 AM
	 */
	private static class ViewHolder{
		private ArrayList<View> cachedViews = new ArrayList<View>();
		
		public void cacheView(View v){
			cachedViews.add(v);
		}
		
		public ArrayList<View> getCachedViews(){
			return cachedViews;
		}
	}

}

首先解释一下这个抽象类的成员变量。 itemLayoutRes是条目View的布局资源, childViewIds是一个int型的数组, 用于存放条目View中要处理的各个子View的id 。 这两个成员变量是通过构造方法注入的, 代码如下:

	private int itemLayoutRes;
	
	//Item中要操作的各个子View的Id
	private int[] childViewIds;

	//布局填充器
	private LayoutInflater inflator;

	
	public AutoReuseViewAdapter(Context context, int itemLayoutRes, int ... childViewIds) {
		
		inflator = LayoutInflater.from(context);

		this.itemLayoutRes = itemLayoutRes;
		this.childViewIds = childViewIds;

	}

在构造方法中, 还要根据context创建一个LayoutInflater对象, 用于从XML布局中加载条目View。

然后说一下getView方法的实现。 这个类中, 将getView中的逻辑分到两个方法中去处理。首先重用View的逻辑是业务无关的, 在getView 中实现, 将数据和事件绑定到各个子View上是业务相关的, 交给boundDataAndEventToViews方法处理, 这是个抽象方法, 必须由子类覆盖并实现, 因为业务相关的东西也只有具体子类才知道。

现在再从整体上审视一下这个Adapter的实现。 由于getView是由ListView调用的, 至于如何调用, 何时调用我们无法干预,但是不管怎么写代码, getView方法必须完成属于他的所有任务。 这里再重复一下getView 的职责:

1 判断是否可以重用View, 如果不能重用, 就创建新的条目View, 找到条目View中的各个子View, 被存放在一个ViewHolder独享中。如果可以重用View, 就重用这个view, 并且从和这个View相关的ViewHolder对象中找到已经缓存的子View。


2  将具体的数据和事件绑定到各个子View上。


在我们实现的这个AutoReuseViewAdapter中, getView方法同样完成了两个职责。 也就是说原有的处理流程还是由getView方法定义, 只是将部分逻辑(绑定数据和事件到子View)交给具体子类去实现, 这是标准的模板方法设计模式。



使用自动重用View功能的Adaper


在上面的步骤中, AutoReuseViewAdapter已经实现好了。 现在就看看如何根据这个AutoReuseViewAdapter实现自己业务相关的Adapter。基于AutoReuseViewAdapter实现自己的Adapter, 只需要做以下工作:

1 自定义自己的Adapter, 继承AutoReuseViewAdapter;

2 在子类的构造方法中调用父类AutoReuseViewAdapter的构造方法, 将条目布局的资源索引和条目View中各个子View 的Id传到父类构造器中;

3 覆盖并实现父类中的抽象方法, 在boundDataAndEventToViews方法中对子View进行数据和事件绑定。

在上面“适配器的一般使用方式“一节中, 给出了适配器的一般实现。 现在我们基于AutoReuseViewAdapter重新实现它, 看看是不是简单了很多。下面是重新实现的版本:

	private static class ScheduleAdapter extends /*BaseAdapter*/AutoReuseViewAdapter{

		private List<Schedule> meetings ;
		
		public ScheduleAdapter(Context context ) {
			super(context, R.layout.schedule_item, 
					R.id.schedule_name_id, R.id.schedule_time_id, R.id.schedule_icon_id);
		}

		public ScheduleAdapter(Context context, List<Schedule> meetings) {
			this(context);
			this.meetings = meetings;
			
		}

		public List<Schedule> getMeetings() {
			return meetings;
		}

		public void setMeetings(List<Schedule> meetings) {
			this.meetings = meetings;
		}

		@Override
		public int getCount() {
			return meetings.size();
		}

		@Override
		public Object getItem(int position) {
			return meetings.get(position);
		}

		@Override
		public long getItemId(int position) {
			return position;
		}


		@Override
		protected void boundDataAndEventToViews(int position, View itemView,
				ArrayList<View> childViews) {
			
			Schedule data = meetings.get(position);
			
			((TextView)childViews.get(0)).setText(data.getName());
			((TextView)childViews.get(1)).setText(data.getStartTime() + "		" + data.getEndTime());
			//((ImageView)childViews.get(2)).setImageResource(resId);
		}
		
	}

看, 是不是变得简单多了。 我们现在只需要关心业务相关的逻辑了, 不用再关心业务无关的逻辑(重用View的逻辑)。所以, 如果在项目中有一些业务无关的代码总是重复的写, 那么有很大可能有优化的余地, 这时我们就要发扬程序员的”懒惰“的优良传统,将重复的逻辑抽取出来。

最后有两点需要强调:

1 因为父类中的getView方法定义了完整的逻辑流程, 所以子类绝对不要覆盖getView方法, 只要实现boundDataAndEventToViews方法就够了。

boundDataAndEventToViews方法中, 需要绑定数据和事件的子View是由一个集合ArrayList<View> childViews, 通过父类传递到子类中的, 子View在集合中的顺序, 和构造方法中子View的Id传入的顺序相同。 以上面的代码为例,在构造方法中子View的id传入的顺序如下: R.id.schedule_name_id, R.id.schedule_time_id, R.id.schedule_icon_id 。 第一个是显示名字的TextView, 第二个是显示时间的TextView, 第三个是显示图标的ImageView。所以在boundDataAndEventToViews中childViews集合中子View的顺序也是这样的。这种顺序相关性必须由子类定义和维护, 如果顺序不对, 可能会抛出类型转化异常。





  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答1: 要实现一个作业功能的源码,首先需要明确这个功能的具体需求。比如,是老师发布作业、学生提交作业并打分、作业的截止日期等等。接下来,可以采用MVC(Model-View-Controller)架构来设计程序,将不同的功能分别放在不同的类中。 模型层(Model)可以将数据存储在数据库中,包括学生信息、作业内容、分数等。可以使用SQLite或者Room来进行数据库的操作。 视图层(View)则负责展示数据和接收用户的操作,比如老师发布作业的界面、学生提交作业的界面、显示作业列表和分数的界面等。UI可以采用XML布局来进行设计,通过findViewById()方法获取相关控件并设置相应的监听器。 控制器层(Controller)则是连接模型层和视图层的桥梁,负责处理各种逻辑和业务逻辑,比如用户提交作业后如何保存到数据库、如何判断作业是否在截止日期前提交等。 实现作业功能的源码需要考虑到代码的可重用性和可扩展性。可以将一些通用的类(如数据库的操作类)封装起来,方便其他模块调用。同时,在代码中加入注释和文档,方便其他开发人员理解和修改。 总体来说,实现作业功能的源码需要对Android Studio和MVC架构都有一定的了解和掌握,还需要积累一定的编程经验和技巧。 ### 回答2: Android Studio实现作业功能的源码具体实现方法会根据实际的需求和功能而有所不同,但是一般而言,可以参考下面的步骤和代码实现: 1. 创建一个Activity用于展示作业列表,并在布局文件中添加一个ListView或RecyclerView。 2. 创建一个作业实体类,该实体类可以包括标题、内容、截止时间等信息,根据实际需求添加相应的属性。 3. 创建一个作业数据管理类,可以使用SQLite数据库或其他方式实现数据的增删改查操作。在该类中定义获取作业列表、添加作业、删除作业等方法,用于与Activity交互。 4. 在作业列表Activity中初始化数据管理类,获取作业列表,并将作业列表展示在ListView或RecyclerView中。 5. 当用户点击作业列表的某个作业时,跳转到作业详情页面。创建一个作业详情Activity,展示作业的详细信息,并提供修改作业、删除作业等功能。 6. 在作业详情Activity中可以通过Intent传递作业实体类对象到编辑作业页面。创建一个编辑作业Activity,用于编辑作业的信息,包括标题、内容、截止时间等。 7. 编辑作业Activity中可以将作业的信息保存到数据库中,并返回作业详情页面,同时更新作业列表页面的作业信息。 8. 在作业详情Activity中,可以通过对话框或其他方式提供删除作业的操作。删除作业时,需要将作业从数据库中删除,并返回到作业列表页面,同时更新作业列表的展示。 9. 可以根据实际需求添加其他功能,如作业提醒、作业分享等。 以上是Android Studio实现作业功能的源码实现步骤及代码示例,具体实现方式还需要根据实际需求进行相应的调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值