Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
236 views
in Technique[技术] by (71.8m points)

android - Custom Button background is being drawn wrong (sometimes)

I have this custom button, which does not draw himself and has no child views. Right after app launch it looks like this:

enter image description here

At this point I don't know what code and details might be relevant for posting here. The fact is after the app changes state the button checks whether to stay VISIBLE or go INVISIBLE. It remains VISIBLE. It calls setVisibility(View.VISIBLE) and after that, when the screen shows again, it looks like this:

enter image description here

If I click the button it returns to be fine with its original background dimensions.

What have I done so far
I have debugged the code down to android sources.
First onDraw(); I only call there super.onDraw(); and it seems to deal only with the text and not the background, and if so, it works OK, since the text is still positioned and dimensioned as before.
Second onMeasure(); here too I only call super.onMeasure();; it is called several (11) times before showing for the first time and it is called 5 times after setVisibility(); it is not called at all when I click the button.
Third onTouchEvent(), called when I click the button. It sets a different color background for ACTION_DOWN and restores the original color background on ACTION_UP

@Override
protected void onDraw(Canvas canvas) {
    Log.d(TAG + " " + getText(), "+ onDraw(canvas:" + canvas + ")");
    super.onDraw(canvas);
    Log.d(TAG + " " + getText(), "- onDraw()");
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    Log.d(TAG + " " + getText(), String.format("+ onMeasure(widthMeasureSpec:%x, heightMeasureSpec:%x)", widthMeasureSpec,heightMeasureSpec));
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    Log.d(TAG + " " + getText(), String.format("- onMeasure(): width=%d, hieght=%d", getMeasuredWidth(), getMeasuredHeight()));
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.d(TAG + " " + getText(), "+ onTouchEvent(event:" + event + ")");
    super.onTouchEvent(event);

    if( clickable ) {
        if( event.getAction() == MotionEvent.ACTION_UP ) {
            setBackgroundDrawable(normalBackground);
            clickUp.soundPlay();
        } else if( event.getAction() == MotionEvent.ACTION_DOWN ) {
            setBackgroundDrawable(pressedBackground);
            clickDown.soundPlay();
        }
    }

    Log.d(TAG + " " + getText(), "- onTouchEvent()");
    return true;
}

/**
 * Sets the MyButton visible if stateFlags matches.<br>
 * @param stateFlags The current app state.<br> 
 */
public void setState(int stateFlags) {
    Log.d(TAG + " " + getText(), "+ setState(stateFlags:" + stateFlags + ")");
    if( state == stateFlags || state == State.NORMAL) {
        setVisibility(View.VISIBLE);
        Log.d(TAG + " " + getText(), "state(" + state + ") VISIBLE before was " + getVisibility());
    } else {
        setVisibility(View.INVISIBLE);
        Log.d(TAG + " " + getText(), "state(" + state + ") INVISIBLE before was " + getVisibility());
    }
    requestLayout();
    Log.d(TAG + " " + getText(), "- setState()");
}

@Override
protected int[] onCreateDrawableState(int extraSpace) {
    Log.d(TAG + " " + getText(), "+ onCreateDrawableState(extraSpace:" + extraSpace + ")");
    Log.d(TAG + " " + getText(), "- onCreateDrawableState()");
    return super.onCreateDrawableState(extraSpace);
}

these are the logs for the button:

