3

I would like to draw an image as below using android drawable something as below:

enter image description here

I could do the arrow but when I add circle, the image gets distorted.

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
    <shape>
        <solid android:color="@android:color/transparent"/>
        <size android:width="2dp" android:height="50dp"/>
    </shape>
</item>

<item android:bottom="20dp">
    <rotate
        android:fromDegrees="45"
        android:toDegrees="45">
        <shape android:shape="rectangle">
            <solid android:color="#F0AD4E"/>
            <corners
                android:radius="0dp"
                android:bottomRightRadius="0dp"
                android:bottomLeftRadius="0dp"/>
        </shape>
    </rotate>
</item>

<item android:top="20dp">
    <rotate
        android:fromDegrees="-45"
        android:toDegrees="45">
        <shape android:shape="rectangle">
            <solid android:color="#F0AD4E"/>
            <corners
                android:radius="0dp"
                android:topRightRadius="0dp"
                android:topLeftRadius="0dp"/>
        </shape>
    </rotate>
</item>

Can anyone help me to complete the picture.

Thanks.

Bojan Kseneman
  • 15,488
  • 2
  • 54
  • 59
Noorul
  • 939
  • 1
  • 11
  • 21

2 Answers2

4

Don't use drawables for this, use a custom view and canvas.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by Bojan on 17.5.2015.
 */

public class CircleRightArrow extends View {

    private int circleColor = 0xFF505070;
    private int arrowColor = 0xFF505070;
    private int measuredSize;
    private int strokeWidth;

    private Paint mCirclePiant, mArrowPaint;

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

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

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

    private void init(Context context, AttributeSet attributeSet, int defStyle) {
        mCirclePiant = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePiant.setColor(circleColor);
        mCirclePiant.setStyle(Paint.Style.STROKE);

        mArrowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mArrowPaint.setColor(arrowColor);
        mArrowPaint.setStyle(Paint.Style.STROKE);
        mArrowPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        int measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);

        measuredSize = Math.min(measuredHeight, measuredWidth);
        strokeWidth = Math.round(measuredSize * 0.05f);
        mCirclePiant.setStrokeWidth(strokeWidth);
        mArrowPaint.setStrokeWidth(strokeWidth);
        // Make a square
        setMeasuredDimension(measuredSize + strokeWidth, measuredSize + strokeWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (measuredSize <= 0) {
            // Not much we can draw, can we
            return;
        }

        float center = (measuredSize + strokeWidth) * 0.5f;
        canvas.drawCircle(center, center, measuredSize * 0.5f, mCirclePiant);

        canvas.drawLine(center + 0.2f * measuredSize, center, center - 0.1f * measuredSize, center + 0.2f * measuredSize, mArrowPaint);
        canvas.drawLine(center + 0.2f * measuredSize, center, center - 0.1f * measuredSize, center - 0.2f * measuredSize, mArrowPaint);
    }
}

Result of 50x50dp at 1920x1080p

sldjasd

Bojan Kseneman
  • 15,488
  • 2
  • 54
  • 59
  • Thanks Bojan. I used XML drawable to support multiple states by using different colors on touch, focused etc. How can I do the same in this case? – Noorul May 18 '15 at 05:53
  • Which states do you want to support? If only pressed, then override onTouch and change the color there... just change the int values for color (circleColor, arrowColor) and call invalidate(); Once the finger is released, revert back and again call invalidate(); – Bojan Kseneman May 18 '15 at 06:22
  • Oh, and don't forget the transparency bits 0x**AA**RRGGBB. You can also use Color.parseColor("#RRGGBB") – Bojan Kseneman May 18 '15 at 06:40
  • Hi Bojan, could you show me how to make the arrow inside the circle to be 90 degrees? – Noorul May 28 '15 at 11:30
  • How do you mean 90 degrees? You can rotate the whole view in XML by using `android:rotation="90"`... this is the most simple way as no changes to the code need to be done – Bojan Kseneman May 28 '15 at 12:05
  • The angle inside the right arrow (in terms of degrees) is not 90. I want to make it 90. Compare with the image in my first post – Noorul May 28 '15 at 12:08
  • you would need to some basic math... perpendicular lines. – Bojan Kseneman May 28 '15 at 12:33
1

Following Bojan's answer, I edited the view to redraw based on the touch events and added the OnClickListener part. Feel free to suggest any improvements.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by noorul.ahmed on 5/18/2015.
 */
public class CircleRightArrow extends View {

    private int viewColor;
    private int backgroundColor;
    private int normalColor = 0xFFF0AD4E;
    private int normalBackground = 0xFFFFFFFF;
    private int touchBackground = 0xFFEC971F;
    private int touchColor = 0xFFFFFFFF;
    private int disabledBackground = 0xFFFFFFFF;
    private int disabledColor = 0xFFCECECE;
    private int measuredSize;
    private int strokeWidth;
    private boolean touched = false;
    private float startX, startY, endX, endY;
    private OnClickListener listener;

