天天看点

六种方式让你的List滑动更流畅

看到一篇关于不错的文章,简单翻译了下。原文地址:http://leftshift.io/6-ways-to-make-your-lists-scroll-faster-than-the-wind

快乐与悲伤共存。开发一款App总是很有趣,然而比较伤心的是拿着一款旧的三星测试机的你,得到一款新的MotoX却发现你的App在上面运行的总是不流畅。第一眼看到App如此卡顿,我真的流泪了。此时尽管夜已深,我依然决定找出fix的办法。

以下是我找出一些条目:

1>:在Adapter中getView方法里面,减少条件判断;

2>:减少GC操作;

3>:避免在滑动时加载图片;

4>:将scrollingCache跟animateCache设置为false;

5>:简化你的list View布局层次;

6>:使用ViewHolder模式;

如果这些你全都熟悉,可能你没有必要再读下文了。不管怎样,如果还不是完全了解,请接着往下看。(这里给出一些建议。这里给出的方法唯一的解决方案。有些时候它能够起作用,有时需要你尝试其它方案。这些都取决于你的代码。写这些方案的目的是帮助你的App更流畅)

1:在Adapter中getView方法里面,减少条件判断

(在你的Adapter中,可能需要清理某些状态信息。每次当你的屏幕上需要显示一项时这个方法都会被调用。因此这里定义的结构集,在每次App运行时都会进行大量调用。因此此处减少条件判断比较有意义。现在问题是不能在这里做,在哪儿又比较合适呢?答案很简单,如果你正在使用一个可序列化的类来获取其值,你只需要将条件判断移动到类里面,将值存在里面即可。通过这种方式,条件判断仅仅只需执行一次,下面是一个简单的例子)

这里是处理冗余之前的View代码(这里有很复杂的条件判断,我们移除了条件判断,使其更便于理解)

@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
    Object current_event = mObjects.get(position);
    ViewHolder holder = null;
    if (convertView == null) {
        holder = new ViewHolder();
        convertView = inflater.inflate(R.layout.row_event, null);
        holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
        holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
        convertView.setTag(holder);
    } else {
            holder = (ViewHolder) convertView.getTag();
    }

    // If you have conditions like below then you may face problem
    if (doesSomeComplexChecking()) {
            holder.ThreeDimention.setVisibility(View.VISIBLE);
    } else {
            holder.ThreeDimention.setVisibility(View.GONE); 
    }

    // This method is setting layout parameters each time when getView is getting called which is wrong.
    RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
    holder.EventPoster.setLayoutParams(imageParams);

    return convertView;
}
           

下面是更新后的getView代码:

@Override
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
    Object current_event = mObjects.get(position);
    ViewHolder holder = null;

    if (convertView == null) {
        holder = new ViewHolder();
        convertView = inflater.inflate(R.layout.row_event, null);
        holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
        holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
        // This will now execute only for the first time of each row
        RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
        holder.EventPoster.setLayoutParams(imageParams);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    // This way you can simply avoid conditions
    holder.ThreeDimension.setVisibility(object.getVisibility());

    return convertView;
}
           

2:检查和减少你logs中GC的警告

当大量的创建跟销毁对象时,GC将频繁的出现。因此一般情况在getView中不创建大量的对象;更好的建议是-除了ViewHolder不创建任何对象。如果在Log中频繁出现“GC已经释放了内存”,那说明你的代码中存在严重的问题,你有必要按照下面的方法检查下:

1>:列表中每项的布局层次

2>:getView中的条件判断是否较多

3>:每项View的属性

3:避免在滑动的时候加载图片

当用户快速滑动的时候,应用程序正在下载图片,此时应该停止加载图片,保证滑动的流畅性;当滑动停止时再加载图片。这种方式的效果很明显。

同时,Google也给出了示例代码,参考https://code.google.com/p/iosched/ 

对于那些讨厌打开连接并下载代码的人,下面是一小段代码,代码说明了如何添加一个scroll listener来stop跟start加载图片队列

<span style="font-size:18px;">listView.setOnScrollListener(new OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView listView, int scrollState) {
        // Pause disk cache access to ensure smoother scrolling
        if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
            imageLoader.stopProcessingQueue();
        } else {
            imageLoader.startProcessingQueue();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // TODO Auto-generated method stub
    }
});</span>
           

4:将scrollingCache跟animateCache设置为false

ListView有很多的属性,以下是一些详细的说明,参考:http://stackoverflow.com/questions/15570041/scrollingcache

scrolling cache是一个基本的drawing cache

在Android中,你可以请求一个View将正在绘制的缓存存储在drawing cache中(比如Bitmap)。通常,drawing cache不可用,因为它会占用内存;但是你也可以通过setDrawingCacheEnabled或者hardware layers(setLayerType)来请求创建一个drawing cache。

为什么这个比较有用?因为在View框架中,使用drawing cache能够让你的View得以比较平滑的绘制。

这类动画也能够被硬件加速,因为渲染系统能够获取该Bitmap并且作为texture(如果使用了硬件层)传递给GPU,能够做更快的矩阵操作(比如:改变alpha值、旋转、移动)。

在ListView中,当你快速滑动时,其实本质上是对ListView中View做动画效果(不管是向上还是向下移动),ListView对可见的子View(靠近边缘的可见子View)使用drawing cache使其能够更快的进行动画操作。

使用drawing cache有什么缺点吗?答案是的,由于使用drawing cache需要消耗内存,这就是为什么默认是关闭的原因。对于ListView来说,只要你Touch 或者移动一点点(跟滑动做区别),缓存就自动创建了。换句话说,当ListView认为你要滑动 or 快速滑动时,它将为你创建一个scrolling cache来确保更快的scroll or fling 动画移动。

同样,有时AnimationCache也有不好的地方(个人认为它导致了频繁的GC)

对于子View,布局动画应该创建一个drawing cache。尽管动画缓存会占用更多的内存且需要更长初始化,但是它提供了更好的性能。动画缓存默认可用。动画缓存参考Google文档。

这里是我们将要在ListView中修改的。原始代码如下:

<ListView
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:divider="@color/list_background_color"
    android:dividerHeight="0dp"
    android:listSelector="#00000000"
    android:smoothScrollbar="true" />
           

下面是修改后的代码:

<ListView
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:divider="@color/list_background_color"
    android:dividerHeight="0dp"
    android:listSelector="#00000000"
    android:smoothScrollbar="true"
    android:scrollingCache="false"
    android:animationCache="false" />
           

5:简化LIstView每项布局的层次

你需要保证列表view布局尽可能的小。布局的层级跟深度直接跟view的测量、绘制相关(可以使用Hierarchy Viewer工具查看)。后续博客可能会继续讨论该知识点。

6:使用view holder模式

对于初级开发者,这是一个经常犯的错误。第一次你的App表现正常,当快速滑动时,你的App将crash,出现这个问题的原因是因为,当你快速滑动时,其View每次都需要inflate,导致性能降低。正确的做法是使用VIew Holder模式,将View存储起来。具体参考文章1,文章2。

如果有其它技巧,欢迎补充。谢谢~~~