Android invalidateDrawable() not working

2.2k Views Asked by At

I have a bunch of drawables in a custom view. I want the user to be able to press on one or multiple drawables and it changes colors. Currently, each drawable is just a StateListDrawable with two states: state_pressed and not pressed. Every time I press a drawable, setState returns true so I'm assuming that it is actually changed, but I don't see the drawable image change. Is invalidateDrawable not doing anything? What am I doing wrong? How can I redraw the one drawable when pressed without needing call customView.invalidate() and redrawing the whole thing each time? I was doing that originally but found that my app ran very slowly/inefficiently. Thanks!

The flow:

Custom View (contains set of our custom class - TouchKey)
- Custom class TouchKey containing drawable and info
- Upon press or release, custom class finds which drawable to change

Here's code for a button touch within TouchKey class (MyTouch is a custom class tracking all the touches on the android device):

public void pressed(MyTouch touch) {
    boolean successfulStateChange = this.drawable.setState(new int[]{android.
                                                           R.attr.state_pressed});
    this.customView.invalidateDrawable(drawable);
}

public void released(MyTouch touch) {
    boolean successfulStateChange = this.drawable.setState(new int[]{-android.
                                                           R.attr.state_pressed});
    this.customView.invalidateDrawable(drawable);
}

How my StateListDrawable is being drawn in my custom view:

public class CustomView extends View {

    private TreeMap<Integer, TouchKey> keymap;

    /* Initialization Code Stuff Here - call drawKey */

    // StateListDrawable Creation
    private StateListDrawable drawKey(Canvas canvas, int bounds_l, 
                                  int bounds_t, int bounds_r, int bounds_b) 
        throws Resources.NotFoundException, XmlPullParserException, IOException {

        StateListDrawable key = new StateListDrawable();
        key.addState(new int[]{android.R.attr.state_pressed}, 
                     ContextCompat.getDrawable(mContext, R.drawable.key_pressed));
        key.addState(new int[]{-android.R.attr.state_pressed}, 
                     ContextCompat.getDrawable(mContext, R.drawable.key_released));    

        key.setBounds(bound_l, bounds_t, bounds_r, bounds_b);
        key.draw(canvas);

        return key;
    }
}
2

There are 2 best solutions below

7
On BEST ANSWER

I was doing that originally but found that my app ran very slowly/inefficiently

If do it so you have a big advantages in some place in your code, or (I suppose) doesn't scale images before drawing. So try to find a logic on your code that have a huge advantage on system. Because View.invalidate() so fast method.

Other 0,02$ :

I suppose that you develop something like a keyboard. For this case you need invalidate just region of your canvas.

View.invalidate(new Rect(0, 0, 49, 49));
0
On

I had the problem too, but I solve it in the end. The reason for this problem is that the Drawable object which you are using in the context doesn't setup it's Bounds by call setBounds(Rect), so it's Bounds is Rect(0,0,0,0) by default. This cause invalidateDrawable() of View which Drawable attached not working.

See the View.invalidateDrawable():

@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
    if (verifyDrawable(drawable)) {
        final Rect dirty = drawable.getDirtyBounds();
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;

        invalidate(dirty.left + scrollX, dirty.top + scrollY,
                dirty.right + scrollX, dirty.bottom + scrollY);
        rebuildOutline();
    }
}

Look check Drawable.getDirtyBounds():

  /**
 * Return the drawable's dirty bounds Rect. Note: for efficiency, the
 * returned object may be the same object stored in the drawable (though
 * this is not guaranteed).
 * <p>
 * By default, this returns the full drawable bounds. Custom drawables may
 * override this method to perform more precise invalidation.
 *
 * @return The dirty bounds of this drawable
 */
@NonNull
public Rect getDirtyBounds() {
    return getBounds();
}

So the Rect dirty is Rect(0,0,0,0), if you not setup Drawable.Bounds.Therefore View.invalidate() doesn't working.

So what you have to do is setup Drawable.Bounds in some place in the code,like that:

@Override
public void draw(@NonNull Canvas canvas) {
    Rect localRect = canvas.getClipBounds();
    this.setBounds(localRect);
    ...
}