Get rid of reinstallation in ListView with background for each item

I work with ListViewwhere list items have a background resource. I want to get rid of this overflow as much as I can. I know about the “Case Case Study” on the Romain Guy blog, but it's hard for me to get the list view fully optimized. Code for a simplified example is shown at the bottom of this post. As an example, we can only cite the “new activity” master with a list toss. My question is based on this example. Here is a screenshot with the inscription and without the crossed out marking of the initial, non-optimized case:

Naive case: 2x-3x overdraw for the entire screen

Make sure that the page has a gray background (this is a texture in my real project) and the list items have a white background (these are nine patches in my real project). Overdraw is dramatic, no part of the screen is drawn only once, and list items are displayed three times before they display the letter of content.

It’s easy to get rid of the background representation of the decor in Windowand cut out the full overdraw layer. If the list contains enough elements to fill the entire screen, I can also lower the background ListViewand get to a very good place:

Optimized case: backgrounds gone, almost no overdraw

, , , . , . ( ), , :

Deoptimized case: handles partial lists correctly at the cost of some overdraw

, , . , overdraw?

, :

:

//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" />
+5
2

, j__m answer. , ListView. , WRAP_CONTENT , ListView getView(...) .

, ListView#onMeasure() , , , , getView() onDraw(), . , Romain Guy , 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 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;
    }
}
+3

ListView LinearLayout, ListView WRAP_CONTENT layout_weight = "1". .

+2

All Articles