Frage

I have a custom View class in Android, which is to display nothing in the beginning, but only draw its content as soon as show() has been called on it.

I've checked this on Android 4.1 and 4.4, and it seems to work. However, some users with Android KitKat (e.g. on a Samsung Galaxy S4), have reported that nothing is ever drawn in this View. What could be the reason for that? (The full code can be found here.)

Usage in XML:

<com.my.package.MyView
    android:id="@+id/my_view"
    android:layout_width="48dp"
    android:layout_height="48dp" />

Usage in Java:

MyView myView = (MyView) findViewById(R.id.my_view);
myView.show(42);

Referenced class MyView in Java:

public class MyView extends View {

    public MyView(Context context) {
        super(context);
        init();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    protected void init() {
        setWillNotDraw(true);
    }

    public void show(int input) {
        // do some initialization so that onDraw() knows what to draw
        setWillNotDraw(false);
        // invalidate() doesn't seem to be necessary (no effect)
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // draw things on the canvas now
    }

}
War es hilfreich?

Lösung

After several more hours of debugging, I finally found the solution.

At first, I added this to my init() method (which is called from all constructors):

setWillNotDraw(false);

Android must know that this View must be rendered and drawn, which should be the default value, anyway. But if you extend ViewGroup, for example, it is not the default setting, so you have to clear that flag.

As others have noted, one should only call that method once, in order to set correct flag for the View. If you want to prevent drawing in the beginning, use a custom flag and only execute the statements in your onDraw(...) if appropriate. Thus I remove the second call to setWillNotDraw(...) from show(...).

In addition to that, I added a call to ...

invalidate();

... at the end of show(...), which is when I provide input to my custom View and it must be re-drawn.

However, the problem of the View not being drawn ("invisible" View) did only occur on certain devices. It was, for example, a Samsung Galaxy S4 (Android 4.4). When I tested it on the emulator with Android 4.4, the problem did not occur. The reason for that was actually simple:

View.isHardwareAccelerated() revealed that the Galaxy S4 (Android 4.4) was using hardware acceleration on the custom View, while the Galaxy S3 Mini (Android 4.1.2), the Galaxy S2 and the emulator were not.

Here's what the docs say about hardware acceleration:

Hardware acceleration is not supported for all of the 2D drawing operations, turning it on might affect some of your custom views or drawing calls. Problems usually manifest themselves as invisible elements, exceptions, or wrongly rendered pixels.

And apart from that, the GPU can cache your custom View as a static image.

The simple solution (in init()) was therefore to disable hardware acceleration for the custom View class:

if (Build.VERSION.SDK_INT >= 11) {
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

Andere Tipps

Keep your own boolean around for whether you should be drawing or not:

private boolean mDraw;

protected void init() {
    mDraw = false;
}

public void show(int input) {
    mDraw = true;
    invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if(!mDraw) return;

    // draw things on the canvas now
}

Alternatively, setVisibility when you actually need it:

protected void init() {
    setVisibility(View.INVISIBLE);
}

public void show(int input) {
    setVisibility(View.VISIBLE);
    invalidate();
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top