    private Paint mCirclePiant, mArrowPaint;

    public CircleRightArrow(Context context) {
        super(context);
        init(context, null, 0);
        setInitialColor();
    }

    public CircleRightArrow(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
        setInitialColor();
    }

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

    private void init(Context context, AttributeSet attributeSet, int defStyle) {
        mCirclePiant = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePiant.setColor(viewColor);
        mCirclePiant.setStyle(Paint.Style.STROKE);

        mArrowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mArrowPaint.setColor(viewColor);
        mArrowPaint.setStyle(Paint.Style.STROKE);
        mArrowPaint.setStrokeCap(Paint.Cap.ROUND);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measuredHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        int measuredWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);

        measuredSize = Math.min(measuredHeight, measuredWidth);
        strokeWidth = Math.round(measuredSize * 0.025f);
        mCirclePiant.setStrokeWidth(strokeWidth);
        mArrowPaint.setStrokeWidth(strokeWidth);
        // Make a square
        setMeasuredDimension(measuredSize + strokeWidth, measuredSize + strokeWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
            if (measuredSize <= 0) {
                // Not much we can draw, can we
                return;
            }

            float center = (measuredSize + strokeWidth) * 0.5f;
            canvas.drawCircle(center, center, measuredSize * 0.5f, mCirclePiant);

            canvas.drawLine(center + 0.2f * measuredSize, center, center - 0.1f * measuredSize, center + 0.2f * measuredSize, mArrowPaint);
            canvas.drawLine(center + 0.2f * measuredSize, center, center - 0.1f * measuredSize, center - 0.2f * measuredSize, mArrowPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN :
                touched = true;
                startX = event.getX();
                startY = event.getY();
                togglePaintColor(event);
                postInvalidate();
                return true;
            case MotionEvent.ACTION_UP :
                endX = event.getX();
                endY = event.getY();
                togglePaintColor(event);
                postInvalidate();
                float diffX = Math.abs(startX - endX);
                float diffY = Math.abs(startY - endY);

                if (diffX <= 5 && diffY <= 5 && touched ) {
                    dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,1));
                }
                return true;
            default:
                return false;
        }
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if(event.getAction() == KeyEvent.ACTION_UP) {
            touched = false;
            if(listener != null) listener.onClick(this);
        }
        return super.dispatchKeyEvent(event);
    }

    public void setListener(OnClickListener listener) {
        this.listener = listener;
    }

    public void setInitialColor(){
        if (isClickable()){
            viewColor = normalColor;
            backgroundColor = normalBackground;
        }
        else {
            viewColor = disabledColor;
            backgroundColor = disabledBackground;
            this.listener = null;
        }
        mCirclePiant.setColor(viewColor);
        mCirclePiant.setStyle(Paint.Style.STROKE);
        mArrowPaint.setColor(viewColor);
    }

    @Override
    public void setClickable(boolean isClickable){
        super.setClickable(isClickable);
        if (isClickable){
            viewColor = normalColor;
            backgroundColor = normalBackground;
        }
        else {
            viewColor = disabledColor;
            backgroundColor = disabledBackground;
            this.listener = null;
        }
        mCirclePiant.setColor(viewColor);
        mCirclePiant.setStyle(Paint.Style.STROKE);
        mArrowPaint.setColor(viewColor);
        postInvalidate();
    }

    private void togglePaintColor(MotionEvent event) {
        if (isClickable()) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                viewColor = touchColor;
                backgroundColor = touchBackground;
                mCirclePiant.setColor(backgroundColor);
                mCirclePiant.setStyle(Paint.Style.FILL);
                mArrowPaint.setColor(viewColor);

            } else if (event.getAction() == MotionEvent.ACTION_UP) {
                viewColor = normalColor;
                backgroundColor = normalBackground;
                mCirclePiant.setColor(viewColor);
                mCirclePiant.setStyle(Paint.Style.STROKE);
                mArrowPaint.setColor(viewColor);
            }
        }
    }
}

The three button states are:

  1. Normal State enter image description here
  2. Pressed State enter image description here
  3. Disabled state enter image description here
Noorul
  • 939
  • 1
  • 11
  • 21
  • Nice, one thing you could still improve is use custom attributes to override the color values in XML. Keep these as a default, but allow you to change them in XML. You can take my answer here as an example. http://stackoverflow.com/questions/29876100/setting-width-of-seekbar-to-make-swipe-to-unlock-effect/29951478#29951478 – Bojan Kseneman May 19 '15 at 06:27