*** Beggining - first show ***
+ onMeasure(widthMeasureSpec:800002b8, heightMeasureSpec:800003aa)
- onMeasure(): width=200, hieght=125
+ onMeasure(widthMeasureSpec:40000099, heightMeasureSpec:800003aa)
- onMeasure(): width=153, hieght=125
+ onMeasure(widthMeasureSpec:800002b8, heightMeasureSpec:80000152)
- onMeasure(): width=200, hieght=125
+ onMeasure(widthMeasureSpec:40000099, heightMeasureSpec:80000152)
- onMeasure(): width=153, hieght=125
+ onMeasure(widthMeasureSpec:800002b8, heightMeasureSpec:80000035)
- onMeasure(): width=200, hieght=53
+ onMeasure(widthMeasureSpec:40000099, heightMeasureSpec:80000035)
- onMeasure(): width=153, hieght=53
+ onSizeChanged(w:153, h:53, oldw:0, oldh:0)
- onSizeChanged()
+ onLayout(changed:true, left:12, top:3, right:165, bottom:56)
- onLayout()
+ onMeasure(widthMeasureSpec:800002b8, heightMeasureSpec:80000035)
- onMeasure(): width=200, hieght=53
+ onMeasure(widthMeasureSpec:40000099, heightMeasureSpec:80000035)
- onMeasure(): width=153, hieght=53
+ onLayout(changed:false, left:12, top:3, right:165, bottom:56)
- onLayout()
+ onDraw(canvas:android.view.Surface$CompatibleCanvas@b3e434b0)
+ onCreateDrawableState(extraSpace:0)
- onCreateDrawableState()
- onDraw()

*** App changes state - button shows wrong ****
+ setState(stateFlags:2)
state(1) VISIBLE before was 0
- setState()
+ onMeasure(widthMeasureSpec:800002b8, heightMeasureSpec:80000152)
- onMeasure(): width=200, hieght=125
+ onMeasure(widthMeasureSpec:800002b8, heightMeasureSpec:80000035)
- onMeasure(): width=200, hieght=53
+ onMeasure(widthMeasureSpec:40000099, heightMeasureSpec:80000035)
- onMeasure(): width=153, hieght=53
+ onLayout(changed:false, left:12, top:3, right:165, bottom:56)
- onLayout()
+ onDraw(canvas:android.view.Surface$CompatibleCanvas@b3e434b0)
- onDraw()

*** I am about to click the button, return to show fine ***
+ onTouchEvent(event:MotionEvent { action=ACTION_DOWN, id[0]=0, x[0]=61.0, y[0]=36.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=2879242, downTime=2879242, deviceId=0, source=0x1002 })
- onTouchEvent()
+ onDraw(canvas:android.view.Surface$CompatibleCanvas@b3e434b0)
+ onCreateDrawableState(extraSpace:0)
- onCreateDrawableState()
- onDraw()
+ onTouchEvent(event:MotionEvent { action=ACTION_UP, id[0]=0, x[0]=61.0, y[0]=36.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=2879342, downTime=2879242, deviceId=0, source=0x1002 })
- onTouchEvent()
+ performClick()
- performClick()
+ onDraw(canvas:android.view.Surface$CompatibleCanvas@b3e434b0)
+ onCreateDrawableState(extraSpace:0)
- onCreateDrawableState()
- onDraw()

More details will be posted on demand, if deemed relevant.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I found a bypass!, though I would like very much to understand what is really the problem.

The bypass is:

@Override
protected void onDraw(Canvas canvas) {
    // This is a bypass for the problem of partial background redraw.
    // The problem causes are not understood yet.
    // But setting mBackgroundSizeChanged = true; in View causes the next Draw to be OK
    onScrollChanged(0, 0, 0, 0);
    super.onDraw(canvas);
}

The ultimate bypass line is (View.class):

mBackgroundSizeChanged = true;

But mBackgroundSizeChanged is not accessible from derived classes, and it has no setter per se. So I found the closest thing to a setter there is: onScrollChanged(); it does set mBackgroundSizeChanged = true and in my case is all it does. Check the few source code lines in TextView.class and View.class to see if in your case it does other stuff.

Old Bypass (still works but executes more unecessary lines):

Right after I change the visibility of a button I added these lines:

Drawable d = cb.getBackground();
cb.setBackgroundDrawable(null);
cb.setBackgroundDrawable(d);

And it solved the problem, now the button retains its background.

Explanation how I got there
This forces the button background to reset itself. I did look at the source of setBackgroundDrawable() trying to understand what code in there is making the difference. I first noticed that had to set the background to null first and reset it afterwards to force a redo.
Further debug reduced the difference to:

mBackgroundSizeChanged = true; 

In the lasts code lines of setBackgroundDrawable()


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...