Custom view clickable area overlap

1.1k views Asked by At

I have a diagonal shaped imageview. When i clicked the imageview it doesn't respect the clipped area and perform clicking on the transparent area aswell.

How can i make only non transparent area or shape clickable? You can see the view's source code here if needed

Thanks in advance.

enter image description here

enter image description here

2

There are 2 answers

0
Fatih Santalu On BEST ANSWER

With merging @Raghunandan and @oğuzhan-döngül's solutions i finally managed to solve the issue. Final code can be found here for future readers. Thank you for all your support.

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.Region;
import android.support.annotation.IntDef;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.MotionEvent;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Created by santalu on 7/4/17.
 *
 * Note: if position set NONE mask won't be applied
 *
 * POSITION    DIRECTION
 *
 * TOP         LEFT |  RIGHT
 * BOTTOM      LEFT |  RIGHT
 * LEFT        TOP  |  BOTTOM
 * RIGHT       TOP  |  BOTTOM
 */

public class DiagonalImageView extends AppCompatImageView {

    public static final String TAG = DiagonalImageView.class.getSimpleName();

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ NONE, LEFT, RIGHT, TOP, BOTTOM })
    public @interface Position {
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ LEFT, RIGHT, TOP, BOTTOM })
    public @interface Direction {
    }

    public static final int NONE = 0;
    public static final int TOP = 1;
    public static final int RIGHT = 2;
    public static final int BOTTOM = 4;
    public static final int LEFT = 8;

    private final Path mClipPath = new Path();
    private final Path mBorderPath = new Path();

    private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private Region mClickRegion = new Region();
    private RectF mClickRect = new RectF();

    private int mPosition;
    private int mDirection;
    private int mOverlap;
    private int mBorderColor;
    private int mBorderSize;

    private boolean mBorderEnabled;

    public DiagonalImageView(Context context) {
        super(context);
        init(context, null);
    }

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

    private void init(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }

        setLayerType(LAYER_TYPE_HARDWARE, null);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiagonalImageView);
        try {
            mPosition = a.getInteger(R.styleable.DiagonalImageView_di_position, NONE);
            mDirection = a.getInteger(R.styleable.DiagonalImageView_di_direction, RIGHT);
            mOverlap = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_overlap, 0);
            mBorderSize = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_borderSize, 0);
            mBorderColor = a.getColor(R.styleable.DiagonalImageView_di_borderColor, Color.BLACK);
            mBorderEnabled = a.getBoolean(R.styleable.DiagonalImageView_di_borderEnabled, false);

            mBorderPaint.setColor(mBorderColor);
            mBorderPaint.setStyle(Style.STROKE);
            mBorderPaint.setStrokeWidth(mBorderSize);
        } finally {
            a.recycle();
        }
    }

    public void set(@Position int position, @Direction int direction) {
        if (mPosition != position || mDirection != direction) {
            mClipPath.reset();
            mBorderPath.reset();
        }
        mPosition = position;
        mDirection = direction;
        postInvalidate();
    }

    public void setPosition(@Position int position) {
        if (mPosition != position) {
            mClipPath.reset();
            mBorderPath.reset();
        }
        mPosition = position;
        postInvalidate();
    }

    public void setDirection(@Direction int direction) {
        if (mDirection != direction) {
            mClipPath.reset();
            mBorderPath.reset();
        }
        mDirection = direction;
        postInvalidate();
    }

    public void setBorderEnabled(boolean enabled) {
        mBorderEnabled = enabled;
        postInvalidate();
    }

    public @Position int getPosition() {
        return mPosition;
    }

    public @Direction int getDirection() {
        return mDirection;
    }

    public boolean isBorderEnabled() {
        return mBorderEnabled;
    }

    @Override protected void onDraw(Canvas canvas) {
        if (mClipPath.isEmpty()) {
            super.onDraw(canvas);
            return;
        }

        int saveCount = canvas.save();
        canvas.clipPath(mClipPath);
        super.onDraw(canvas);
        if (!mBorderPath.isEmpty()) {
            canvas.drawPath(mBorderPath, mBorderPaint);
        }
        canvas.restoreToCount(saveCount);
    }

    @Override protected void dispatchDraw(Canvas canvas) {
        if (!mClipPath.isEmpty()) {
            canvas.clipPath(mClipPath);
        }
        super.dispatchDraw(canvas);
    }

    @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (!changed) {
            return;
        }

        if (mClipPath.isEmpty()) {
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();

            if (width <= 0 || height <= 0) {
                return;
            }

            setClipPath(width, height);
        }
    }

    @Override public boolean onTouchEvent(MotionEvent event) {
        if (!mClickRegion.isEmpty()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    Point point = new Point();
                    point.x = (int) event.getX();
                    point.y = (int) event.getY();
                    //Log.d(TAG, "point: " + point);
                    if (!mClickRegion.contains(point.x, point.y)) {
                        //Log.d(TAG, "clicked outside");
                        return false;
                    }
            }
        }
        return super.onTouchEvent(event);
    }

    private void setClipPath(final int width, final int height) {
        mClipPath.reset();
        mBorderPath.reset();

        switch (mPosition) {
            case TOP:
                if (mDirection == LEFT) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, mOverlap);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, 0);
                        mBorderPath.lineTo(width, mOverlap);
                    }
                } else {
                    mClipPath.moveTo(0, mOverlap);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, mOverlap);
                        mBorderPath.lineTo(width, 0);
                    }
                }
                break;
            case RIGHT:
                if (mDirection == TOP) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width - mOverlap, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(width, 0);
                        mBorderPath.lineTo(width - mOverlap, height);
                    }
                } else {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width - mOverlap, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(width - mOverlap, 0);
                        mBorderPath.lineTo(width, height);
                    }
                }
                break;
            case BOTTOM:
                if (mDirection == LEFT) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height - mOverlap);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, height);
                        mBorderPath.lineTo(width, height - mOverlap);
                    }
                } else {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height - mOverlap);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, height - mOverlap);
                        mBorderPath.lineTo(width, height);
                    }
                }
                break;
            case LEFT:
                if (mDirection == TOP) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(mOverlap, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, 0);
                        mBorderPath.lineTo(mOverlap, height);
                    }
                } else {
                    mClipPath.moveTo(mOverlap, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(mOverlap, 0);
                        mBorderPath.lineTo(0, height);
                    }
                }
                break;
        }

        mClipPath.close();
        mClipPath.computeBounds(mClickRect, true);
        mClickRegion.setPath(mClipPath, new Region((int) mClickRect.left, (int) mClickRect.top, (int) mClickRect.right, (int) mClickRect.bottom));
        mBorderPath.close();
    }
}
1
Oğuzhan Döngül On

