在Android SeekBar拇指上添加动态文本

21

我正在尝试在Android 2.2中制作自定义SeekBar,但是我所做的一切似乎都是错误的!我正在尝试在SeekBar的拇指图像上显示SeekBar的值。有人有相关经验吗?

11个回答

36

我采用了一种不同的方法,可以提供更多自定义拇指的可能性。 最终输出将如下所示:

enter image description here

首先,您需要设计布局,该布局将设置为拇指可绘制对象。

layout_seekbar_thumb.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="@dimen/seekbar_thumb_size"
        android:layout_height="@dimen/seekbar_thumb_size"
        android:background="@drawable/ic_seekbar_thumb_back"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvProgress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0"
            android:textColor="#000000"
            android:textSize="14sp" />

    </LinearLayout>

</LinearLayout>

这里的 seekbar_thumb_size 可以根据您的需求设置任何小的大小。我在这里使用了 30dp。对于背景,您可以使用任何可用的 drawable/icon。

现在,您需要将此视图设置为拇指 drawable,因此请使用以下代码获取它:

View thumbView = LayoutInflater.from(YourActivity.this).inflate(R.layout.layout_seekbar_thumb, null, false);

在这里,我建议在 onCreate() 中初始化此视图,这样就不需要再次膨胀它。

现在,在 seekBar 进度更改时将此视图设置为拇指 drawable。在您的代码中添加以下方法:

public Drawable getThumb(int progress) {
        ((TextView) thumbView.findViewById(R.id.tvProgress)).setText(progress + "");

        thumbView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        Bitmap bitmap = Bitmap.createBitmap(thumbView.getMeasuredWidth(), thumbView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        thumbView.layout(0, 0, thumbView.getMeasuredWidth(), thumbView.getMeasuredHeight());
        thumbView.draw(canvas);

        return new BitmapDrawable(getResources(), bitmap);
}

现在从onProgressChanged()方法中调用此方法。

seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

                // You can have your own calculation for progress                    
                seekBar.setThumb(getThumb(progress));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

注意:在初始化seekBar时,调用getThumb()方法以使用默认值进行初始化。

通过这种方法,您可以在进度更改时拥有任何自定义视图。


这种方法不是有点昂贵吗?因为每次SeekBar的值改变时,您都需要膨胀一个新布局并将其绘制到画布上。 - andrea.rinaldi
1
@AjayAujla,您可以进行初始化。请阅读我的最后一个注意事项。如果您有默认值,则可以在需要的任何地方调用getThumb()方法(例如,在视图的基本初始化之后的onCreate()中)。 :) - Chintan Shah
哦,很好,我错过了那个部分。感谢你的澄清! - AjCodez
非常好,运行得很顺利。 - Vinesh Chauhan
@humayoonsiddique,请问您将SeekBar的拇指设置为了什么? - Chintan Shah
显示剩余4条评论

21

我假设您已经扩展了基类,因此您应该拥有类似于以下内容的代码:

public class SeekBarHint extends SeekBar {
  public SeekBarHint (Context context) {
      super(context);
  }

  public SeekBarHint (Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
  }

  public SeekBarHint (Context context, AttributeSet attrs) {
      super(context, attrs);
  }
}

现在,您要使用自己的代码覆盖onDraw方法。插入以下内容:

@Override
protected void onDraw(Canvas c) {
    super.onDraw(c);
}

现在,你想在滑块附近绘制一些文本,但是没有方便的方法来获取滑块的x位置。我们只需要做一点数学运算。

@Override
protected void onDraw(Canvas c) {
    super.onDraw(c);
    int thumb_x = ( (double)this.getProgress()/this.getMax() ) * (double)this.getWidth();
    int middle = this.getHeight()/2;
    // your drawing code here, ie Canvas.drawText();
}

1
很好的提示!我认为drawText()调用应该在super调用之后。 - Stephen Niedzielski
1
每当拇指移动、滑块被触摸等情况发生时,onDraw方法都会被调用。 - Snailer
@Snailer,我也给你点赞了。你能否更新你的答案并提供完整解决方案?我也查看过这个问题,但无法正确理解它。https://dev59.com/nGox5IYBdhLWcg3wVS4J - TheFlash
我建议您使用强制类型转换到 **double**,如下所示:int thumb_x = (int)((double)(this.getProgress() / this.getMax()) * this.getWidth()); - Saro Taşciyan
6
你可以用这段代码替换计算thumb_x的代码 -> int thumb_x = this.getThumb().getBounds().exactCenterX() - muilpp
显示剩余6条评论

