【问题标题】:Getting rid of overdraw in a ListView with per-item backgrounds摆脱具有每个项目背景的 ListView 中的过度绘制
【发布时间】:2013-03-15 14:10:51
【问题描述】:

我正在使用ListView,其中列表项具有背景资源。我想尽可能多地摆脱透支。我知道 Romain Guy 博客上的 Performance Case Study 帖子,但我无法完全优化列表视图。本文底部显示了一个简化示例的代码。该示例基本上只是带有列表视图的“新活动”向导。我的问题建立在此示例之上。这是初始未优化案例的带有和不带有过度绘制标记的屏幕截图:

见证页面有灰色背景(在我的真实项目中是纹理),列表项有白色背景(在我的真实项目中是九个补丁)。过度绘制是戏剧性的,屏幕的任何部分都不会只绘制一次,列表项在显示内容字母之前会绘制三次。

很容易去掉Window中的decor view背景,剪掉一个完整的overdraw层。如果列表包含足够的项目来填满整个屏幕,我也可以删除ListView 的背景,以便找到一个非常好的地方:

不幸的是,当列表中的项目少于填满整个屏幕时,这不起作用。如果我没有设置任何背景,则列表项下方没有任何内容。如果我在列表视图(或任何父视图)上设置背景,我会为所有确实存在的列表项完全透支:

这是较小的邪恶,但令人不满意,因为大多数时候我的列表会从屏幕上消失。有没有一种可靠的方法可以在最后一项之后在列表视图中获取背景,而不会引入过度绘制?

本题使用的示例代码:

活动:

//MainActivity.java
public class MainActivity extends Activity {
    private static final String[] DATA = { "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", "Juliet" };

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Optimization 1: getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        ListView lv = (ListView) findViewById(android.R.id.list);
        lv.setAdapter(new ArrayAdapter<String>(this, R.layout.activity_main__list_item,
                    android.R.id.text1, data));
    }
}

布局:

<!-- layout/activity_main.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:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="#FFDDDDDD"
        android:text="Page header information" />
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#FFDDDDDD"
        android:divider="@null" />
</LinearLayout>

<!-- layout/activity_main__list_item.xml -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android" 
    android:id="@android:id/text1"
    android:layout_width="match_parent" 
    android:layout_height="48dp"
    android:background="@android:color/white" />

【问题讨论】:

    标签: android listview optimization


    【解决方案1】:

    将 ListView 放在 LinearLayout 中,将 ListView 的高度设置为 WRAP_CONTENT,并在其下方放置另一个 view,layout_weight="1"。将您的背景放在该视图上。

    【讨论】:

    • 如果只有 1 个项目怎么办。它不会填满整个屏幕吗?看起来很奇怪
    • 你把我弄糊涂了哈哈——除了列表项之外,你不是想让一个背景填满屏幕吗?您当前有一个 ListView 填充屏幕并在整个屏幕上绘制其背景,包括在子项下。我已经建议了如何让该背景仅在没有子项的地方绘制,从而避免过度绘制。
    • 我想这可行,但是,我记得在某处读过,如果项目不是固定高度,将 wrap_content 放在列表视图上是个坏主意。我不记得我在哪里读过它,让我检查一下。
    • optimizing layout 提到不要嵌套权重,但我不认为这是我之前读到的(这是我在实际项目中会遇到的)。
    • 您的解决方案有效(赞成),但我对嵌套权重的事情感到困扰。到目前为止,谢谢,但我会在接受之前继续寻找更长的时间。
    【解决方案2】:

    我想出了一个自己的解决方案,可以避免 j__m 的answer 的嵌套权重问题。不利的一面是,它不能轻松地与其他 ListView 自定义组合。对于列表视图的高度,这可能比涉及WRAP_CONTENT 的解决方案更有效,因为在测量期间ListView 不必getView(...) 其子级。

    当然,ListView#onMeasure() 实现足够聪明,一旦它有足够的高度来填充其父视图并且它在onDraw() 期间到达getView() 那些相同的位置,它就会停止测量子级,因此效率论点似乎是没有实际意义。尽管如此,Romain Guy 是 quite insistent,ListView 不应该使用 WRAP_CONTENT

    /**
     * A list view for use with list items that have their own background drawable. Such list views
     * suffer from GPU Overdraw of the item background on top of the list view (ancestor) background.
     * <p>
     * This subclass detects when its data set contains enough elements to fill all available space
     * and start scrolling. If this is the case, it sets its own background to transparent.
     * </p>
     * <p><strong>Limitation:</strong> Header and Footer views are ignored. If the list view has few
     * enough items that it wouldn't scroll, but a header and/or footer are big enough to cause it to
     * scroll anyway, the background is not hidden and overdraw is present.
     * </p>
     * <p>Source: https://stackoverflow.com/q/15625930/49489 CC-BY-SA</p>
     */
    public class BackgroundListView extends ListView {
    
        /** We need our own instance, because it's used as a sentinel value later on. */
        private static final Drawable TRANSPARENT = new ColorDrawable(0x00000000);
    
        /** If true, the next call to {@code onDraw(Canvas)} evaluates whether to show or hide the background. */
        private boolean mNeedsBackgroundCheck = true;
    
        /** The background to restore if the list shrinks. */
        private Drawable mOriginalBackground;
    
        public BackgroundListView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public BackgroundListView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public BackgroundListView(Context context) {
            this(context, null, 0);
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            this.mOriginalBackground = this.getBackground();
        }
    
        @Override
        public void setBackground(Drawable background) {
            super.setBackground(background);
            Drawable newBackground = getBackground();
            if (newBackground != TRANSPARENT) {
                this.mOriginalBackground = newBackground;
            }
        }
    
        @Override
        public void setBackgroundResource(int resid) {
            super.setBackgroundResource(resid);
            Drawable newBackground = getBackground();
            if (newBackground != TRANSPARENT) {
                this.mOriginalBackground = newBackground;
            }
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            if (mNeedsBackgroundCheck) {
                maybeHideBackground();
            }
            super.onDraw(canvas);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mNeedsBackgroundCheck = true;
        };
    
        @Override
        protected void handleDataChanged() {
            super.handleDataChanged();
            mNeedsBackgroundCheck = true;
        }
    
        private void maybeHideBackground() {
            if (isInEditMode()) {
                return;
            }
            final int maxPosition = getAdapter().getCount() - 1;
            final int firstVisiblePosition = getFirstVisiblePosition();
            final int lastVisiblePosition = getLastVisiblePosition();
            if (firstVisiblePosition > 0 || lastVisiblePosition < maxPosition) {
                setBackground(TRANSPARENT);
            } else {
                setBackground(mOriginalBackground);
            }
            mNeedsBackgroundCheck = false;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-20
      • 1970-01-01
      • 2015-01-17
      相关资源
      最近更新 更多