Updated your library code and found a solution. Used your mClipPath which is calculated by the library before drawing layout. Tried this in a single layout.

First override dispatchDraw() method like below:

@Override
protected void dispatchDraw(Canvas canvas) {
    final Path path = mClipPath;
    canvas.clipPath(path);
    super.dispatchDraw(canvas);
}

Then add your xml this two lines:

android:clickable="true"
android:foreground="?selectableItemBackground"

Full widget xml:

<com.santalu.diagonalimageview.DiagonalImageView
                android:id="@+id/image"
                android:layout_width="wrap_content"
                android:layout_height="@dimen/collapsing_image_height"
                android:layout_marginTop="?actionBarSize"
                android:background="?android:windowBackground"
                android:scaleType="centerCrop"
                android:src="@drawable/demo"
                android:clickable="true"
                android:foreground="?selectableItemBackground"
                app:di_borderEnabled="false"
                app:di_direction="left"
                app:di_overlap="@dimen/collapsing_overlap_size"
                app:di_position="bottom"/>

Full Code:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.support.annotation.IntDef;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Created by santalu on 7/4/17.
 *
 * Note: if position set NONE mask won't be applied
 *
 * POSITION    DIRECTION
 *
 * TOP         LEFT |  RIGHT
 * BOTTOM      LEFT |  RIGHT
 * LEFT        TOP  |  BOTTOM
 * RIGHT       TOP  |  BOTTOM
 */

public class DiagonalImageView extends AppCompatImageView {

