0

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;
    }
}
yun
  • 1,243
  • 11
  • 30

2 Answers2

0

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));
Sergey Shustikov
  • 15,377
  • 12
  • 67
  • 119
  • Hi ssh, what do you mean in your first sentence, just having a little trouble understanding it. And I tried to invalidate the rect but I'm still achieving very slow times – yun Feb 15 '16 at 22:18
  • @yun.cloud I mean that you have a long playing work in UI thread. Maybe it dialogs, or progressbar's or similar like this. Spent a little bit time to inspect your code with the question "What the hard logic in this method/class?" If you sure that problem not here try to inspect your code via DDMS tools and look what is going on in your system. Why you have a lags? – Sergey Shustikov Feb 15 '16 at 22:21
  • Well I do have a audio thread running the whole time the app is running so it's probably that? I guess this is important so each time a drawable is pressed a sine wave is played. When I don't have to redraw each time, multitouch is fine. However, trying to put in the redrawing of each pressed drawable slows down tracking - sometimes when 3 drawables are pressed at the same time, two change/play immediately while the 3rd one takes a bit longer to play/change color. I'm guessing this lag has more to do on my end with the audio processing slowing things down? – yun Feb 15 '16 at 22:29
  • @yun.cloud it may produce lags if you prepare every sound or load it from filesystem. I suppose you do that in UI thread. – Sergey Shustikov Feb 15 '16 at 22:31
  • Well I'm running my audio threading in Open SLES, so could that be it? I just tested it without the audio thread running and the touches still lag (same problem but without sound) – yun Feb 15 '16 at 22:32
  • @yun.cloud open DDMS and inspect for allocation. Maybe it hard, but i'm pretty sure that you have a complex problem – Sergey Shustikov Feb 15 '16 at 22:34
  • I tested on a different Android device (a newer model, the one I was using is a Samsung Galaxy Tablet 3) today. All multitouches/lags I experienced on my usual testing device was basically gone. I'm guessing that some devices cannot process as quickly and efficiently as the other ones. To remedy this, I put two modes - a low and high speed mode where the low has the color change and the high has no color change. I know this isn't the best work around but is a better solution for right now as there are so many android devices and I can't test on all of them. Marking this as correct answer! Thx! – yun Feb 16 '16 at 15:09
0

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);
    ...
}
ziou2004
  • 1
  • 1