Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing a custom circle segment dynamically

Tags:

android

My customer wants the following widget in the application:
enter image description here
The Text comes from the server. The gradient angle depends on variables which also comes from the server. Also, customer wants the gradient to be filled dynamically (a user must see how the gradient is filled starting from the 0).
Now I do the following: I use two images - one is the colored circle, the second is the grey circle. I create a circle segment with a certain angle and apply it as a mask to the grey circle, then combine the colored circle with the new grey circle (where a sector is cut off).
Here is my code. I initialize the variables calling the initializeVarsForCompoundImDrawing, then a few times in a second call the makeCompoundImage and in the end call the nullVarsForCompoundImDrawing to free the resources:

private static Bitmap notColoredBitmap;
private static Bitmap coloredBitmap;
private static Bitmap notColoredWithMaskBitmap;
private static Bitmap finalBitmap;
private static Canvas notColoredWithMaskCanvas;
private static Paint paintForMask;
private static Paint smoothPaint;
private static Canvas finalCanvas;
private static RectF rectForMask;

public static void initializeVarsForCompoundImDrawing()
{
    Context context = MainApplication.getContext();
    notColoredBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.not_colored);
    coloredBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.colored);
    
    paintForMask = new Paint(Paint.ANTI_ALIAS_FLAG);
    paintForMask.setStyle(Paint.Style.FILL);
    paintForMask.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    rectForMask = new RectF(0, 0, notColoredBitmap.getWidth(), notColoredBitmap.getHeight());

    smoothPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}

public static void nullVarsForCompoundImDrawing()
{
    notColoredBitmap = null;
    coloredBitmap = null;
    
    paintForMask = null;
    rectForMask = null;
    smoothPaint = null;
}

public static void makeCompoundImage(ImageView imageView, int angle)
{
    notColoredWithMaskBitmap = Bitmap.createBitmap(notColoredBitmap.getWidth(), notColoredBitmap.getHeight(), Bitmap.Config.ARGB_8888);
    notColoredWithMaskCanvas = new Canvas(notColoredWithMaskBitmap);
    notColoredWithMaskCanvas.drawBitmap(notColoredBitmap, 0, 0, smoothPaint);
    notColoredWithMaskCanvas.drawArc(rectForMask, 270, angle, true, paintForMask);

    finalBitmap = Bitmap.createBitmap(notColoredBitmap.getWidth(), notColoredBitmap.getHeight(), Bitmap.Config.ARGB_8888);
    finalCanvas = new Canvas(finalBitmap);
    finalCanvas.drawBitmap(coloredBitmap, 0, 0, smoothPaint);
    finalCanvas.drawBitmap(notColoredWithMaskBitmap, 0, 0, smoothPaint);

    imageView.setImageBitmap(finalBitmap);
}

The first question: is it possible to improve this code to use less resources?
The second question: how can I add the text to the finalBitmap (now it is a TextView which is shown at the top of the ImageView with the image)?

like image 414
smb Avatar asked Oct 11 '13 10:10

smb


3 Answers

I have been doing similar thing - animated progressbar, like on your picture, but every second state of progressbar was changing. Inside i had time text, wich remained till progress expire. I will just put here my whole view, maybe it would be useful to somebody.

public class TickerView extends View {

private Paint mPaint, backPaint;
private RectF mOval;
private Paint mTextPaint;

private int time;
private float progress = 100;

public TickerView(Context context) {
    super(context);

    init();
}

public TickerView(Context context, AttributeSet attrs) {
    super(context, attrs);

    init();
}

public TickerView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    init();
}

