欢迎加入程序员,开发者交流裙: 188168040
在上一课中有介绍一系列的BitmapFactory.decode*) 方法,当数据源是网络或者是磁盘时(或者是任何实际源不在内存的),这些方法都不应该在main UI 线程中执行。那些情况下加载数据是不可以预知的,它依赖于许多因素(从网络或者硬盘读取数据的速度, 图片的大小, CPU的速度, etc.)。如果其中任何一个任务卡住了UI thread, 系统会出现ANR的错误。
这一节课会介绍如何使用 AsyncTask 在后台线程中处理bitmap并且演示了如何处理并发(concurrency)的问题。
今天我们分享一个关于CursorLoader和ListView的一个技巧。
首先简单介绍一个CursorLoader。
Android 3.0之后引入了CursorLoader,CursorLoader是目前Android唯一实现的一个Loader(5.0当中是否加入了其他的Loader类型,我还没有研究过)。也就是现在Google提供的只是可以返回Cursor类型的Loader。
由于CursorLoader内部集成了ContentObserver,所以CursorLoader在我们底层的ContentProvider数据发生改变的时候来通知我们数据发生了改变,然后在Client端的我们(一般就是Activity或者Fragment)就会知道数据发生了改变。好了,基本的概念熟悉了。现在我们讨论一个具体的技巧。如果我们现在开发一款类似于新浪微博的程序,当前用户查看的他所关注的用户发的帖子,都会先从WebService上获取下来,然后存储到本地的我们自己建立的SQLite当中,然后通过在这个SQLite上面封装的ContentProvider我们就可以读取到更新好的用户数据,而且这样的话,即使用户在离线的环境下使用这个程序也可以浏览之前缓存好的数据。使程序稳定。
现在我们通过借助CursorLoader,每当ContentProvider当中的数据发生了改变,就会自动通知我们数据发生了改变。然后我们就可以更新ListView了(这里我们假设我们保存用户的发出状态信息以ListView展示出来)。
但是问题往往出现在细节上面。这里的问题就出现在ListView上面。当我们使用返回的Cursor当中所包含了最新的数据时,我们希望把这些最新的数据显示到整个ListView的顶部。通常我们会使用setSelectionFromTop()
来使ListView保持在同样的可见的位置。但是ListView还是会改变位置,并且有时候还会闪一下。具体的效果你们可以感受以下。发生这种情况的原因就是ListView在旧的位置显示我们最新或得到的数据(具体点说,就是在当前的ListView的position-2上显示了我们最新获得的数据,但是原来的position-2上面的数据就被挤掉了。这样一来就会发生闪烁的问题)。解决的办法就是在我们的ListView加载各个child的时候适当的将ListView锁住。
即如果我们通过阻止ListView的children的layout的过程,就能阻止每一个item上面的闪烁的问题。
以下是具体的代码:
public class BlockingListView extends ListView
{
private boolean mBlockLayoutChildren;
public BlockingListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public void setBlockLayoutChildren(boolean block)
{
mBlockLayoutChildren = block;
}
@Override
protected void layoutChildren()
{
if (!mBlockLayoutChildren)
{
super.layoutChildren();
}
}
}
复制代码
以下就是具体在我们的代码当中使用的过程:
int firstVisPos = mListView.getFirstVisiblePosition();
View firstVisibleItem = mListView.getChildAt(0);
int top = firstVisibleItem != null ? firstVisibleItem.getTop() : 0;
// 将child layout pass锁住
// 将ListView加锁,注意我们在后面还要解锁
mListView.setBlockLayoutChildren(true);
// 用于加到整个ListView最顶部的最新的数据的条数
int itemsAddedBeforeFirstVisible = ...;
// 更新Cursor,或者如果不使用Cursor的话,这里调用的就是我们很熟悉的notifyDataSetChanged()
mAdapter.swapCursor(...);
// 将ListView解锁
// 即让ListView可以进行ListView的children item的layout pass过程
mListView.setBlockLayoutChildren(false);
// 通过调用setSelectedFromTop来改变ListView的position
mListView.setSelectionFromTop(firstVisPos + itemsAddedBeforeFirstVisible, top);
复制代码
好了,现在就可以解决闪烁的问题了。
上面的解决办法不止对于CursorLoader加载数据到ListView当中有帮助,即使我们只是
简单的将网络数据获取到,然后直接加载到ListView当中进行显示也是同样的道理。
我们只需要适当的控制ListView的子item的layout pass就可以解决了。
写的有点乱,希望能帮助到你.
在上一课中有介绍一系列的BitmapFactory.decode*) 方法,当数据源是网络或者是磁盘时(或者是任何实际源不在内存的),这些方法都不应该在main UI 线程中执行。那些情况下加载数据是不可以预知的,它依赖于许多因素(从网络或者硬盘读取数据的速度, 图片的大小, CPU的速度, etc.)。如果其中任何一个任务卡住了UI thread, 系统会出现ANR的错误。
这一节课会介绍如何使用 AsyncTask 在后台线程中处理bitmap并且演示了如何处理并发(concurrency)的问题。
今天我们分享一个关于CursorLoader和ListView的一个技巧。
首先简单介绍一个CursorLoader。
Android 3.0之后引入了CursorLoader,CursorLoader是目前Android唯一实现的一个Loader(5.0当中是否加入了其他的Loader类型,我还没有研究过)。也就是现在Google提供的只是可以返回Cursor类型的Loader。
由于CursorLoader内部集成了ContentObserver,所以CursorLoader在我们底层的ContentProvider数据发生改变的时候来通知我们数据发生了改变,然后在Client端的我们(一般就是Activity或者Fragment)就会知道数据发生了改变。好了,基本的概念熟悉了。现在我们讨论一个具体的技巧。如果我们现在开发一款类似于新浪微博的程序,当前用户查看的他所关注的用户发的帖子,都会先从WebService上获取下来,然后存储到本地的我们自己建立的SQLite当中,然后通过在这个SQLite上面封装的ContentProvider我们就可以读取到更新好的用户数据,而且这样的话,即使用户在离线的环境下使用这个程序也可以浏览之前缓存好的数据。使程序稳定。
现在我们通过借助CursorLoader,每当ContentProvider当中的数据发生了改变,就会自动通知我们数据发生了改变。然后我们就可以更新ListView了(这里我们假设我们保存用户的发出状态信息以ListView展示出来)。
但是问题往往出现在细节上面。这里的问题就出现在ListView上面。当我们使用返回的Cursor当中所包含了最新的数据时,我们希望把这些最新的数据显示到整个ListView的顶部。通常我们会使用setSelectionFromTop()
来使ListView保持在同样的可见的位置。但是ListView还是会改变位置,并且有时候还会闪一下。具体的效果你们可以感受以下。发生这种情况的原因就是ListView在旧的位置显示我们最新或得到的数据(具体点说,就是在当前的ListView的position-2上显示了我们最新获得的数据,但是原来的position-2上面的数据就被挤掉了。这样一来就会发生闪烁的问题)。解决的办法就是在我们的ListView加载各个child的时候适当的将ListView锁住。
即如果我们通过阻止ListView的children的layout的过程,就能阻止每一个item上面的闪烁的问题。
以下是具体的代码:
public class BlockingListView extends ListView
{
private boolean mBlockLayoutChildren;
public BlockingListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public void setBlockLayoutChildren(boolean block)
{
mBlockLayoutChildren = block;
}
@Override
protected void layoutChildren()
{
if (!mBlockLayoutChildren)
{
super.layoutChildren();
}
}
}
复制代码
以下就是具体在我们的代码当中使用的过程:
int firstVisPos = mListView.getFirstVisiblePosition();
View firstVisibleItem = mListView.getChildAt(0);
int top = firstVisibleItem != null ? firstVisibleItem.getTop() : 0;
// 将child layout pass锁住
// 将ListView加锁,注意我们在后面还要解锁
mListView.setBlockLayoutChildren(true);
// 用于加到整个ListView最顶部的最新的数据的条数
int itemsAddedBeforeFirstVisible = ...;
// 更新Cursor,或者如果不使用Cursor的话,这里调用的就是我们很熟悉的notifyDataSetChanged()
mAdapter.swapCursor(...);
// 将ListView解锁
// 即让ListView可以进行ListView的children item的layout pass过程
mListView.setBlockLayoutChildren(false);
// 通过调用setSelectedFromTop来改变ListView的position
mListView.setSelectionFromTop(firstVisPos + itemsAddedBeforeFirstVisible, top);
复制代码
好了,现在就可以解决闪烁的问题了。
上面的解决办法不止对于CursorLoader加载数据到ListView当中有帮助,即使我们只是
简单的将网络数据获取到,然后直接加载到ListView当中进行显示也是同样的道理。
我们只需要适当的控制ListView的子item的layout pass就可以解决了。
写的有点乱,希望能帮助到你.