Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Canvas Arc, Can Sweep Gradient Start Angle Be Changed?

I am attempting to draw a arc that is filled by a gradient

The image below is what I want

enter image description here

The image below is what I have now

enter image description here

As you can see in the images, my gradient starts too earlier

I know why this happening

If I complete the arc to form a circle I get this

enter image description here

As we can see, the gradient starts from 90 degrees. But my arc is drawn from 135 degrees and sweeps to 270

My question is how can get the gradient to start from 135 degrees and sweep to 270? Is it possible

This is my method so far of doing the sweep gradient

  public void setProgressColourAsGradient(boolean invalidateNow) {
        SweepGradient sweepGradient = new SweepGradient(baseArcRect.centerX(), baseArcRect.centerY(),progressGradientColourStart,progressGradientColourEnd);
        //Make the gradient start from 90 degrees
        Matrix matrix = new Matrix();
        matrix.setRotate(90,baseArcRect.centerX(), baseArcRect.centerY());
        sweepGradient.setLocalMatrix(matrix);

        progressFillPaint.setShader(sweepGradient);
        if (invalidateNow) {
            invalidate();
        }
    }

I cannot find any API to tell SweepGradient where to actually start.

I have added all code to the appendix section

Thanks for reading!

Comment 1

I tried setting the rotation to happen at 135 degrees

enter image description here

Appendix A ArcWithGradient View

public class ArcWithGradient extends View {

    private Paint progressFillPaint;
    private RectF baseArcRect;
    private int progressGradientColourStart;
    private int progressGradientColourEnd;
    /**
     * Thickness of the arc
     */
    private int thickness;

    public ArcWithGradient(Context context) {
        super(context);
        init();
    }

    public ArcWithGradient(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ArcWithGradient(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {

        progressGradientColourStart = ContextCompat.getColor(getContext(), R.color.pinnacle_gradient_start);
        progressGradientColourEnd = ContextCompat.getColor(getContext(), R.color.pinnacle_gradient_end);
        thickness = UiUtils.dpToPx(getContext(), 25);

        //We do not want a colour for this because we will set a gradient
        progressFillPaint = CanvasUtil.makeStrokePaint(UiUtils.dpToPx(getContext(), 25), -1);
        baseArcRect = new RectF(0, 0, 0, 0);
        setProgressColourAsGradient(false);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
        super.onSizeChanged(width, height, oldw, oldh);
        //Ensures arc is within the rectangle
        float radius = Math.min(width, height) / 2;//

        //I do radius - thickness so that the arc is within the rectangle
        float baseArcLeft = ((width / 2) - (radius - thickness));
        float baseArcTop = ((height / 2) - (radius - thickness));
        float baseArcRight = ((width / 2) + (radius - thickness));
        float baseArcBottom = ((height / 2) + (radius - thickness));

        baseArcRect.set(baseArcLeft, baseArcTop, baseArcRight, baseArcBottom);
        //Recalculate the gradient
        setProgressColourAsGradient(false);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawArc(baseArcRect, 135, 270, false, progressFillPaint);
    }

    public void setProgressColourAsGradient(boolean invalidateNow) {
        SweepGradient sweepGradient = new SweepGradient(baseArcRect.centerX(), baseArcRect.centerY(),progressGradientColourStart,progressGradientColourEnd);
        //Make the gradient start from 90 degrees
        Matrix matrix = new Matrix();
        matrix.setRotate(90,baseArcRect.centerX(), baseArcRect.centerY());
        sweepGradient.setLocalMatrix(matrix);

        progressFillPaint.setShader(sweepGradient);
        if (invalidateNow) {
            invalidate();
        }
    }
}

Appendix B UiUtils

public class UiUtils {

    public static int dpToPx(Context ctx, float dp) {
        return Math.round(dp * ctx.getResources().getDisplayMetrics().density);
    }
}

Appendix C CanvasUtil

public class CanvasUtil {

    public static Paint makeStrokePaint(int width, int color) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStrokeCap(Paint.Cap.SQUARE);
        paint.setStrokeWidth(width);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(color);
        return paint;
    }

    public static Paint makeFillPaint(int color) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(color);
        return paint;
    }

}

like image 386
Ersen Osman Avatar asked Jun 12 '17 16:06

Ersen Osman


1 Answers

As mentioned in the comments by @pskink you need to use the SweepGradient constructor that takes a list of colors and a list of positions. The positions argument is an array of floats that indicate the percentage of 360 degrees where the color should begin.

SweepGradient example

In my example I'm rotating the canvas 115 degrees and then drawing a sweep angle of 310 degrees for my arch. The first color begins at an angle of 0 (because the canvas has been rotated), and the gradient reaches the second color at an angle of 310 degrees so it matches the sweep angle of the arch. The outer ring shows that the blue color continues all the way to 360 degrees, but the blue color begins at 310 degrees.

class SweepGradientView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
): View(context, attrs, defStyleAttr) {


    private val archPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.STROKE
        strokeWidth = 32 * context.resources.displayMetrics.density
    }

    private val archBounds = RectF()
    private val archInset = 72 * context.resources.displayMetrics.density

    private val gradientColors = intArrayOf(Color.MAGENTA, Color.BLUE)
    private val gradientPositions = floatArrayOf(0/360f, 310/360f)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        val size = Math.min(width, height)
        setMeasuredDimension(size, size)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        archPaint.shader = SweepGradient(width / 2f, height / 2f, gradientColors, gradientPositions)
        archBounds.set(archInset, archInset, width.toFloat() - archInset, height.toFloat() - archInset)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val cx = width / 2f
        val cy = height / 2f

        canvas.save()
        canvas.rotate(115f, cx, cy)
        canvas.drawArc(archBounds, 0f, 310f, false, archPaint)
        canvas.drawCircle(cx, cy, width / 2.2f, archPaint)
        canvas.restore()
    }

}
like image 162
Victor Rendina Avatar answered Oct 14 '22 16:10

Victor Rendina