ListView混搭ProgressBar最佳更新方式

很多时候我们都通过BaseAdapter.getView()中的convertView来提高ListView的性能,这个时候如果我的的ListView的Item里有一个正在更新ProgressBar,结果就悲惨了。。。 滑动界面时并没有达到我们想要的效果。解决这个问题其实很容易,在数据集中保存一下更新的进度,然后在getView中不断去设置进度。

还有一个问题就是,当有进度更新的时候,我们是要不断mAdapter.notifyDatasetChanged()来更新ListView吗?这样做当然可以,但是效率极其低下,要知道notifyDatasetChanged的代价是非常高的。那么,有没有更好的方式呢?当然有,那就是手动更新item的方式。

下面,我们来一步步的实现一个最佳的更新方式,至于何时选用手动更新,何时选用notify,我认为在有数据频繁更新或者只需要更新一条数据的时候要选择前者,普通的情况还是选择notify更容易一些。

首先,我们来建立一个表示任务的实体类:

public class Task {
	private String name;
	private boolean isDownload;
	private int progress;

	public Task() {}

	public Task(String name) {
		this.name = name;
	}

	...
	setter and getter
	...
}

很容易理解,name表示任务的标题, isDownload表示是否正在下载,我们下面会通过isDownload来控制ProgressBar的显示和隐藏,progress当然就是进度了。

ListView的布局我们来看一下:

<ListView
        android:id="@+id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:cacheColorHint="@android:color/transparent"
        android:divider="@android:color/darker_gray"
        android:dividerHeight="1dp"
        android:listSelector="@android:color/transparent" />

还有ListView的Item的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/item_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <ProgressBar
        android:id="@+id/item_progress"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="invisible" />

</LinearLayout>

都是最简单的布局,我们直接略过,Item的布局包含一个标题和一个进度条。

如何来模拟下载过程呢? 看下面代码:

private void download(final int positionInAdapter) {
	...
	new Thread(new Runnable() {
		@Override
		public void run() {
			for (int i = 1; i < 101; i++) {
				final int progress = i;
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						publishProgress(positionInAdapter, progress);
					}
				});
				SystemClock.sleep(500);
			}
		}
	}).start();
}

这段代码的意思是每隔500ms更新一下进度,而positionInAdapter这个参数表示我们点击的这个item在Adapter中的位置(也就是在数据集中的位置)。

在来看看Adapter怎么写的,

public class MyAdapter extends BaseAdapter {
	private Context mContext;
	private ArrayList<Task> mTasks;

	public MyAdapter(Context context, ArrayList<Task> tasks) {
		mContext = context;
		mTasks = tasks;
	}

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

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

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		final ViewHolder holder;
		if(convertView == null) {
			convertView = View.inflate(mContext, R.layout.item, null);
			holder = new ViewHolder();
			holder.name = (TextView) convertView.findViewById(R.id.item_name);
			holder.progress = (ProgressBar) convertView.findViewById(R.id.item_progress);
			convertView.setTag(holder);
		}else {
			holder = (ViewHolder) convertView.getTag();
		}

		holder.name.setText(mTasks.get(position).getName());

		if(mTasks.get(position).isDownload()) {
			holder.progress.setVisibility(View.VISIBLE);
			holder.progress.setProgress(mTasks.get(position).getProgress());
		}else {
			holder.progress.setVisibility(View.INVISIBLE);
		}

		return convertView;
	}

	static class ViewHolder {
		TextView name;
		ProgressBar progress;
	}
}

一个最普通的Adapter写法,稍微值得注意的是40~45行,我们根据isDownload来判断是否显示和更新ProgressBar。

到这里我们的基本工作就算做完了,下面就是要在Activity中来更新ListView的item了,而不是通过notifyDatasetChanged方法。

在Activity中,我们通过ListView的ItemClick来实现模拟下载,在ItemClick中我们调用download方法,来看看download中我们省略的那些代码。

