Реализация этого с использованием обычного индикатора выполнения и вертикального перемещения делает все слишком сложным. Поэтому мое решение состоит в том, чтобы создать собственное представление, которое состоит из представления с фоном ClipDrawable для прогресса и TextView для метки.
Полный код:
LabeledVerticalProgressBar. java
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;
}
}
}
attrs. xml
<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);
}
Результаты:
Вы можете использовать setProgess()
, чтобы установить прогресс без анимации или передать false
в animate
параметр.
На данный момент я не могу создать видимость текста для работы надписи .
Я думаю, что с помощью нескольких правок вы также можете сделать это по горизонтали.