Invalidate() на самом деле не перерисовывает детей

Итак, у меня довольно странные настройки, и я получаю некоторые странные визуальные ошибки. По сути, у меня есть два вида в относительном макете: первый - это просто фоновое изображение ImageView; второе - это то же фоновое изображение, но размытое, что создает эффект замерзшего стекла:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/profile_holder"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <!-- background -->
    <ImageView
        android:id="@+id/backgroundImage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="0dp"
        android:layout_marginTop="0dp"
        android:scaleType="centerCrop"
        android:src="@drawable/default_logo" />

    <!-- blurry background -->
    <com.custom.MaskedBlurredBackgroundView_
        android:id="@+id/blurred_background"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="0dp"
        android:layout_marginTop="0dp" />

<ScrollView
    android:id="@+id/scrolly"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true" >

            <com.custom.HalvedLinearLayout_
                android:paddingTop="100dp"
                android:id="@+id/profileHolder"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                 >

                <Button
                    android:id="@+id/profileButtonTracks"
                    style="@style/ProfileButtons"
                    android:drawableLeft="@drawable/ic_profile_menu_music"
                    android:text="@string/profile_button_tracks" />

...

Размытый фон должен находиться только за представлением HalvedLinearLayout_, которое прокручивается вверх, поэтому мне нужно, чтобы размытый фон маскировался над HalvedLinearLayout_, чтобы не размытый фон просвечивал:

scrolly  = ((ScrollView) rootView.findViewById(R.id.scrolly));
scrolly.setOnScrollListener(new ScrollView.OnScrollListener() {

    private boolean hasScrolled = false;

    @Override
    public void onScrollChanged(int l, int t, int oldl, int oldt) {

        if (!hasScrolled) {
            hasScrolled = true;
            Logger.action(Logger.Action.BioView, band);
        }

        positionMask();
    }
});

...

protected void positionMask() {
    if (blurredBackground == null) return;

    Logger.d(TAG, "positionMask() " + rootView.getId());

    //blurredBackground.setMaskBottom(blurredBackground.getHeight() - profileHolder.getPaddingTop() - scrollY);
    blurredBackground.setMaskBottom(profileHolder.getTop() + profileHolder.getPaddingTop() - scrolly.getScrollY());
    //blurredBackground.invalidate();
    //profileHolder.invalidate();
    rootView.postInvalidate();
}

Теперь проблема в том, что все работает так, как должно, за исключением огромного очевидного факта, что когда вы прокручиваете ScrollView, blurredBackground фактически рисует НАД HalvedLinearLayout_, стирая в нем кнопки. (но только на полпути, иногда. Иногда это будет хорошо, иногда половина кнопки будет перетянута, а другая половина сохранена... все виды странных сбоев.)

Я начал отладку и заметил кое-что интересное: вызов invalidate() для rootView фактически не делает инвалидами () все дочерние элементы. Я переопределил все недействительные и нарисованные функции некоторыми вещами:

public class MaskedBlurredBackgroundView extends BlurredBackgroundView {
    /***
     * Sets the mask to cover everything above the given Y coordinate
     * @param t Y Coordinate in pixels
     */
    public void setMaskBottom(int y) {
        maskBottom  = y;
    }

    private void clipCanvas(Canvas canvas) {
        Logger.d(TAG, "dispatchDraw clipping: " + maskBottom + ", " + getWidth() + "," + getHeight());
        canvas.clipRect(0, maskBottom, getWidth(), getHeight(), Region.Op.REPLACE);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.save();
        clipCanvas(canvas);
        super.dispatchDraw(canvas);

        canvas.restore();
    }
    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        Logger.d(TAG, "drawChild: " + child + getWidth() + "," + getHeight());

        return super.drawChild(canvas, child, drawingTime);
    }

    @Override
    public void invalidate() {
        Logger.d(TAG, "invalidate: " + getWidth() + "," + getHeight());
        super.invalidate();
    }

И теперь, когда я просматриваю журнал, rootView.invalidate() запускается при прокрутке, но дети никогда не перерисовывают:

07-23 12:36:49.328: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.348: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.348: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.368: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.368: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.378: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.398: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.418: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.448: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.478: D/BandDetailFrag(12982): positionMask() 2131165298

Как заставить детей перерисовать, ПО ПРАВИЛЬНОМУ ПОРЯДКУ? Прямо сейчас я предполагаю, что они рисуют не по порядку, и поэтому размытый фон рисует поверх всего остального.

Вот скриншот глюка:

снимок экрана с глюком: вы видите размытый фон, замаскированный правильно, но рисуя над HalvedLinearLayout

2 ответа

Я столкнулся с этой же проблемой с invalidate()и поскольку в SO нет хорошего ответа, я решил опубликовать свое решение.

Это рекурсивная функция, которая будет перебирать все дочерние представления ViewGroup, Если ребенок ViewGroup, он вернется и обыщет ребенка. В противном случае, это вызовет invalidate() на ребенка (это случай прекращения).

public void invalidateRecursive(ViewGroup layout) {
    int count = layout.getChildCount();
    View child;
    for (int i = 0; i < count; i++) {
        child = layout.getChildAt(i);
        if(child instanceof ViewGroup)
            invalidateRecursive((ViewGroup) child);
        else
            child.invalidate();
    }
}

Параметр layout должно быть в состоянии быть что-нибудь типа ViewGroup (LinearLayout, RelativeLayoutи т. д.) делая это довольно общее решение.

Он работает для моих целей, но я не подверг его тщательному тестированию. Если есть какие-либо ошибки, пожалуйста, прокомментируйте и дайте мне знать.

Хорошо, я понял, как избавиться от сбоев, по крайней мере: все, что мне нужно было сделать, это заменить "Region.Op.REPLACE" на "Region.Op.INTERSECT", и теперь это работает!

Invalidate () по-прежнему не перерисовывает детей, но я думаю, что я позволю этому слайду теперь, когда он работает.

Другие вопросы по тегам