private void download(final int positionInAdapter) {
	mTasks.get(positionInAdapter).setDownload(true);
	if(positionInAdapter >= mListView.getFirstVisiblePosition() &&
			positionInAdapter <= mListView.getLastVisiblePosition()) {
		int positionInListView = positionInAdapter - mListView.getFirstVisiblePosition();
		ProgressBar item = (ProgressBar) mListView.getChildAt(positionInListView)
				.findViewById(R.id.item_progress);
		item.setVisibility(View.VISIBLE);
	}
	...
}

上来,我们先去更新一下task列表中表示下载的boolean变量,表示该条数据正在下载。

下面一个判断是关键,我们需要判断当前点击该条item是否在ListView的可见域内(这个判断其实是多余的,既然能点击到,肯定是可见了,但是为了严谨,我们还是加了这个判断),这里为什么要这么判断呢?来看下图。

在该图中,我们ListView第一个可见项对应在数据集中的位置应该是2,但是0和1是不可见的。所以,我们只需要判断一下,当前数据集中的位置是否在ListView.getFirstVisibleItem()和ListView.getLastVisibleItem()之间就可以判断出该item是否处于可见区域内了。

接下来第5行,我们通过positionInAdapter - mListView.getFirstVisibleItem()来获取当前item在ListView中的位置,如果我们点击对应图片中的4号位置,那么我们需要更新ListView中第4-2=2的item。现在我们又可以获取ListView的Item了,当然我们就可以获取该Item中的ProgressBar了,6~8行,我们精确的获取到了ProgressBar,并且更新ProgressBar为显示状态。

到目前为止,可以通过ItemClick来实现控制ProgressBar的显示了,当然,我们没有使用notifyDatasetChanged()。

接下来,我们来看看是如果更新进度的。更新进度的过程主要在publishProgress方法中。

public void publishProgress(final int positionInAdapter, final int progress) {
//	mTasks.get(positionInAdapter).setDownload(true);
	mTasks.get(positionInAdapter).setProgress(progress);

	if(positionInAdapter >= mListView.getFirstVisiblePosition() &&
			positionInAdapter <= mListView.getLastVisiblePosition()) {
		int positionInListView = positionInAdapter - mListView.getFirstVisiblePosition();
		ProgressBar item = (ProgressBar) mListView.getChildAt(positionInListView)
				.findViewById(R.id.item_progress);
		item.setProgress(progress);
	}
}

代码和上面的非常相似,其实原理也是一样的。

首先来看看这个方法的参数:positionInAdapter表示在数据集中的位置,progress表示进度,这个不难理解。

接下来,我们通过更新task任务列表中对应条目的progress来保存一下现在的进度,但是,我们没有notify。

接下来还是同样的逻辑,只不过,我们把控制ProgressBar显示的部分替换成了更新ProgressBar进度了。

这样,我们就实现了,在不调用notifyDatasetChanged()的情况下来更新ListView的Item的目的。这样做有一个很明显的好处就是,每次,我们只更新一条item,其他的item我们并没有去更新,而notifyDatasetChanged的实现方式是,保存当前的位置,并更新所有的item,然后恢复位置。这样一比较,我们这种方式的优势就体现出来了。

有人可能还会有疑问,我们在滑动的时候怎么处理呢? 别忘了,我们在更新的前面都在task列表中更新了数据,所以在滑动的时候,我们交给Adapter.getView()去处理了。

来看一下效果,结束本篇博客。

时间: 2024-11-05 02:41:17

ListView混搭ProgressBar最佳更新方式的相关文章

ListView嵌套ProgressBar更新方式

很多时候我们都通过BaseAdapter.getView()中的convertView来提高ListView的性能,这个时候如果我的的ListView的Item里有一个正在更新ProgressBar,结果就悲惨了... 滑动界面时并没有达到我们想要的效果.解决这个问题其实很容易,在数据集中保存一下更新的进度,然后在getView中不断去设置进度. 还有一个问题就是,当有进度更新的时候,我们是要不断mAdapter.notifyDatasetChanged()来更新ListView吗?这样做当然可