    public static final String TAG = DiagonalImageView.class.getSimpleName();

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ NONE, LEFT, RIGHT, TOP, BOTTOM })
    public @interface Position {
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ LEFT, RIGHT, TOP, BOTTOM })
    public @interface Direction {
    }

    public static final int NONE = 0;
    public static final int TOP = 1;
    public static final int RIGHT = 2;
    public static final int BOTTOM = 4;
    public static final int LEFT = 8;

    private final Path mClipPath = new Path();
    private final Path mBorderPath = new Path();

    private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private int mPosition;
    private int mDirection;
    private int mOverlap;
    private int mBorderColor;
    private int mBorderSize;

    private boolean mBorderEnabled;

    public DiagonalImageView(Context context) {
        super(context);
        init(context, null);
    }

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

    private void init(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }

        setLayerType(LAYER_TYPE_HARDWARE, null);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiagonalImageView);
        try {
            mPosition = a.getInteger(R.styleable.DiagonalImageView_di_position, NONE);
            mDirection = a.getInteger(R.styleable.DiagonalImageView_di_direction, RIGHT);
            mOverlap = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_overlap, 0);
            mBorderSize = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_borderSize, 0);
            mBorderColor = a.getColor(R.styleable.DiagonalImageView_di_borderColor, Color.BLACK);
            mBorderEnabled = a.getBoolean(R.styleable.DiagonalImageView_di_borderEnabled, false);

            mBorderPaint.setColor(mBorderColor);
            mBorderPaint.setStyle(Style.STROKE);
            mBorderPaint.setStrokeWidth(mBorderSize);
        } finally {
            a.recycle();
        }

    }

    public void set(@Position int position, @Direction int direction) {
        if (mPosition != position || mDirection != direction) {
            mClipPath.reset();
            mBorderPath.reset();
        }
        mPosition = position;
        mDirection = direction;
        postInvalidate();
    }

    public void setPosition(@Position int position) {
        if (mPosition != position) {
            mClipPath.reset();
            mBorderPath.reset();
        }
        mPosition = position;
        postInvalidate();
    }

    public void setDirection(@Direction int direction) {
        if (mDirection != direction) {
            mClipPath.reset();
            mBorderPath.reset();
        }
        mDirection = direction;
        postInvalidate();
    }

    public void setBorderEnabled(boolean enabled) {
        mBorderEnabled = enabled;
        postInvalidate();
    }

    public @Position int getPosition() {
        return mPosition;
    }

    public @Direction int getDirection() {
        return mDirection;
    }

    public boolean isBorderEnabled() {
        return mBorderEnabled;
    }

    @Override protected void onDraw(Canvas canvas) {
        if (mClipPath.isEmpty()) {
            super.onDraw(canvas);
            return;
        }

        int saveCount = canvas.save();
        canvas.clipPath(mClipPath);
        super.onDraw(canvas);
        if (!mBorderPath.isEmpty()) {
            canvas.drawPath(mBorderPath, mBorderPaint);
        }
        canvas.restoreToCount(saveCount);
    }

    @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (!changed) {
            return;
        }

        if (mClipPath.isEmpty()) {
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();

            if (width <= 0 || height <= 0) {
                return;
            }

            setClipPath(width, height);
        }
    }

    private void setClipPath(final int width, final int height) {
        mClipPath.reset();
        mBorderPath.reset();

        switch (mPosition) {
            case TOP:
                if (mDirection == LEFT) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, mOverlap);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, 0);
                        mBorderPath.lineTo(width, mOverlap);
                    }
                } else {
                    mClipPath.moveTo(0, mOverlap);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, mOverlap);
                        mBorderPath.lineTo(width, 0);
                    }
                }
                break;
            case RIGHT:
                if (mDirection == TOP) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width - mOverlap, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(width, 0);
                        mBorderPath.lineTo(width - mOverlap, height);
                    }
                } else {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width - mOverlap, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(width - mOverlap, 0);
                        mBorderPath.lineTo(width, height);
                    }
                }
                break;
            case BOTTOM:
                if (mDirection == LEFT) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height - mOverlap);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, height);
                        mBorderPath.lineTo(width, height - mOverlap);
                    }
                } else {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height - mOverlap);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, height - mOverlap);
                        mBorderPath.lineTo(width, height);
                    }
                }
                break;
            case LEFT:
                if (mDirection == TOP) {
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(mOverlap, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(0, 0);
                        mBorderPath.lineTo(mOverlap, height);
                    }
                } else {
                    mClipPath.moveTo(mOverlap, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);

                    if (mBorderEnabled) {
                        mBorderPath.moveTo(mOverlap, 0);
                        mBorderPath.lineTo(0, height);
                    }
                }
                break;
        }

        mClipPath.close();
        mBorderPath.close();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        final Path path = mClipPath;
        canvas.clipPath(path);

        super.dispatchDraw(canvas);
    }
}