11
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
                int val = (progress * (seekBar.getWidth() - 2 * seekBar.getThumbOffset())) / seekBar.getMax();
                text_seekbar.setText("" + progress);
                text_seekbar.setX(seekBar.getX() + val + seekBar.getThumbOffset() / 2);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                text_seekbar.setVisibility(View.VISIBLE);

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                text_seekbar.setVisibility(View.GONE);
            }
        });

很棒的答案!如何在垂直滑动条中实现此功能。 - Naveen Kumar M
你在哪里定义了text_seekbar? - ciaranodc

6

嘿,我找到了另一种解决方案,似乎更简单:

 private void setText(){
    int progress = mSeekBar.getProgress();
    int max= mSeekBar.getMax();
    int offset = mSeekBar.getThumbOffset();
    float percent = ((float)progress)/(float)max;
    int width = mSeekBar.getWidth() - 2*offset;

    int answer =((int)(width*percent +offset - mText.getWidth()/2));
    mText.setX(answer);

}


@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    setText();
    mText.setText(""+progress);
}

当我们通过编程设置进度而不是通过拖动滑块来调用此方法时,它无法正常工作。 - Rishabh Srivastava
1
在您编写的程序中,通过调用setText()函数来动态更改bar的文本内容,问题就解决了! - Yakir Yehuda

6

以下代码可以使你的 TextView 居中对齐 SeekBar 的滑块中心。

在 xml 中,YOUR_TEXT_VIEW 的 width 必须设置为wrap_content。 希望这段代码能对你有所帮助。

@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
       YOUR_TEXT_VIEW.setText(Integer.toString(progress));
       double pourcent = progress / (double) seekBar.getMax();
       int offset = seekBar.getThumbOffset();
       int seekWidth = seekBar.getWidth();
       int val = (int) Math.round(pourcent * (seekWidth - 2 * offset));
       int labelWidth = YOUR_TEXT_VIEW.getWidth();
       YOUR_TEXT_VIEW.setX(offset + seekBar.getX() + val
                 - Math.round(pourcent * offset)
                 - Math.round(pourcent * labelWidth/2));
}

它可以工作,但文本没有居中并且超出了边界,你有解决方案吗? - Tamim Attafi

6
这对我有用。
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int val = (progress * (seekBar.getWidth() - 2 * seekBar.getThumbOffset())) / seekBar.getMax();
_testText.setText("" + progress);
_testText.setX(seekBar.getX() + val + seekBar.getThumbOffset() / 2);
}

如何为垂直拖动条实现此功能 - Naveen Kumar M

4
我使用这个库来创建可绘制的文本视图,并在编程中将该可绘制对象放入滑块中。 https://github.com/amulyakhare/TextDrawable 代码如下:
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
            String dynamicText = String.valueOf(progress);
            TextDrawable drawable = TextDrawable.builder()
                    .beginConfig()
                    .endConfig()
                    .buildRoundRect(dynamicText , Color.WHITE ,20);
            seekBar.setThumb(drawable);

        }
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {

        }
    });

3

我认为最好的方法是通过代码实现。这并不可怕,毕竟我们都是程序员 :)

class ThumbDrawable(context: Context) : Drawable() {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val textBounds = Rect()

    private var shadowColor = context.resources.getColor(R.color.wallet_screen_option_shadow)
    private val size = context.resources.getDimensionPixelSize(R.dimen.thumbRadius).toFloat()
    private val textSize = context.resources.getDimensionPixelSize(R.dimen.thumbTextSize).toFloat()
    var progress: Int = 0

    init {
        textPaint.typeface = Typeface.createFromAsset(context.assets, "font/avenir_heavy.ttf")
        val accentColor = context.resources.getColor(R.color.accent)
        paint.color = accentColor
        textPaint.color = accentColor
        textPaint.textSize = textSize
        paint.setShadowLayer(size / 2, 0f, 0f, shadowColor)
    }

    override fun draw(canvas: Canvas) {
        Timber.d("bounds: $bounds")
        val progressAsString = progress.toString()
        canvas.drawCircle(bounds.left.toFloat(), bounds.top.toFloat(), size, paint)
        textPaint.getTextBounds(progressAsString, 0, progressAsString.length, textBounds)
        //0.6f is cause of the avenirs spacing, should be .5 for proper font
        canvas.drawText(progressAsString, bounds.left.toFloat() - textBounds.width() * 0.6f, bounds.top.toFloat() - size * 2, textPaint)
    }