private void init(){
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setColor(Color.WHITE);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(UIHelper.getPixel(1));//1dp
    mPaint.setDither(true);                    // set the dither to true
    mPaint.setStrokeJoin(Paint.Join.ROUND);    // set the join to round you want
    mPaint.setStrokeCap(Paint.Cap.ROUND);      // set the paint cap to round too
    mPaint.setPathEffect(new PathEffect());   // set the path effect when they join.


    backPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    backPaint.setColor(ColorManager.roseLine);
    backPaint.setStyle(Paint.Style.STROKE);
    backPaint.setStrokeWidth(UIHelper.getPixel(1));//1dp
    backPaint.setDither(true);                    // set the dither to true
    backPaint.setStrokeJoin(Paint.Join.ROUND);    // set the join to round you want
    backPaint.setStrokeCap(Paint.Cap.ROUND);      // set the paint cap to round too
    backPaint.setPathEffect(new PathEffect());   // set the path effect when they join.

    measure(MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY);
    mOval = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());

    mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mTextPaint.setColor(Color.WHITE);
    mTextPaint.setTypeface(FontSetter.HelveticaNeueLight);
    mTextPaint.setTextSize(UIHelper.getPixel(15));
    mTextPaint.setTextAlign(Paint.Align.CENTER);

    mOval = new RectF(UIHelper.getPixel(1), UIHelper.getPixel(1), getWidth(), getHeight());
}

/**
 *
 * @param persent in range [0..1]
 * @param time - seconds left before progress will be executed
 */
public void setProgress(float persent, int time){
    this.progress = persent;
    this.time = time;
    invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawText("" + time,
            getWidth() / 2,
            (getHeight() - mTextPaint.ascent()) / 2,
            mTextPaint);

    mOval.right = getWidth() - UIHelper.getPixel(1);
    mOval.bottom = getHeight() - UIHelper.getPixel(1);

    canvas.drawArc(mOval, 0, 360, false, backPaint);

    int angle = (int)( progress * 360 );
    canvas.drawArc(mOval, -90, angle, false, mPaint);
}

}

like image 157
Anton Kizema Avatar answered Oct 05 '22 13:10

Anton Kizema


answering your questions: yes it can be done easier, much more easier:

public class Ring extends View {
    private Bitmap mBack;
    private Paint mPaint;
    private RectF mOval;
    private Paint mTextPaint;

    public Ring(Context context) {
        super(context);
        Resources res = getResources();
        mBack = BitmapFactory.decodeResource(res, R.drawable.back);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Bitmap ring = BitmapFactory.decodeResource(res, R.drawable.ring);
        mPaint.setShader(new BitmapShader(ring, TileMode.CLAMP, TileMode.CLAMP));
        mOval = new RectF(0, 0, mBack.getWidth(), mBack.getHeight());
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(24);
        mTextPaint.setTextAlign(Align.CENTER);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.translate((getWidth() - mBack.getWidth()) / 2, (getHeight() - mBack.getHeight()) / 2);
        canvas.drawBitmap(mBack, 0, 0, null);
        float angle = 220;
        canvas.drawArc(mOval, -90, angle, true, mPaint);
        canvas.drawText("Text",
            mBack.getWidth() / 2,
            (mBack.getHeight() - mTextPaint.ascent()) / 2,
            mTextPaint);
    }
}

EDIT:

and this is an alternate solution (no centering, no text inside, just a concept)

class Ring extends View {
    private Bitmap back;
    private Bitmap ring;
    private RectF oval;
    private Paint arcPaint;

    public Ring(Context context) {
        super(context);
        Resources res = getResources();
        back = BitmapFactory.decodeResource(res, R.drawable.back);
        ring = BitmapFactory.decodeResource(res, R.drawable.ring);
        arcPaint = new Paint();
        arcPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
        oval = new RectF(-1, -1, ring.getWidth()+1, ring.getHeight()+1);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawARGB(0xaa, 0, 255, 0);
        canvas.drawBitmap(back, 0, 0, null);
        canvas.saveLayer(oval, null, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
        canvas.drawBitmap(ring, 0, 0, null);
        float angle = 300;
        canvas.drawArc(oval, angle-90, 360-angle, true, arcPaint);
        canvas.restore();
    }
}
like image 29
pskink Avatar answered Oct 05 '22 13:10

pskink


You can use my project as example https://github.com/donvigo/CustomProgressControls. It's not documented yet on github, but I hope you will understand my code :)

EDIT: Text inside circle it's not a TextView, it's drawed using paint.

like image 25
Veaceslav Gaidarji Avatar answered Oct 05 '22 13:10

Veaceslav Gaidarji