Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalidate() doesn't actually redraw children

So, I have a pretty weird setup, and I'm getting some weird visual bugs out of it. Basically, I have two views in a relative layout: The first is just an ImageView background image; the second is that same background image but blurred to give a kind of behind-frosted-glass effect:

<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" />

...

The blurred background should only be behind the HalvedLinearLayout_ view, which scrolls up, so I need the blurred background to mask itself above HalvedLinearLayout_ so the non-blurry background shows through:

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();
}

Now, the problem is that everything works as it should, EXCEPT for the huge glaring fact that when you scroll the ScrollView, the blurredBackground actually draws OVER the HalvedLinearLayout_, obliterating the Buttons in it. (but only halfway, sometimes. Sometimes it will be fine, sometimes half the button will be drawn over and the other half preserved... all kinds of weird glitches.)

I started debugging, and I noticed something interesting: calling invalidate() on the rootView doesn't actually invalidate() all the children. I overrode all the invalidate and draw functions with some logging stuff:

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();
    }

And now when I look at the log, the rootView.invalidate() is firing when I scroll, but the children never redraw:

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

How do I force the children to redraw, IN THE CORRECT ORDER? Right now I'm guessing they draw out of order and that's why the blurry background is drawing on top of everything else.

Here's a screenshot of the glitch:

screenshot of glitch: see the blurred background masked correctly, but drawing OVER the HalvedLinearLayout_

like image 615
phreakhead Avatar asked Jul 23 '13 19:07

phreakhead


2 Answers

I ran into this same issue with invalidate(), and since there isn't a good answer elsewhere on SO, I figured I'd post my solution.

This is a recursive function that will loop through all of the children views of a ViewGroup. If the child is a ViewGroup, it will recurse and search the child. Otherwise, it will call invalidate() on the child (this is the termination case).

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();
    }
}

The parameter layout should be able to be anything of type ViewGroup (LinearLayout, RelativeLayout, etc.) making this a pretty general solution.

It's working for my purposes, but I haven't put it to thorough testing. If there are any mistakes, please comment and let me know.

like image 102
Bob Liberatore Avatar answered Nov 16 '22 22:11

Bob Liberatore


Ok, I figured out how to get rid of the glitching, at least: all I had to do was change "Region.Op.REPLACE" with "Region.Op.INTERSECT" and now it works!

Invalidate() still doesn't redraw the children, but I guess I'll let that slide now that it's working.

like image 45
phreakhead Avatar answered Nov 16 '22 23:11

phreakhead