Android: Custom circle ProgressBar with spaces in between - android

I am trying to create a custom view that has a Circle and in it, I have to have sections in run time as shown in the image below. I tried a lot of stuff in onDraw method but got no luck. I even tried https://github.com/donvigo/CustomProgressControls . Basically, I want to give a number of sections and then in each section I can select colors as per my need.
I am looking for ProgressBar that should have gap/space as shown in the image; in between circles. Say if I have given 5 sections, 3 of which should be "full", it should color the first 3 in red, and the other 2 in green, for example.
To draw I am doing like:
private void initExternalCirclePainter() {
internalCirclePaint = new Paint();
internalCirclePaint.setAntiAlias(true);
internalCirclePaint.setStrokeWidth(internalStrokeWidth);
internalCirclePaint.setColor(color);
internalCirclePaint.setStyle(Paint.Style.STROKE);
internalCirclePaint.setPathEffect(new DashPathEffect(new float[]{dashWith, dashSpace}, dashSpace));
}

I might be a little late to the party, but I actually wrote a custom component that has 2 rings that look quite similar to what you're trying to achieve. You can just remove the outer ring easily. The image of what I got in the end:
Here's the class:
public class RoundedSectionProgressBar extends View {
// The amount of degrees that we wanna reserve for the divider between 2 sections
private static final float DIVIDER_ANGLE = 7;
public static final float DEGREES_IN_CIRCLE = 360;
public static final int PADDING = 18;
public static final int PADDING2 = 12;
protected final Paint paint = new Paint();
protected final Paint waitingPaint = new Paint();
protected final Paint backgroundPaint = new Paint();
private int totalSections = 5;
private int fullSections = 2;
private int waiting = 3; // The outer ring. You can omit this
private RectF rect = new RectF();
public RoundedSectionProgressBar(Context context) {
super(context);
init(context, null);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RoundedSectionProgressBar(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
// Can come from attrs if need be?
int strokeWidth = 3;
setupPaint(context, strokeWidth, paint, R.color.filled_color_inner_ring);
setupPaint(context, strokeWidth, waitingPaint, R.color.empty_color_inner_ring);
setupPaint(context, strokeWidth, backgroundPaint, R.color.filled_color_outer_ring);
}
private void setupPaint(Context context, int strokeWidth, Paint backgroundPaint, int colorRes) {
backgroundPaint.setStrokeCap(Paint.Cap.SQUARE);
backgroundPaint.setColor(context.getResources().getColor(colorRes));
backgroundPaint.setAntiAlias(true);
backgroundPaint.setStrokeWidth(strokeWidth);
backgroundPaint.setStyle(Paint.Style.STROKE);
}
public int getTotalSections() {
return totalSections;
}
public void setTotalSections(int totalSections) {
this.totalSections = totalSections;
invalidate();
}
public int getFullSections() {
return fullSections;
}
public void setNumberOfSections(int fullSections, int totalSections, int waiting) {
this.fullSections = fullSections;
this.totalSections = totalSections;
this.waiting = waiting;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
rect.set(getLeft() + PADDING, getTop() + PADDING, getRight() - PADDING, getBottom() - PADDING);
float angleOfSection = (DEGREES_IN_CIRCLE / totalSections) - DIVIDER_ANGLE;
// Drawing the inner ring
for (int i = 0; i < totalSections; i++) {
// -90 because it doesn't start at the top, so rotate by -90
// divider_angle/2 especially in 2 sections, it's visibly rotated by Divider angle, so we split this between last and first
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
if (i < fullSections) {
canvas.drawArc(rect, startAngle, angleOfSection, false, paint);
} else {
canvas.drawArc(rect, startAngle, angleOfSection, false, backgroundPaint);
}
}
// Drawing the outer ring
rect.set(getLeft() + PADDING2, getTop() + PADDING2, getRight() - PADDING2, getBottom() - PADDING2);
for (int i = 0; i < waiting; i++) {
float startAngle = -90 + i * (angleOfSection + DIVIDER_ANGLE) + DIVIDER_ANGLE / 2;
canvas.drawArc(rect, startAngle, angleOfSection, false, waitingPaint);
}
}
}
Notice that this code won't give you the outer ring's 'empty' slots, since we decided against them in the end. The inner circle will have both the empty and filled slots. The whole class can be reused, and it's responsible just for the 2 rings that are drawn, the 6/6, +3 and the red circle are parts of another view.
The most important piece of the code is the onDraw method. It contains the logic for drawing the arcs in the for loop, as well as the logic for calculating the angles and adding spaces between them. Everything is rotated by -90 degrees, because I needed it to start at the top, rather than on the right, as it is the 0-degree angle in Android. It's not that complex, and you can modify it to fit your needs better should you need to.

I find it easier to do math for drawArc(operating on angle values based on number of sections) rather than computing the arc length.
Here's a quick idea, with a lot of hard-coded properties, but you should be able to get the idea:
public class MyStrokeCircleView extends View {
private Paint mPaint;
private RectF mRect;
private int mPadding;
private int mSections;
private int mFullArcSliceLength;
private int mColorArcLineLength;
private int mArcSectionGap;
public MyStrokeCircleView(Context context) {
super(context);
init(null, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs, 0);
}
public MyStrokeCircleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs, defStyle);
}
private void init(AttributeSet attrs, int defStyle) {
mPaint = new Paint();
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setColor(ContextCompat.getColor(getContext(), android.R.color.darker_gray));
mPadding = 5;
mRect = new RectF(mPadding, mPadding, mPadding, mPadding);
mSections = 4;
mFullArcSliceLength = 360 / mSections;
mArcSectionGap = mFullArcSliceLength / 10;
mColorArcLineLength = mFullArcSliceLength - 2 * mArcSectionGap;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRect.right = getWidth() - mPadding;
mRect.bottom = getHeight() - mPadding;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mSections; i++) {
canvas.drawArc(mRect, i * mFullArcSliceLength + mArcSectionGap, mColorArcLineLength, false, mPaint);
}
}
}

