I have a project I'm working on, in this project I'm using progressBars as a chart of bars,
that is: I get a list of items, then I check which item has the max value and I set that max value to be the max progress of all the progressbars(items),
then I would like to show each of the items value (progress), but I want to attach it to the progress of the specific item, in other words I would like to show the progress value at the end of each progress of each progressBar,
Example: max progress 29, dont mind the percent value or the labels below
(source: datasciencecentral.com)
I've managed to make the progressbars vertical after some searching, but this issue I'm struggling to resolve,
Is there a way to attach something to the progress of a progress bar? is there a way to reference the progress in xml? (The max value and the different items progress is only known at runtime, and they remain static after)
thx.
Implementing this using the normal progress bar and making it vertical makes everything too complicated. So my solution is to create a custom view which consists of a view with a ClipDrawable background for progress and a TextView for label.
Full code:
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID;
public class LabeledVerticalProgressBar extends ConstraintLayout {
private TextView textView;
private View progressView;
private Drawable progressDrawable;
private float min;
private float max;
private float progress;
private int labelTextAppearanceId;
private boolean isLabelAbove = true;
private int numDecimals;
private String unit = "";
private int height;
public LabeledVerticalProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
private void init(AttributeSet attrs) {
initViews();
initAttributes(attrs);
initProgress();
initLabel();
}
private void initViews() {
progressView = new View(getContext());
progressView.setId(View.generateViewId());
LayoutParams progressParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
progressParams.topToTop = PARENT_ID;
progressParams.bottomToBottom = PARENT_ID;
progressParams.startToStart = PARENT_ID;
progressParams.endToEnd = PARENT_ID;
progressView.setLayoutParams(progressParams);
textView = new TextView(getContext());
int padding = (int)(4 * getResources().getDisplayMetrics().density);
textView.setPadding(padding, padding, padding, padding);
LayoutParams textParams = new LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
textParams.startToStart = progressView.getId();
textParams.endToEnd = progressView.getId();
textParams.bottomToBottom = progressView.getId();
textView.setLayoutParams(textParams);
addView(progressView);
addView(textView);
setClipChildren(false);
setClipToPadding(false);
}
private void initAttributes(AttributeSet attrs) {
TypedArray a = getContext().getTheme().obtainStyledAttributes(
attrs,
R.styleable.LabeledVerticalProgressBar,
0, 0);
try {
progressDrawable = a.getDrawable(R.styleable.LabeledVerticalProgressBar_progress_drawable);
min = a.getFloat(R.styleable.LabeledVerticalProgressBar_min, 0);
max = a.getFloat(R.styleable.LabeledVerticalProgressBar_max, 100);
progress = a.getFloat(R.styleable.LabeledVerticalProgressBar_progress, 0);
initTextAppearance(a);
int labelPos = a.getInt(R.styleable.LabeledVerticalProgressBar_label_position, 0);
if (labelPos == 1) {
isLabelAbove = false;
}
numDecimals = a.getInt(R.styleable.LabeledVerticalProgressBar_num_decimals, 0);
unit = a.getString(R.styleable.LabeledVerticalProgressBar_unit);
if (min >= max) {
throw new IllegalArgumentException("max should be greater than min");
}
clampProgress();
} finally {
a.recycle();
}
}
private void initTextAppearance(TypedArray a) {
/*TypedValue styleId = new TypedValue();
boolean resolved = getContext().getTheme().resolveAttribute(R.styleable.LabeledVerticalProgressBar_label_text_appearance,
styleId, true);
if (resolved) {
labelTextAppearanceId = styleId.data;
} else {
labelTextAppearanceId = -1;
}*/
ColorStateList color = a.getColorStateList(R.styleable.LabeledVerticalProgressBar_label_text_color);
int size = a.getDimensionPixelSize(R.styleable.LabeledVerticalProgressBar_label_text_size, -1);
int style = a.getInt(R.styleable.LabeledVerticalProgressBar_label_text_style, -1);
Drawable background = a.getDrawable(R.styleable.LabeledVerticalProgressBar_label_background);
if (color != null) {
textView.setTextColor(color);
}
if (size != -1) {
textView.setTextSize(size);
}
if (style != -1) {
switch (style) {
case 0: textView.setTypeface(textView.getTypeface(), Typeface.NORMAL); break;
case 1: textView.setTypeface(textView.getTypeface(), Typeface.BOLD); break;
case 2: textView.setTypeface(textView.getTypeface(), Typeface.ITALIC); break;
case 3: textView.setTypeface(textView.getTypeface(), Typeface.BOLD_ITALIC); break;
}
}
if (background != null) {
textView.setBackground(background);
}
}
private void initProgress() {
if (progressDrawable == null) {
progressDrawable = initDefaultProgressDrawable();
}
updateProgressView();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
height = progressView.getMeasuredHeight();
initLabel();
if (isLabelAbove) {
// Add top space for label when progress is at max.
setPadding(0, textView.getMeasuredHeight(), 0, 0);
}
super.onLayout(changed, left, top, right, bottom);
}
private void initLabel() {
updateLabel();
/*if (labelTextAppearanceId != -1) {
TextViewCompat.setTextAppearance(textView, labelTextAppearanceId);
}*/
}
public void setProgress(float progress) {
this.progress = progress;
clampProgress();
progressView.getBackground().setLevel(computeLevel());
updateLabel();
}
public void setProgress(final float progress, boolean animate, int duration) {
if (animate) {
float end = clampProgress(progress);
float start = this.progress;
ValueAnimator animator = ValueAnimator.ofFloat(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
LabeledVerticalProgressBar.this.progress = (float) animation.getAnimatedValue();
progressView.getBackground().setLevel(computeLevel());
updateLabel();
}
});
animator.setDuration(duration);
animator.start();
} else {
setProgress(progress);
}
}
public void setMax(float max) {
this.max = max;
if (min >= max) {
throw new IllegalArgumentException("max should be greater than min");
}
setProgress(progress);
}
public void setMin(float min) {
this.min = min;
if (min >= max) {
throw new IllegalArgumentException("max should be greater than min");
}
setProgress(progress);
}
public void setUnit(String unit) {
this.unit = unit;
}
public void setProgressDrawableColor(int color) {
progressDrawable = new ColorDrawable(color);
updateProgressView();
}
public void setProgressDrawable(Drawable progressDrawable) {
this.progressDrawable = progressDrawable;
updateProgressView();
}
private void updateProgressView() {
ClipDrawable clip = new ClipDrawable(progressDrawable, Gravity.BOTTOM, ClipDrawable.VERTICAL);
progressView.setBackground(clip);
clip.setLevel(computeLevel());
}
private void updateLabel() {
float translation = computeLabelTranslation();
if (isLabelAbove) {
textView.setTranslationY(translation);
} else {
if (-translation > textView.getMeasuredHeight()) {
textView.setTranslationY(translation + textView.getMeasuredHeight());
} else {
textView.setTranslationY(0);
}
}
String progressStr = String.format("%." + numDecimals + "f", progress) + unit;
textView.setText(progressStr);
}
private int computeLevel() {
float fraction = computeProgressFraction();
return (int) (fraction * 10000);
}
private float computeLabelTranslation() {
return -computeProgressFraction() * height;
}
private float computeProgressFraction() {
clampProgress();
return (progress - min) / (max - min);
}
private Drawable initDefaultProgressDrawable() {
int colorAttr = getContext().getResources().getIdentifier("colorPrimary",
"attr", getContext().getPackageName());
TypedValue outValue = new TypedValue();
getContext().getTheme().resolveAttribute(colorAttr, outValue, true);
return new ColorDrawable(outValue.data);
}
private void clampProgress() {
if (progress > max) {
progress = max;
} else if (progress < min) {
progress = min;
}
}
private float clampProgress(float progress) {
if (progress > max) {
return max;
} else if (progress < min) {
return min;
} else {
return progress;
}
}
}
<resources>
<declare-styleable name="LabeledVerticalProgressBar">
<attr name="progress_drawable" format="reference|color"/>
<attr name="min" format="float"/>
<attr name="max" format="float"/>
<attr name="progress" format="float"/>
<attr name="label_text_appearance" format="reference"/>
<attr name="label_text_color" format="color"/>
<attr name="label_text_size" format="dimension"/>
<attr name="label_text_style" format="enum">
<enum name="normal" value="0"/>
<enum name="bold" value="1"/>
<enum name="italic" value="2"/>
<enum name="bold_italic" value="3"/>
</attr>
<attr name="label_background" format="reference|color"/>
<attr name="label_position" format="enum">
<enum name="above" value="0"/>
<enum name="below" value="1"/>
</attr>
<!--Number of displayed decimal places for label-->
<attr name="num_decimals" format="integer"/>
<attr name="unit" format="string"/>
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<marabillas.loremar.bindtextviewandprogressbar.LabeledVerticalProgressBar
android:id="@+id/progressBar"
android:layout_width="80dp"
android:layout_height="480dp"
android:background="#ddd"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:progress_drawable="#6a0dad"
app:label_text_color="#fff"
app:label_text_size="24sp"
app:label_text_style="bold"
app:label_background="#4ccc"
app:label_position="below"
app:min="20"
app:max="500"
app:progress="20"
app:num_decimals="0"
app:unit="%"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@Override
protected void onStart() {
super.onStart();
final LabeledVerticalProgressBar progressBar = findViewById(R.id.progressBar);
progressBar.setProgress(500, true, 3000);
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
progressBar.setProgressDrawableColor(Color.GREEN);
}
}, 2000);
handler.postDelayed(new Runnable() {
@Override
public void run() {
GradientDrawable gradient = new GradientDrawable();
int color1 = Color.HSVToColor(new float[]{240f, 1f, 1f});
int color2 = Color.HSVToColor(new float[]{240f, 1f, 0.5f});
int[] colors = { color1, color2, color1};
gradient.setColors(colors);
gradient.setGradientType(GradientDrawable.LINEAR_GRADIENT);
gradient.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
progressBar.setProgressDrawable(gradient);
progressBar.setMax(2000);
progressBar.setProgress(4000);
progressBar.setMin(1000);
progressBar.setUnit("");
progressBar.setProgress(1500, true, 3000);
}
}, 3000);
}
You can use setProgess()
to set progress without animation or pass false
to animate
parameter.
As for now, I can't seem to make text appearance for label work.
I think with some few edits, you can also make this horizontal.