Question

I have a custom view, extending RelativeLayout and using it as a list item in a ListView. It's basically a rectangle with a funky angle on the left. Depending on what I return in my onTouchEvent, I can end up in 2 possible states:

  1. The list item will display its pressed state properly and return to the unpressed state but will not send the click to the ListView.
  2. The list item will remain in the pressed state (and can't get out) BUT will send the click to the ListView perfectly fine.

I have never done custom views of this type before but would really appreciate advice. I assume I'm missing something simple, but I've spent way too much time on this at this point to go on without asking questions!

Here's the code:

public class ListItemBackgroundView extends RelativeLayout {
    private final Path PATH = new Path();
    private final Paint BRUSH = new Paint(Paint.ANTI_ALIAS_FLAG);
    private static int mBackgroundColor = -1;
    private static int mPressedBackgroundColor = -1;
    private static int mAngleOffset = -1;

    private boolean isPressed;

    public ListItemBackgroundView(Context context) {
        super(context);
        setWillNotDraw(false);
    }

    public ListItemBackgroundView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
    }

    public ListItemBackgroundView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setWillNotDraw(false);
    }

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

        final int w = canvas.getWidth();
        final int h = canvas.getHeight();

        if (mBackgroundColor == -1) {
            mBackgroundColor = getResources().getColor(R.color.color_1);
            mPressedBackgroundColor = getResources().getColor(R.color.color_2);
            mAngleOffset = getResources().getDimensionPixelSize(R.dimen.list_view_angle_offset);
        }

        PATH.moveTo(0, 0);
        PATH.lineTo(mAngleOffset, h * (0.35f));
        PATH.lineTo(0, h);
        PATH.lineTo(w, h);
        PATH.lineTo(w, 0);
        PATH.lineTo(0, 0);
        PATH.close();

        BRUSH.setStyle(Paint.Style.FILL);
        BRUSH.setColor(isPressed ? mPressedBackgroundColor : mBackgroundColor);

        canvas.drawPath(PATH, BRUSH);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isPressed = true;
                result = false;
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                isPressed = false;
                result = true;
                invalidate();
                break;
        }
        return result;
    }
}
Was it helpful?

Solution

The solution is to use a StateListDrawable as the background.

public class ListItemStateListDrawable extends StateListDrawable {
    private final int mBackgroundColor, mPressedBackgroundColor, mAngleOffset;
    private final Path PATH = new Path();
    private final Paint BRUSH_PRESSED = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Paint BRUSH = new Paint(Paint.ANTI_ALIAS_FLAG);
    private boolean isDrawn = false;
    private boolean isPressed = false;

    public ListItemStateListDrawable(int backgroundColor, int pressedBackgroundColor, int angleOffset) {
        this.mBackgroundColor = backgroundColor;
        this.mPressedBackgroundColor = pressedBackgroundColor;
        this.mAngleOffset = angleOffset;
    }

    @Override
    protected boolean onStateChange(int[] stateSet) {
        for(int st : stateSet){
            boolean pressed = st == android.R.attr.state_pressed;
            boolean not_pressed = st == android.R.attr.stateNotNeeded || st ==  -android.R.attr.state_pressed ||
                    st ==  android.R.attr.state_accelerated || st ==  android.R.attr.state_focused;
            if(pressed || not_pressed){
                isPressed = pressed;
                invalidateSelf();
                break;
            }
        }
        return super.onStateChange(stateSet);
    }

    @Override
    public void draw(Canvas canvas) {
        if(!isDrawn){
            final int w = canvas.getWidth();
            final int h = canvas.getHeight();

            PATH.moveTo(0, 0);
            PATH.lineTo(mAngleOffset, h * (0.35f));
            PATH.lineTo(0, h);
            PATH.lineTo(w, h);
            PATH.lineTo(w, 0);
            PATH.lineTo(0, 0);
            PATH.close();

            BRUSH.setStyle(Paint.Style.FILL);
            BRUSH.setColor(mBackgroundColor);
            BRUSH_PRESSED.setStyle(Paint.Style.FILL);
            BRUSH_PRESSED.setColor(mPressedBackgroundColor);

            canvas.drawPath(PATH, BRUSH);
            isDrawn = true;
        }else {
            canvas.drawPath(PATH, isPressed ? BRUSH_PRESSED : BRUSH);
        }
    }
}

OTHER TIPS

using StateListDrawable and adding the states by addState is a good, std way but you can do it shorter by:

class MyStateListDrawable extends StateListDrawable {
    @Override
    protected boolean onStateChange(int[] stateSet) {
        invalidateSelf();
        return super.onStateChange(stateSet);
    }
    @Override
    public void draw(Canvas canvas) {
        int[] state = getState();
        Log.d(TAG, "draw " + StateSet.dump(getState()));
        // remove states you dont want or add you do
        if (isA(android.R.attr.state_pressed)) {
            Log.d(TAG, "draw state_pressed");
            canvas.drawColor(0xffff0000);
        } else
        if (isA(android.R.attr.state_focused)) {
            Log.d(TAG, "draw state_focused");
            canvas.drawColor(0xffff0000);
        } else
        if (isA(android.R.attr.state_selected)) {
            Log.d(TAG, "draw state_selected");
            canvas.drawColor(0xffffff00);
        } else {
            Log.d(TAG, "draw default state");
            canvas.drawColor(0xff0000ff);
        }
    }
    private boolean isA(int i) {
        int[] state = getState();
        for (int st : state) {
            if (st == i) {
                return true;
            }
        }
        return false;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top