Related

How to use android canvas to draw a striped rectangle?

rectangles
I already drew the left rounded rectangle. I need to insert some lines horizontally for it to become a striped rectangle. How can I achieve this?
I already tried to use path and draw a line. But nothing worked how it was supposed to. Can someone help me?
Here's a sample code for drawing a striped rectangle in Android Canvas:
public class StripeRectView extends View {
private Paint mPaint;
private int mStripeWidth = 10;
private int mStripeHeight = 10;
public StripeRectView(Context context) {
super(context);
init();
}
public StripeRectView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public StripeRectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int x = 0;
int y = 0;
while (y <= getHeight()) {
canvas.drawRect(x, y, x + mStripeWidth, y + mStripeHeight, mPaint);
x += mStripeWidth * 2;
if (x > getWidth()) {
x = 0;
y += mStripeHeight * 2;
}
}
}
}
the Canvas is used to draw rectangles, with alternating stripes of width mStripeWidth and height mStripeHeight until the entire height of the View is filled. You can modify it according to your needs.

How to Convert a variable to a constant

I am new to android & java. This might be very simple but I'm stuck... I am creating an app where by clicking on an "add" button a square is added in some default position on a canvas. You can then move and resize the square. Once you finish moving/ reshaping, my intention is for the square to maintain its new size/ position and for the user to be able to click on the add button where a new square will appear in the original default position. You can then again move / reshape the second square. And eventually add as many squares as you like (each unique).
I have managed to add the first square and i am also able to move/ reshape. I also created an array list to store the squares. My issue is that as soon as i add the second square, the shape and position of the first square are reset to the default position and size. And if i move the second square, so does the first one. So effectively its as if i end up with a stacked set of squares that all move and reshape at the same time.
The question is, how do i convert the variable information of position/ size to a constant before i add the square to the array?
I have the following in my main activity
addBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(addTimer != null) {
addTimer.cancel();
}
if (addclickcount>0) {
addBoxtoList();
}
addBox();
addclickcount++;
}
});
public void addBox(){ mCustomView.drawRoundRect();}
public void addBoxtoList(){mCustomView.addBoxtoList();}
Then in CustomView
public class CustomView extends View {
private ArrayList<Box> boxesArray = new ArrayList<>();
private RectF mRectSquare;
private Paint mPaintSquare;
private static RectF aRectSquare;
private static Paint aPaintSquare;
private int mSquareLeft, mSquareTop, dx, dy;
int colorSelected;
public CustomView(Context context) {
super(context);
init(null);
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init (#Nullable AttributeSet set){
mRectSquare = new RectF();
mPaintSquare = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintSquare.setColor(Color.BLUE);
mPaintSquare.setStyle(Paint.Style.FILL_AND_STROKE);
mPaintSquare.setAlpha(85);
}
public void drawRoundRect(){
if (General.addBoxCheck == true) {
dx = 200;
dy = 200;
mSquareLeft = scrwd - dx - 50;
mSquareTop = 50;
General.addBoxCheck = false;
}
mRectSquare.left = mSquareLeft;
mRectSquare.top = mSquareTop;
mRectSquare.right = mRectSquare.left + dx;
mRectSquare.bottom = mRectSquare.top + dy;
postInvalidate();
}
public void addBoxtoList(){
boxid = boxesArray.size() + 1;
aRectSquare = mRectSquare;
aPaintSquare = mPaintSquare;
Box boxa = new Box(boxid, aRectSquare, aPaintSquare);
boxesArray.add(boxa);
//postInvalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawRoundRect(mRectSquare, 15, 15, mPaintSquare);
if(boxesArray != null) {
RectF rectF = new RectF();
Paint paint = new Paint();
for (int i = 0; i < boxesArray.size(); i++) {
paint = boxesArray.get(i).getPaint();
rectF = boxesArray.get(i).getRect();
canvas.drawRoundRect(rectF, 15, 15, paint);
}
}
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean value = super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:{
return true;
}
case MotionEvent.ACTION_MOVE:{
int x = (int) event.getX();
int y = (int) event.getY();
mSquareLeft = x;
mSquareTop = y;
postInvalidate();
return value;
}
}
return value;
}
Thanks
The question seems quite vague, if the values are changing then it means that you changed the values. If you don’t change the values of the array then essentially it should be a "constant". My assumption is that you are overwriting the array and therefore the value is changing. It would help to see your code.
Check the index of the stored square shapes and verify that you're not overwriting on any of the previous indexes.

How to use a custom view in main activity

I'm developing a game which shows shapes randomly, I got the class but I got no idea how to call the custom view from my GameFragment. I was wondering if anyone can help me to achieve it.
CustomView.java
public class CustomViews extends View {
private final int SQUARE_SIZE = 500;
private RectF rectF;
private Paint paint;
private boolean CIRCLE = false;
private boolean RECTANGLE = false;
public CustomViews(Context context) {
super(context);
init(null);
}
public CustomViews(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public CustomViews(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public CustomViews(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
private void init(#Nullable AttributeSet set) {
rectF = new RectF();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
randomColorGenerator();
}
public void rect(Canvas canvas) {
int height = canvas.getHeight() / 2;
int width = canvas.getWidth() / 2;
rectF.set(width - 200, height - 200, width + 200, height + 200);
}
private void circle(Canvas canvas) {
float cx;
float cy;
float radius = 200;
cx = canvas.getWidth() / 2;
cy = canvas.getWidth() / 2;
canvas.drawCircle(cx, cy, radius, paint);
}
private void randomColorGenerator() {
Random random = new Random();
int choices = 3;
switch (random.nextInt(choices)) {
case 0:
paint.setColor(Color.GREEN);
break;
case 1:
paint.setColor(Color.RED);
break;
case 2:
paint.setColor(Color.BLACK);
break;
}
}
public void randomShapeGenerator(Canvas canvas) {
Random random = new Random();
int numberOfMethods = 2;
switch (random.nextInt(numberOfMethods)) {
case 0:
rect(canvas);
rectSelected();
break;
case 1:
circle(canvas);
circleSelected();
break;
}
}
public boolean circleSelected(){
RECTANGLE = false;
return CIRCLE = true;
}
public boolean rectSelected( ) {
CIRCLE = false;
return RECTANGLE = true;
}
#Override
public void onDraw(Canvas canvas) {
randomShapeGenerator(canvas);
canvas.drawRect(rectF, paint);
}
}
Now I need to call it from my GameFragment where there are some buttons and whenever you click on them a new random shape must be generate. Is anyone got any idea?
Assuming your fragment has a LinearLayout with id main_content then this might help you to add your custom view programmatically:
Button btn = findViewById(R.id.yourbutton);
btn.setOnClickListener(v -> {
LinearLayout main_layout = findViewById(R.id.main_content);
CustomViews customView = new CustomViews(this);
customView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
main_layout.addView(customView);
});
layoutParams might be also tuned according your needs.
You have to take into account where you want to display the customViews and also their sizes. For instance, if you have a linear layout with vertical orientation, you can add views in the following way:
main_layout.addView(customView, 400, 400);
This will display multiple customViews one below the other (until the screen's height is reached).
Indeed, CustomViews has to be modified a bit (here are two methods to be changed):
public void rect(Canvas canvas) {
int height = canvas.getHeight() / 2;
int width = canvas.getWidth() / 2;
rectF.set(width - 200, height - 200, width + 200, height + 200);
canvas.drawRect(rectF, paint);
}
#Override
public void onDraw(Canvas canvas) {
randomShapeGenerator(canvas);
}

Unable to create a growing view

Im using the following code to animate the view when it is drawn...
public class MyView extends View {
int iCurStep = 0;// current animation step
class Points {
float x, y;
Points(float _x, float _y) {
x = _x;
y = _y;
}
}
Points[] drawPaths = {new Points(-75, 0), new Points(20, 60), new Points(60, 20)};
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
PathMeasure pm = new PathMeasure(path, false);
if (iCurStep <= 20) {
pm.getPosTan(fSegmentLen * iCurStep, afP, null);
path.moveTo(drawPaths[0].x, drawPaths[0].y);
for (int i = 1; i < drawPaths.length; i++) {
path.lineTo(drawPaths[i].y, drawPaths[i].y);
}
canvas.drawPath(path, paint);
iCurStep++;
} else {
iCurStep = 0;
}
}
}
What Im expecting is that, it has to be a growing view...I mean the view has to grow as it is being drawn...But Im not able to produce to do so...How can I be able to sort this out?
Use a ObjectAnimator.
Create your view as normal, but then create an ObjectAnimator (docs: https://developer.android.com/reference/android/animation/ObjectAnimator.html). You'll do something like this in your fragment or activity (you can make it in XML as well). Essentially you create an ObjectAnimator for an object with a property string. The property string must have a camelCase setter. So if you wanted to call view.setScaleX() as the property you were going to modify, you'd need to set the property string to 'scaleX'. Here's a simple example.
View growMe = new View(args);
float startSize = 1.0f;
float endSize = 2.0f;
ObjectAnimator grower = ObjectAnimator.ofFloat(growMe, "scaleX", startSize, endSize);
int durationMs = 1000;
grower.setDuration (durationMs)
grower.start();
The one other thing you'll need to do is add an AnimationListener to your View that actually adjusts the size to be the correct as the end of the animation as (I believe--I might be wrong on this part), the view will re-size after the animation ends.
I hope that helps!

Prevent ImageView redraw state after hide keyboard

First, I have a view like this:
When user press some text in edittext, I try to draw some rectangles in ImageView with specific coordinates. The problem is when the soft keyboard is hidden, all draw state of Imageview display wrong coordinates, look like it invalidated the imageview. I wonder is there any way to stop this.
Here is my Custom Drawable Imageview:
public class DrawableImageView extends ImageView {
private Paint ptBlur;
private List<Rect> rects;
private int originWidth;
private int originHeight;
public DrawableImageView(Context context) {
super(context);
initPaint();
}
public DrawableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public DrawableImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initPaint();
}
public void setRects(List<Rect> rects, int originWidth, int originHeight) {
this.originHeight = originHeight;
this.originWidth = originWidth;
this.rects = rects;
invalidate();
}
private void initPaint() {
ptBlur = new Paint();
ptBlur.setColor(Color.YELLOW);
ptBlur.setStyle(Paint.Style.STROKE);
ptBlur.setStrokeWidth(5);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (rects != null) {
int width = getWidth();
int height = getHeight();
for (Rect rect : rects) {
rect.bottom = Math.round(height * rect.bottom / originHeight);
rect.left = Math.round(width * rect.left / originWidth);
rect.right = Math.round(width * rect.right / originWidth);
rect.top = Math.round(height * rect.top / originHeight);
canvas.drawRect(rect, ptBlur);
}
}
}
}
The OnEditorActionListener for EditText:
// get the rectangles and draw
List<Rect> rects = DataUtil.getHighlightRectList(searchText);
drawableImageView.setRects(rects, pageWidth, pageHeight);

Categories

Resources