    override fun setAlpha(alpha: Int) {
    }

    override fun getOpacity(): Int {
        return PixelFormat.OPAQUE
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
    }

}

在您的进度条实现中,
class CustomSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
        SeekBar(context, attrs, defStyleAttr) {

    init {
        thumb = ThumbDrawable(context)
        setPadding(0, 0, 0, 0);
    }

    override fun invalidate() {
        super.invalidate()
        if (thumb is ThumbDrawable) (thumb as ThumbDrawable).progress = progress

    }



}

最终结果类似于这样:seek bar implementation result。该结果与滑动条的实现有关。

3

对我来说效果不错
有一些硬编码)
请写出可能有的改进方案

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v7.widget.AppCompatSeekBar;
import android.util.AttributeSet;
import android.util.TypedValue;

public class CustomSeekBar extends AppCompatSeekBar {

    @SuppressWarnings("unused")
    private static final String TAG = CustomSeekBar.class.getSimpleName();

    private Paint paint;
    private Rect bounds;

    public String dimension;

    public CustomSeekBar(Context context) {
        super(context);
        init();
    }

    public CustomSeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomSeekBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init(){
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setTextSize(sp2px(14));

        bounds = new Rect();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        String label = String.valueOf(getProgress()) + dimension;
        paint.getTextBounds(label, 0, label.length(), bounds);
        float x = (float) getProgress() * (getWidth() - 2 * getThumbOffset()) / getMax() +
            (1 - (float) getProgress() / getMax()) * bounds.width() / 2 - bounds.width() / 2
            + getThumbOffset() / (label.length() - 1);
        canvas.drawText(label, x, paint.getTextSize(), paint);
    }

    private int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
    }
}

这段代码中的dimension参数是什么意思,因为这个字符串参数没有任何字符串值。 - Kishan Donga

0
我创建了这个示例来展示如何支持不同类型的屏幕尺寸以及如何计算Thumb的实际位置,因为有时位置可能会是0。
public class CustomProgressBar extends RelativeLayout implements AppCompatSeekBar.OnSeekBarChangeListener {

    @BindView(R.id.userProgressBar)
    protected AppCompatSeekBar progressSeekBar;
    @BindView(R.id.textPorcent)
    protected TextView porcent;
    @BindView(R.id.titleIndicator)
    protected TextView title;

    public CustomProgressBar(Context context) {
        super(context);
        init();
    }

    public CustomProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.custom_progressbar_view, this, true);
        ButterKnife.bind(this);
        setColors(R.color.green, R.color.progress_bar_remaining);
        progressSeekBar.setPadding(0, 0, 0, 0);
        progressSeekBar.setOnSeekBarChangeListener(this);
    }

    private void setPorcentTextViewPosition(float widthView) {
        int width = CoreUtils.getScreenSize().x;
        float xPosition = ((float) progressSeekBar.getProgress() / 100) * width;
        float finalPosition = xPosition - (widthView / 2f);
        if (width - xPosition < widthView) {
            porcent.setX(width - widthView);
        } else if (widthView < finalPosition) {
            porcent.setX(finalPosition);
        }
    }

    public void setColors(int progressDrawable, int remainingDrawable) {
        LayerDrawable layer = (LayerDrawable) progressSeekBar.getProgressDrawable();
        Drawable background = layer.getDrawable(0);
        Drawable progress = layer.getDrawable(1);

        background.setColorFilter(ContextCompat.getColor(getContext(), remainingDrawable), PorterDuff.Mode.SRC_IN);
        progress.setColorFilter(ContextCompat.getColor(getContext(), progressDrawable), PorterDuff.Mode.SRC_IN);
    }

    public void setValues(int progress, int remaining) {
        int value = (progress * remaining) / 100;
        progressSeekBar.setMax(remaining);
        porcent.setText(String.valueOf(value).concat("%"));

        porcent.post(new Runnable() {
            @Override
            public void run() {
                setPorcentTextViewPosition(porcent.getWidth());
            }
        });
        progressSeekBar.setProgress(value);
    }

    public void setTitle(String title) {
        this.title.setText(title);
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接