[转]Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件

Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件. 1.为了防止拖动ListView时,在列表末尾重复数据显示.需要加入 HashMap<Integer,View> lmap = new HashMap<Integer,View>();其中Integer为列表位置,View为子项视图,加入数据前首先if (lmap.get(position)==null) ,满足条件时,加入lmap.put(position, convertView

剖析微软Hyper-V的最佳部署方式

剖析微软Hyper-V的最佳部署方式 2014-04-24 10:53 布加迪编译 51CTO.com 字号:T | T 微软Hyper-V有两种不同的版本.既可以安装到Windows Server的上面,又可以作为一种独立式虚拟机管理程序来下载和安装.那么,哪种方法更好呢?本文中将权衡每种部署方式的优缺点. AD:51CTO网+ 首届中国APP创新评选大赛火热招募中…… [51CTO精选译文]最近,人们似乎重新对微软的Hyper-V产生了兴趣.这种虚拟机管理程序终于开始成熟起来,它所提供的功能

ListView 的三种数据绑定方式

ListView 的三种数据绑定方式 1.最原始的绑定方式: public ObservableCollection<object> ObservableObj; public MainWindow() { InitializeComponent(); ObservableObj = new ObservableCollection<object>(); ObservableObj.Add(new { Name = "帅波", Sex = "男&quo

SQLite复杂表的更新方式

SQLite复杂表的更新方式 在SQLite中,如果早期设计的表无法满足需要,就需要对表进行更新,如修改名字.添加列.如果针对简单表,修改起来相对容易,直接使用提供的ALTER命令即可.但是如果该表与视图.触发器.索引有关联,处理起来就相对繁琐一些.下面讲解这类表的修改方式. (1)使用以下命令,备份视图.触发器的SQL脚本. SELECT  sql FROM  main.sqlite_master WHERE type='view' or type='trigger' (2)关闭外键约束. P

探寻ASP.NET MVC鲜为人知的奥秘(3):寻找多语言的最佳实践方式

如果你的网站需要被世界各地的人访问,访问者会使用各种不同的语言和文字书写习惯,那么创建一个支持多语言的网站就是十分必要的了,这一篇文章就讲述怎么快速合理的创建网站对多语言的支持.接下来通过一个实例来讲述实践方式. 首先创建一个ASP.NET MVC5应用程序,命名为Internationalization: 然后在Models中添加一个示例的模型类: public class Employee { [Display(Name = "Name", ResourceType = typeo

Puppet 更新方式

基于C/S架构的Puppet更新方式一般有两种,一种是Agent端设置同步时间主动去Puppet Master端拉取配置,另一种是通过Puppet Master端使用puppet kick命令或者借助mcollctive触发更新配置. 1.主动更新 优点: 节点定期主动更新,无论是谁将节点被puppet管理的配置更改了,都会在规定的时间内自动修复,无须管理员登录查看. 环境搭建简单,不需要很复杂的架构,puppet本身C/S架构便可以完成. 缺点: 节点数量过大的情况下同时会向puppetmas

Android cookies正确的更新方式

之前的更新方式 一搜cookies的使用,很容易搜到很多文章,基本的几步大致相同,如下图: 基本上都要求大家先调用cookieManager.removeAllCookie()或者调用 cookieManager.removeSessionCookie(),这样确实每一次的setCookie都能写入新的cookie,简单粗暴有效. 遇到的问题 大家看setCookies的方法: /** * Sets a cookie for the given URL. Any existing cookie

Win10系统将启用更智能的更新方式

使用Win10系统的你肯定遇到过在工作时开始自动更新而不得不搁置工作的情况,想必你也已经被Win10系统的自动更新折磨不已,不过这种情况将会马上得到改观.微软现在已经开始寻找更智能的版本升级更新方式,其新开发的预测模型将尝试确定你是否正在工作,以确保在不中断工作的情况下提供更新,来消除这种恼人的行为.之前微软发布新版本后,其系统升级更新习惯对于用户来说是一种痛苦,但微软向用户保证,它已经知道了用户这方面的困扰,并正在努力的寻找答案. 新的预测模型,新的升级更新方式 在创作者版本的更新中,它将试图