Динамическое создание сегмента пользовательского круга
Мой клиент хочет использовать следующий виджет в приложении:
![enter image description here]()
Text
поступает с сервера. Угол градиента зависит от переменных, которые также поступают с сервера. Кроме того, клиент хочет, чтобы градиент заполнялся динамически (пользователь должен видеть, как градиент заполняется, начиная с 0).
Теперь я делаю следующее: я использую два изображения: один - цветной круг, второй - серый круг. Я создаю сегмент круга с определенным углом и применяю его как маску к серому кругу, а затем объединяю цветной круг с новым серым кругом (где сектор отключен).
Вот мой код. Я инициализирую переменные, вызывающие initializeVarsForCompoundImDrawing
, затем несколько раз во втором вызове makeCompoundImage
и в конце вызываем nullVarsForCompoundImDrawing
, чтобы освободить ресурсы:
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);
}
Первый вопрос: можно ли улучшить этот код, чтобы использовать меньше ресурсов?
Второй вопрос: как добавить текст в finalBitmap
(теперь это TextView
, который отображается в верхней части ImageView
с изображением)?
Ответы
Ответ 1
Отвечая на ваши вопросы: да, это можно сделать проще, гораздо проще:
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:
и это альтернативное решение (без центрирования, без текста внутри, просто концепция)
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();
}
}
Ответ 2
Вы можете использовать мой проект в качестве примера https://github.com/donvigo/CustomProgressControls. Он еще не задокументирован в github, но я надеюсь, что вы поймете мой код:)
РЕДАКТИРОВАТЬ: Текст внутри круга не TextView
, он был создан с помощью paint
.
Ответ 3
Я делаю нечто похожее - анимированный прогресс, как на вашей картинке, но меняется каждое второе состояние прогресса. Внутри у меня был текст времени, который оставался до истечения срока действия. Я просто поставлю здесь весь мой взгляд, возможно, это было бы полезно кому-то.
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);
}
}