androidandroid-drawableandroid-progressbarandroid-seekbarandroid-paint

Custom Progress Bar's drawable does not fit


I am trying to implement the below progress bar: enter image description here

and the result I am getting is: enter image description here

The problem here is that the lines do not fit in the progress bar as it is clear in the left and right side of the image. The reason is that I am using the progress bar's height and width to draw the lines, but the progress bar has a corner radius which is not considered when getting the height and width.'

I tried adding paddings to the progrss bar, but I don't want any empty space as seen in the first image above.

Here are my main classes:

ProgressDrawable.java

class ProgressDrawable extends Drawable {
private static final int NUM_LINES = 60;

private Paint mPaint = new Paint();
private ProgressBar progressBar;

public ProgressDrawable(Context context, ProgressBar progressBar){
    super();
    init(context);

    this.progressBar = progressBar;
}

@Override
protected boolean onLevelChange(int level) {
    invalidateSelf();
    return true;
}

@Override
public void draw(Canvas canvas) {
    float width = progressBar.getWidth();
    float height = progressBar.getHeight();
    for (int i = 0; i < NUM_LINES; i++) {
        float startX = width * i / NUM_LINES;
        float stopX = startX + 2f * width / NUM_LINES;
        //mPaint.setColor((i + 1) * 10000 / NUM_LINES <= getLevel()? 0xff888888 : 0xCACACA);
        mPaint.setShader((i + 1) * 10000 / NUM_LINES <= getLevel()? new LinearGradient(0, 0, 0, height, Color.parseColor("#77F5FF"), Color.parseColor("#31AFF5"), Shader.TileMode.MIRROR) : new LinearGradient(0, 0, 0, height, Color.parseColor("#D9D9D9"), Color.parseColor("#D9D9D9"), Shader.TileMode.MIRROR));
        canvas.drawLine(startX, height, stopX, 0, mPaint);
    }
}

@Override
public void setAlpha(int alpha) {
}

@Override
public void setColorFilter(ColorFilter cf) {
}

@Override
public int getOpacity() {
    return PixelFormat.TRANSLUCENT;
}

private void init(Context context)
{
    Resources resources = context.getResources();
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStrokeWidth(resources.getDimension(R.dimen.vertical_divider_width));
}
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

LinearLayout mainLayout;
ProgressBar pb;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mainLayout = findViewById(R.id.mainLayout);

    LinearLayout ll = new LinearLayout(this);
    ll.setOrientation(LinearLayout.VERTICAL);

    mainLayout.addView(ll);

    pb = findViewById(R.id.progressBar);
    Drawable d = new ProgressDrawable(this, pb);
    pb.setProgressDrawable(d);

    SeekBar.OnSeekBarChangeListener l = new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            int newProgress = pb.getMax() * progress / seekBar.getMax();
            Log.d(MainActivity.class.getSimpleName(), "onProgressChanged " + newProgress);
            pb.setProgress(newProgress);
        }
    };

    startProgress();
}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" android:orientation="vertical" android:gravity="center">

<ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="600dp"
        android:layout_height="50dp"
        android:max="60"
        style="?android:attr/progressBarStyleHorizontal"
        android:background="@drawable/rounded_layout"/>
</LinearLayout>

rounded_layout.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
   android:shape="rectangle">
   <solid android:color="#D9D9D9"/>
   <corners android:radius="15dp" />
</shape>

Any help will be highly appreciated.

Thanks in advance.


Solution

  • Thanks for @0X0nousugar's answer to this question.

    Clipping the canvas was the right way, but my main mistake is that I was drawing the lines with canvas the progress bar's rounded shape is done in xml. My solution was to remove the rounded_layout.xml as background of the preogress bar, draw a clipped rectangle and then make sure to draw the lines inside it.

    Below is the code snippet:

    @Override
    public void draw(Canvas canvas) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        // get the height and width of the progress bar
        float width = progressBar.getWidth();
        float height = progressBar.getHeight();
        Path rectPath = new Path();
        RectF rect = new RectF(0, 0, 0,0);
        // set the size of the rectangle to the size of the progress bar
        rect.set(0, 0, width, height);
        // set the corner radius (mine is 15) to have a rounded rectangle
        rectPath.addRoundRect(rect, 15, 15, Path.Direction.CW);
        // set the color for the rectangle
        mPaint.setColor(Color.parseColor("#D9D9D9"));
        // clip and draw the rectangle
        canvas.clipPath(rectPath);
        canvas.drawRect(rect, mPaint);
        // a loop to create the lines inside the rectangle, one at a time
        for (int i = 0; i < NUM_LINES; i++) {
            float startX = width * i / NUM_LINES;
            float stopX = startX + 2f * width / NUM_LINES;
            mPaint.setShader((i + 1) * 10000 / NUM_LINES <= getLevel()? 
                             new LinearGradient(0, 0, 0, height, 
                             Color.parseColor("#77F5FF"), Color.parseColor("#31AFF5"), 
                             Shader.TileMode.MIRROR) : new LinearGradient(0, 0, 0, 
                             height, Color.parseColor("#D9D9D9"), 
                             Color.parseColor("#D9D9D9"), Shader.TileMode.MIRROR));
                             canvas.drawLine(startX, height, stopX, 0, mPaint);
        }
    }
    

    Note: Make sure that all the lines are drawn with in the rectangle.