I'm trying the (surely simple?) task of having a TextView follow the thumb on a progress bar and show the progress in the TextView.
The problem is that for progress values of less than half max, the TextView drifts to the left of the thumb, getting more and more left of the correct place and vice versa with progress values more than half max the TextView drifts more and more to the right.
Below is a version of code that reproduces the problem...
Layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".Seekbar_test" >
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="59"
android:layout_marginTop="24dp" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
And .java
package com.example.seekbar_test;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
public class Seekbar_test extends Activity {
SeekBar fade_seek;
TextView fade_text;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_seekbar_test);
fade_seek = (SeekBar) findViewById(R.id.seekBar1);
fade_text = (TextView) findViewById(R.id.textView1);
fade_seek.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
say_minutes_left(progress);
}
});
}
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int seek_label_pos = (int)((float)(fade_seek.getMeasuredWidth()) * ((float)how_many / 60f));
fade_text.setX(seek_label_pos);
}
}
This works for me:
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int seek_label_pos = (((fade_seek.getRight() - fade_seek.getLeft()) * fade_seek.getProgress()) / fade_seek.getMax()) + fade_seek.getLeft();
if (how_many <=9)
{
fade_text.setX(seek_label_pos - 6);
}
else
{
fade_text.setX(seek_label_pos - 11);
}
}
Thanks to Pushpendra Kuntal & flightplanner # unable to get right position of texBox in Thumb of seekbar
The following solution works for API >= 15:
Override SeekBar to create the getThumb() method that is not available on api 15:
public class CustomSeekBar extends SeekBar {
private Drawable thumb;
public CustomSeekBar(Context context) {
super(context);
}
public CustomSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
this.thumb = thumb;
}
#Override
public Drawable getThumb() {
return thumb;
}
}
And use it Like this:
correctX = customSeekBar.getThumb().getBounds().left;
//Get the thumb bound and get its left value
int x = seekBar.getThumb().getBounds().left;
//set the left value to textview x value
textView.setX(x);
use this to move text view according to progress
Works good with Ken Nichols code, but here is a better decision, for example, to center you TextView to current position in ProgressBar(SeekBar):
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int seek_label_pos = (((fade_seek.getRight() - fade_seek.getLeft()) * fade_seek.getProgress()) / fade_seek.getMax()) + fade_seek.getLeft();
fade_text.setX(seek_label_pos - fade_text.getWidth() / 2);
}
If you have padding in your ProgressBar(SeekBar), you can use more general version of this code:
private void say_minutes_left(int how_many)
{
String what_to_say = String.valueOf(how_many);
fade_text.setText(what_to_say);
int left = fade_seek.getLeft() + fade_seek.getPaddingLeft();
int right = fade_seek.getRight() - fade_seek.getPaddingRight();
int seek_label_pos = (((right - left * fade_seek.getProgress()) / fade_seek.getMax()) + left;
fade_text.setX(seek_label_pos - fade_text.getWidth() / 2);
}
Use this , we are calculating the x position based on progress and setting it to textview
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
var xPosition= (((seekBar1.right - seekBar1.left) / seekBar1.max) * seekBar1.progress ) + seekBar1.left
textView1.translationX = xPosition.toFloat() - (textView1.width/2)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
})
TextView must be
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
and remove it:
int seek_label_pos = (int)((float)(fade_seek.getMeasuredWidth()) * ((float)how_many / 60f));
fade_text.setX(seek_label_pos);
Hope it's help.
Related
Context: I want to build a horizontal progress bar with some values and want to put some text that move along below this progress bar. So my final ideia is something like this (I already have the progress bar built I only left the text views below)
My current code is something like this:
This is my StackedHorizontalProgressBar class:
public class StackedHorizontalProgressBar extends ProgressBar {
private Paint paint;
int primary_progress, max_value;
public StackedHorizontalProgressBar(Context context) {
super(context);
init();
}
public StackedHorizontalProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
#Override public synchronized void setMax(int max) {
this.max_value = max;
super.setMax(max);
}
#Override public synchronized void setProgress(int progress) {
if (progress > max_value) {
progress = max_value;
}
this.primary_progress = progress;
super.setProgress(progress);
}
#Override public synchronized void setSecondaryProgress(int secondaryProgress) {
if ((primary_progress + secondaryProgress) > max_value) {
secondaryProgress = (max_value - primary_progress);
}
super.setSecondaryProgress(primary_progress + secondaryProgress);
}
private void init() {
paint = new Paint();
paint.setColor(Color.BLACK);
primary_progress = 0;
max_value = 100;
}
}
This is my Main Activity layout (that uses above class)
<android.support.constraint.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"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity">
<github.nisrulz.stackedhorizontalprogressbar.StackedHorizontalProgressBar
android:id="#+id/stackedhorizontalprogressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:progressDrawable="#drawable/stacked_horizontal_progress"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="16dp" />
</android.support.constraint.ConstraintLayout>
This is my MainActivity class:
public class MainActivity extends AppCompatActivity {
int max = 100;
int countPrimary = 20;
int countSecondary = 30;
StackedHorizontalProgressBar stackedHorizontalProgressBar;
TextView txt_primary, txt_secondary;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
stackedHorizontalProgressBar =
(StackedHorizontalProgressBar) findViewById(R.id.stackedhorizontalprogressbar);
stackedHorizontalProgressBar.setMax(max);
stackedHorizontalProgressBar.setProgress(countPrimary);
txt_primary.setText("Primary Value : " + countPrimary + "%");
stackedHorizontalProgressBar.setSecondaryProgress(countSecondary);
txt_secondary.setText("Secondary Value : " + countSecondary + "%");
}
}
I think I must use canvas for this, so if there is anyone with experience with this that can help me.
public class ProgressBarAvtivity extends AppCompatActivity {
ProgressBar progressBar;
TextView tvProgress;
int d = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_progress_bar_avtivity);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
progressBar.setProgress(d);
tvProgress = (TextView)findViewById(R.id.tvProgress);
tvProgress.setText(String.valueOf(d));
if (d > 40) {
tvProgress.setX(((width )* d / 100) - (d/2));
}
else {
tvProgress.setX(((width )* d / 100));
}
}}
Android recently added the support for resizing TextViews text size based on the view size and the min and max text size.
https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html
Unfortunately, they don't support EditTexts, so Is there any other alternatives for EditText?
I was stuck as you, how EditText is child of TextView but don't support autosize ¿?¿?
I have achieve this with some kind of hack.
First I saw the TextView code to copy and implement as extension (in Kotlin) on EditTextView, but.. there is a bulk of methods, so endly I discard that option.
What I have do, it's to use a TextView invisible (yes I know is a complete hack, am not very happy with that but Google should be ashamed about this)
This is my xmls
<TextView android:id="#+id/invisibleTextView"
android:layout_height="0dp"
android:layout_width="match_parent"
android:focusable="false"
app:autoSizeTextType="uniform"
app:autoSizeMinTextSize="#dimen/text_min"
app:autoSizeMaxTextSize="#dimen/text_max"
app:autoSizeStepGranularity="#dimen/text_step"
android:textAlignment="center"
app:layout_constraintLeft_toLeftOf="#id/main"
app:layout_constraintRight_toRightOf="#id/main"
app:layout_constraintTop_toBottomOf="#id/textCount"
app:layout_constraintBottom_toBottomOf="#id/main"
android:visibility="invisible"
tool:text="This is a Resizable Textview" />
<EditText android:id="#+id/resizableEditText"
android:layout_height="0dp"
android:layout_width="match_parent"
android:textAlignment="center"
app:layout_constraintLeft_toLeftOf="#id/main"
app:layout_constraintRight_toRightOf="#id/main"
app:layout_constraintTop_toBottomOf="#id/textCount"
app:layout_constraintBottom_toBottomOf="#id/main"
android:maxLength="#integer/max_text_length"
tool:text="This is a Resizable EditTextView" />
Note: It's important both view have the same width/height
Then on my code I use the autocalculations from this textview to use on my EditTextView.
private fun setupAutoresize() {
invisibleTextView.setText("a", TextView.BufferType.EDITABLE) //calculate the right size for one character
resizableEditText.textSize = autosizeText(invisibleTextView.textSize)
resizableEditText.setHint(R.string.text_hint)
resizableEditText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
resizableEditText.textSize = autosizeText(invisibleTextView.textSize)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
textCount.text = currentCharacters.toString()
val text = if (s?.isEmpty() ?: true) getString(R.string.text_hint) else s.toString()
invisibleTextView.setText(text, TextView.BufferType.EDITABLE)
}
})
}
private fun autosizeText(size: Float): Float {
return size / (resources.displayMetrics.density + MARGIN_FACTOR /*0.2f*/)
}
As note, for change the size of hint i use this Android EditText Hint Size.
I know it's a hard workaround, but at least we are sure this will continue working even when resizable change on future versions, while a propetary or abandoned github lib will fail.
I hope some day, google hear us and implement on childs this wonderfull feature, and we could avoid all this stuff
Hope this helps
This library is based on:
AutoFitTextView https://github.com/ViksaaSkool/AutoFitEditText
Please try this one
You can use a RelativeLayout to hide a TextView behind an EditText, both of which will have equal height and width.
The TextView will have android:autoSizeTextType="uniform"
By using setOnTextChangedListener on the EditText, you can set the text of the TextView to whatever is in the EditText. Then the text size of the TextView will be adjusted automatically. Then you have to set the text size of the EditText as same as that of the TextView. Here is the layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools: context=".MainActivity">
<TextView
android:id="#+id/test"
android:layout_width="match_parent" android:layout_height="match_parent" android:padding="0dp"
android:layout_margin="0dp"
android: autoSizeTextType="uniform"
android: autosizeMaxTextSize="500sp" android: autosizestepGranularity="1sp"/>
<EditText
android:id="#+id/edit"
android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="0dp"/>
android:padding="0dp"
</RelativeLayout>
And the code:
public void code(){
edit.setMovement Method (null);
edit.addTextChangedListener(new Textwatcher() {
#Override
public void onTextChanged (CharSequence s, int start, int before, int count) { test.setText (edit.getText().tostring()); edit.setTextSize(pixel2dip(test.getTextSize()));
}
public static float pixel2dip(float a) {
int b = (int) (a);
int c = (int) (b / Resources.getSystem().getDisplayMetrics ().scaledDensity); return (float) (c);
});
}
Here is an option for single-line EditText views that computes the text width after every change and scales up/down the textScale as needed, adhering to a max and min size.
Perhaps someone can improve it to have it work for multiline EditTexts.
// set up auto-text-size
val maxTextScale = binding.editText.textSize
val minTextScale = 0.2 * maxTextScale // ensure text doesn't get too small.
binding.editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val paint = TextPaint(binding.editText.paint)
val desiredTextWidth = StaticLayout.getDesiredWidth(s, paint)
// added so that the text isn't slightly too big
val ensureWiggleRoom = 0.95F
val scaleFactor = binding.editText.width / desiredTextWidth
val candidateTextSize = truncate(binding.editText.textSize * scaleFactor * ensureWiggleRoom)
if (candidateTextSize > minTextScale && candidateTextSize < maxTextScale) {
binding.editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, candidateTextSize)
}
}
override fun afterTextChanged(s: Editable?) {}
})
It works for me. In xml add:
<EditText
android:id="#+id/editTextMsg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.1"
android:background="#drawable/edit_corner_white"
android:gravity="start|top"
android:inputType="textMultiLine"
android:minLines="1"
android:lines="1"
android:maxLines="20"
android:textSize="18dp"
android:padding="10dp"
android:hint="#string/napisz_cos"
android:imeOptions="actionSend"
/>
and then in activity code:
editTextMsg.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
int div = editTextMsg.getText().toString().length() % 25; //max 25chars in line
if(div == 0 ) {
String tx = editTextMsg.getText().toString().trim() + "\n";
editTextMsg.setText(tx);
int pxH = (int)( editTextMsg.getLineCount() * 24 * getResources().getDisplayMetrics().density);
editTextMsg.setHeight (pxH );
//set cursor to end of text..
editTextMsg.setSelection(editTextMsg.length());
}
}
});
You can try my AutoSizeEditText:
/**
* Wrapper class for {#link EditText}.
* It helps to achieve auto size behaviour which exists in {#link AppCompatTextView}.
* The main idea of getting target text size is measuring {#link AppCompatTextView} and then
* extracting from it text size and then applying extracted text size to target {#link EditText}.
*/
public class AutoSizeEditText extends FrameLayout {
private static final String TEST_SYMBOL = "T";
private static final boolean TEST = false;
/**
* Vertical margin which is applied by default in {#link EditText} in
* comparison to {#link AppCompatTextView}
*/
private static final float VERTICAL_MARGIN = convertDpToPixel(4);
/**
* {#link TextMeasure} which helps to get target text size for {#link #wrappedEditTex}
* via its auto size behaviour.
*/
#NonNull
private final TextMeasure textMeasurer;
/**
* {#link AppCompatEditText} we want to show to the user
*/
#NonNull
private final EditText wrappedEditTex;
public AutoSizeEditText(Context context) {
this(context, null);
}
public AutoSizeEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoSizeEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// don't clip children
setClipChildren(false);
setClipToOutline(false);
setClipToPadding(false);
// using AttributeSet of TextView in order to apply it our text views
wrappedEditTex = createWrappedEditText(context, attrs);
textMeasurer = createTextMeasure(context, attrs, wrappedEditTex);
addView(wrappedEditTex, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
addView(textMeasurer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
#NonNull
private TextMeasure createTextMeasure(Context context, AttributeSet attrs, EditText editText) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.AutoSizeEditText);
final int minSize = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeMinTextSize, convertDpToPixel(10));
final int maxSize = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeMaxTextSize, convertDpToPixel(18));
final int step = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeStepGranularity, convertDpToPixel(1));
typedArray.recycle();
TextMeasure textMeasurer = new TextMeasure(context);
final Editable text = editText.getText();
final CharSequence hint = editText.getHint();
if (!TextUtils.isEmpty(text)) {
textMeasurer.setText(text);
} else if (!TextUtils.isEmpty(hint)) {
textMeasurer.setText(hint);
} else {
textMeasurer.setText(TEST_SYMBOL);
}
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
textMeasurer, minSize, maxSize, step, TypedValue.COMPLEX_UNIT_PX);
textMeasurer.setVisibility(View.INVISIBLE);
textMeasurer.setPadding(0, 0, 0, 0);
if (TEST) {
textMeasurer.setTextColor(Color.RED);
final ColorDrawable background = new ColorDrawable(Color.YELLOW);
background.setAlpha(50);
textMeasurer.setBackground(background);
textMeasurer.setAlpha(0.2f);
textMeasurer.setVisibility(View.VISIBLE);
}
return textMeasurer;
}
/**
* Creating {#link EditText} which user will use and see
*
* #param attrs {#link AttributeSet} which comes from most likely from xml, which can be user for {#link EditText}
* if attributes of {#link TextView} were declared in xml
* #return created {#link EditText}
*/
#NonNull
protected EditText createWrappedEditText(Context context, AttributeSet attrs) {
return new AppCompatEditText(context, attrs);
}
#NonNull
public EditText getWrappedEditTex() {
return wrappedEditTex;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
wrappedEditTex.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
final int targetHeight = (int) (height
- VERTICAL_MARGIN * 2
- wrappedEditTex.getPaddingTop()
- wrappedEditTex.getPaddingBottom());
final int targetWidth = (width
- wrappedEditTex.getTotalPaddingStart()
- wrappedEditTex.getTotalPaddingEnd());
textMeasurer.measure(
MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(targetHeight, MeasureSpec.EXACTLY)
);
setMeasuredDimension(width, height);
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int layoutHeight = getMeasuredHeight();
final int layoutWidth = getMeasuredWidth();
wrappedEditTex.layout(0, 0, layoutWidth, layoutHeight);
if (changed) {
final int measuredHeight = textMeasurer.getMeasuredHeight();
final int measuredWidth = textMeasurer.getMeasuredWidth();
final int topCoordinate = (int) (wrappedEditTex.getPaddingTop() + VERTICAL_MARGIN);
final int leftCoordinate = wrappedEditTex.getTotalPaddingStart();
textMeasurer.layout(
leftCoordinate,
topCoordinate,
measuredWidth + leftCoordinate,
topCoordinate + measuredHeight);
wrappedEditTex.setTextSize(TypedValue.COMPLEX_UNIT_PX, textMeasurer.getTextSize());
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return wrappedEditTex.dispatchTouchEvent(ev);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return wrappedEditTex.onTouchEvent(event);
}
/**
* Adjust text size due to the fact we want hint to be always visible
*
* #param hint Hint for {#link #wrappedEditTex}
*/
public void setHint(CharSequence hint) {
wrappedEditTex.setHint(hint);
textMeasurer.setText(hint);
}
/**
* Adjust text size for TypeFace, because it can change calculations
*
* #param typeface for {#link #wrappedEditTex}
*/
public void setTypeface(Typeface typeface) {
wrappedEditTex.setTypeface(typeface);
textMeasurer.setTypeface(typeface);
}
public void setTextColor(Integer textColor) {
wrappedEditTex.setTextColor(textColor);
}
public void setHintTextColor(Integer hintTextColor) {
wrappedEditTex.setHintTextColor(hintTextColor);
}
public void setText(CharSequence text) {
wrappedEditTex.setText(text);
}
private static class TextMeasure extends AppCompatTextView {
public TextMeasure(Context context) {
super(context);
}
#Override
public void setInputType(int type) {
}
#Override
public void setRawInputType(int type) {
}
#Override
public int getInputType() {
return EditorInfo.TYPE_NULL;
}
#Override
public int getMaxLines() {
return 1;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
#Override
public int getMinLines() {
return 1;
}
}
}
Example of using my component as below:
<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">
<com.vladislavkarpman.autosizeedittext.AutoSizeEditText
android:layout_width="300dp"
android:layout_height="100dp"
app:autoSizeMaxTextSize="50dp"
app:autoSizeMinTextSize="4dp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Basically, I need to detect when the progress changes in the SeekBar and draw a text view on top of the thumb indicating the progress value.
I do this by implementing a OnSeekBarChangeListener
and on the public void onProgressChanged(SeekBar seekBar, int progress, boolean b) method, I call Rect thumbRect = seekBar.getThumb().getBounds(); to determine where the thumb is positioned.
This works perfectly fine, but apparently getThumb() is only available in API level 16+ (Android 4.1), causing a NoSuchMethodError on earlier versions.
Any idea how to work around this issue?
I was able to use my own class to get the Thumb:
MySeekBar.java
package mobi.sherif.seekbarthumbposition;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.SeekBar;
public class MySeekBar extends SeekBar {
public MySeekBar(Context context) {
super(context);
}
public MySeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
Drawable mThumb;
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
mThumb = thumb;
}
public Drawable getSeekBarThumb() {
return mThumb;
}
}
In the activity this works perfectly:
package mobi.sherif.seekbarthumbposition;
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
public class MainActivity extends Activity implements OnSeekBarChangeListener {
MySeekBar mSeekBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSeekBar = (MySeekBar) findViewById(R.id.seekbar);
mSeekBar.setOnSeekBarChangeListener(this);
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
Rect thumbRect = mSeekBar.getSeekBarThumb().getBounds();
Log.v("sherif", "(" + thumbRect.left + ", " + thumbRect.top + ", " + thumbRect.right + ", " + thumbRect.bottom + ")");
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
}
Hopefully this can save some hours for someone else!
I created this method instead of a custom seekBar:
public int getSeekBarThumbPosX(SeekBar seekBar) {
int posX;
if (Build.VERSION.SDK_INT >= 16) {
posX = seekBar.getThumb().getBounds().centerX();
} else {
int left = seekBar.getLeft() + seekBar.getPaddingLeft();
int right = seekBar.getRight() - seekBar.getPaddingRight();
float width = (float) (seekBar.getProgress() * (right - left)) / seekBar.getMax();
posX = Math.round(width) + seekBar.getThumbOffset();
}
return posX;
}
A splendid solution! Thanks.
It's only nessesary to add that to use custom seekbar you need modify your xml
<com.example.MySeekBar
android:id="#+id/..."
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:minHeight="3dp"
android:maxHeight="3dp"
android:progressDrawable="#drawable/seek_bar_2"
android:thumb="#drawable/thumbler_seekbart_circle"
android:thumbOffset="8dp" />
android:thumbOffset="8dp" - is a HALF of a thumb it's better to spesify, thus there will be no mismatching of the text center and the thumb
Positioning can look like this:
int possition = (int) (seekBar.getX() //the beginning of the seekbar
+ seekBar.getThumbOffset() / 2 //the half of our thumb - the text to be above it's centre
+ ((MySeekBar) seekBar).getSeekBarThumb().getBounds().exactCenterX()); //position of a thumb inside the seek bar
#Sherif elKhatib's answer is great but has the disadvantage of caching a copy of the thumb even on API>=16.
I've improved it so that it only caches the Thumb Drawable on API<=15 plus it overrides the method in SeekBar.java to avoid having two methods do the same on API>=16. Only downside: It needs target SDK to be >= 16 which should be the case in most apps nowadays.
public class ThumbSeekBar extends AppCompatSeekBar {
private Drawable thumb;
public ThumbSeekBar(Context context) {
super(context);
}
public ThumbSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ThumbSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public Drawable getThumb() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return super.getThumb();
}
return thumb;
}
#Override
public void setThumb(Drawable thumb) {
super.setThumb(thumb);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
this.thumb = thumb;
}
}
}
I'm trying to make this custom SeekBar in Android 2.2 and everything I do seems to be wrong! I'm trying to display the value of the seekbar over the thumb image of the SeekBar. Does anybody have some experiences with this?
I have followed a different approach which provides more possibilities to customize the thumb. Final output will look like following:
First you have to design the layout which will be set as thumb drawable.
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>
Here seekbar_thumb_size can be any small size as per your requirement. I have used 30dp here. For background you can use any drawable/icon of your choice.
Now you need this view to be set as thumb drawable so get it with following code:
View thumbView = LayoutInflater.from(YourActivity.this).inflate(R.layout.layout_seekbar_thumb, null, false);
Here I suggest to initialize this view in onCreate() so no need to inflate it again and again.
Now set this view as thumb drawable when seekBar progress is changed. Add the following method in your code:
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);
}
Now call this method from 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) {
}
});
Note: Also call getThumb() method when you initialize seekBar to initialize it with default value.
With this approach, you can have any custom view on progress change.
I assume you've already extended the base class, so you have something like:
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);
}
}
Now you override the onDraw method with some of your own code. Insert the following:
#Override
protected void onDraw(Canvas c) {
super.onDraw(c);
}
Now, you want to draw some text near the thumb, but there isn't a convenient way to get the thumb's x-position. We just need a little math.
#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();
}
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);
}
});
This worked for me
#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);
}
Hey I found another solution, seems simpler:
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);
}
This follow code aligns your TextView center to your SeekBar thumb center.
YOUR_TEXT_VIEW width must be wrap_content in xml.
Hope this code will help you.
#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));
}
I used this library to create drawable text view and put that drawable into thumb programmatically.
https://github.com/amulyakhare/TextDrawable
Code is something like this:
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) {
}
});
Works not bad for me
there is a little hardcode)
please write improvements which smbd may has
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());
}
}
IMO best way is to do it through code. It is really not that scary and we are all programmers after all :)
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?) {
}
}
and in your seekbar implementation
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
}
}
final result is something like this
I created this example to show how textview should be supported to different types of screen size and how to calculate the real position of Thumb because sometimes the position could be 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) {
}
}
Add a TextView to your layout. Add onSeekBarChangeListener.
You will want precision so that the text is exactly in the middle of your seek bar thumb, you have to a little calculation. This is because the width of the text is different. Say, you want to show numbers from 0 to 150. Width of 188 will be different from 111. Because of this, the text you are showing will always tilt to some side.
The way to solve it is to measure the width of the text, remove that from the width of the seekbar thumb, divide it by 2, and add that to the result that was given in the accepted answer. Now you would not care about how large the number range. Here is the code:
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
val value = progress * (seekBar.width - 2 * seekBar.thumbOffset) / seekBar.max
label.text = progress.toString()
label.measure(0, 0)
val textWidth = label.measuredWidth
val firstRemainder = seekThumbWidth - textWidth
val result = firstRemainder / 2
label.x = (seekBar.x + value + result)
}
I want to implement a slider, which is basically two lines, one vertical and one horizontal, crossing where the screen is touched. I have managed to make one but I have to issues:
The slider is not very smooth, there is a slight delay when I'm moving the finger
If I place two sliders it is not multitouch, and I'd like to use both of them simultaneously
Here is the code:
public class Slider extends View {
private Controller controller = new Controller();
private boolean initialisedSlider;
private int sliderWidth, sliderHeight;
private Point pointStart;
private Paint white;
private int mode;
final static int VERTICAL = 0, HORIZONTAL = 1, BOTH = 2;
public Slider(Context context) {
super(context);
setFocusable(true);
// TODO Auto-generated constructor stub
}
public Slider(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
pointStart = new Point();
initialisedSlider = false;
mode = Slider.BOTH;
}
#Override
protected void onDraw(Canvas canvas) {
if(!initialisedSlider) {
initialisedSlider = true;
sliderWidth = getMeasuredWidth();
sliderHeight = getMeasuredHeight();
pointStart.x = (int)(sliderWidth/2.0);
pointStart.y = (int)(sliderHeight/2.0);
controller = new Controller(pointStart, 3);
white = new Paint();
white.setColor(0xFFFFFFFF);
}
canvas.drawLine(controller.getCoordX(),0,
controller.getCoordX(),sliderHeight,
white);
canvas.drawLine(0, controller.getCoordY(),
sliderWidth, controller.getCoordY(),
white);
}
public boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
int X = (int)event.getX();
int Y = (int)event.getY();
switch (eventaction) {
case MotionEvent.ACTION_DOWN:
if(isInBounds(X,Y)) {
updateController(X, Y);
}
break;
case MotionEvent.ACTION_MOVE:
if(isInBounds(X,Y)) {
updateController(X, Y);
}
break;
case MotionEvent.ACTION_UP:
if(isInBounds(X,Y)) {
updateController(X, Y);
}
break;
}
invalidate();
return true;
}
private boolean isInBounds(int x, int y) {
return ((x<=(sliderWidth)) && (x>=(0))
&& (y<=(sliderHeight)) && (y>=(0)));
}
private void updateController(int x, int y) {
switch(mode) {
case Slider.HORIZONTAL:
controller.setCoordX(x);
break;
case Slider.VERTICAL:
controller.setCoordY(y);
break;
case Slider.BOTH:
controller.setCoordX(x);
controller.setCoordY(y);
break;
}
}
private class Controller {
private int coordX, coordY;
Controller() {
}
Controller(Point point, int width) {
setCoordX(point.x);
setCoordY(point.y);
}
public void setCoordX(int coordX) {
this.coordX = coordX;
}
public int getCoordX() {
return coordX;
}
public void setCoordY(int coordY) {
this.coordY = coordY;
}
public int getCoordY() {
return coordY;
}
}
}
And the XML file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/hello" />
<com.android.lasttest.Slider
android:id="#+id/slider"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"/>
<com.android.lasttest.Slider
android:id="#+id/slider"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"/>
<com.android.lasttest.Slider
android:id="#+id/slider"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"/>
</LinearLayout>
How to implement a SeekBar
Add the SeekBar to your layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/textView"
android:layout_margin="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<SeekBar
android:id="#+id/seekBar"
android:max="100"
android:progress="50"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Notes
max is the highest value that the seek bar can go to. The default is 100. The minimum is 0. The xml min value is only available from API 26, but you can just programmatically convert the 0-100 range to whatever you need for earlier versions.
progress is the initial position of the slider dot (called a "thumb").
For a vertical SeekBar use android:rotation="270".
Listen for changes in code
public class MainActivity extends AppCompatActivity {
TextView tvProgressLabel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// set a change listener on the SeekBar
SeekBar seekBar = findViewById(R.id.seekBar);
seekBar.setOnSeekBarChangeListener(seekBarChangeListener);
int progress = seekBar.getProgress();
tvProgressLabel = findViewById(R.id.textView);
tvProgressLabel.setText("Progress: " + progress);
}
SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// updated continuously as the user slides the thumb
tvProgressLabel.setText("Progress: " + progress);
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// called when the user first touches the SeekBar
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
// called after the user finishes moving the SeekBar
}
};
}
Notes
If you don't need to do any updates while the user is moving the seekbar, then you can just update the UI in onStopTrackingTouch.
See also
SeekBar Tutorial With Example In Android Studio
Android provides slider which is horizontal
http://developer.android.com/reference/android/widget/SeekBar.html
and implement OnSeekBarChangeListener
http://developer.android.com/reference/android/widget/SeekBar.OnSeekBarChangeListener.html
If you want vertical Seekbar then follow this link
http://hoodaandroid.blogspot.in/2012/10/vertical-seek-bar-or-slider-in-android.html
For future readers!
Starting from material components android 1.2.0-alpha01, you have slider component
ex:
<com.google.android.material.slider.Slider
android:id="#+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:valueFrom="20f"
android:valueTo="70f"
android:stepSize="10" />
Release notes
Material Design Spec
Docs