layout.xml :
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="2dp"
android:id="@+id/minimum_school_rating_text"
android:textColor="@color/grey_800"
android:text="@string/minimum_public_school_rating"
android:textSize="16sp"/>
<com.move.androidlib.view.rangeseekbarv1.RangeSeekBar
android:id="@+id/school_rating_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:rsb_gravity="bottom"
app:rsb_mode="single"
app:rsb_progress_color="@color/school_pin_seekbar"
app:rsb_progress_default_color="@color/red_500"
app:rsb_step_auto_bonding="true"
app:rsb_max="10"
app:rsb_min="1"
app:rsb_thumb_width="20dp"
app:rsb_thumb_height="20dp"
app:rsb_step_width="3dp"
app:rsb_step_height="3dp"
app:rsb_steps="9"
app:rsb_thumb_drawable="@drawable/school_rating_seekbar_red_pin"
app:rsb_tick_mark_gravity="center"
app:rsb_tick_mark_layout_gravity="bottom"
app:rsb_tick_mark_mode="other"
app:rsb_tick_mark_text_array="@array/schoolRatingArray"
app:rsb_tick_mark_text_size="16sp"
app:rsb_tick_mark_text_color="@color/grey_800"
app:rsb_tick_mark_text_margin="16dp" />
RangeSeekBar.java:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
public class RangeSeekBar extends View {
private final static int MIN_INTERCEPT_DISTANCE = 100;
//normal seekBar mode public final static int SEEKBAR_MODE_SINGLE = 1;
//RangeSeekBar public final static int SEEKBAR_MODE_RANGE = 2;
private Canvas canvas;
private AttributeSet attrs;
/** * @hide */ @IntDef({SEEKBAR_MODE_SINGLE, SEEKBAR_MODE_RANGE})
@Retention(RetentionPolicy.SOURCE)
public @interface SeekBarModeDef {
}
//number according to the actual proportion of the number of arranged; public final static int TRICK_MARK_MODE_NUMBER = 0;
//other equally arranged public final static int TRICK_MARK_MODE_OTHER = 1;
/** * @hide */ @IntDef({TRICK_MARK_MODE_NUMBER, TRICK_MARK_MODE_OTHER})
@Retention(RetentionPolicy.SOURCE)
public @interface TickMarkModeDef {
}
//tick mark text gravity public final static int TICK_MARK_GRAVITY_LEFT = 0;
public final static int TICK_MARK_GRAVITY_CENTER = 1;
public final static int TICK_MARK_GRAVITY_RIGHT = 2;
/** * @hide */ @IntDef({TICK_MARK_GRAVITY_LEFT, TICK_MARK_GRAVITY_CENTER, TICK_MARK_GRAVITY_RIGHT})
@Retention(RetentionPolicy.SOURCE)
public @interface TickMarkGravityDef {
}
/** * @hide */ @IntDef({Gravity.TOP, Gravity.BOTTOM})
@Retention(RetentionPolicy.SOURCE)
public @interface TickMarkLayoutGravityDef {
}
/** * @hide */ @IntDef({Gravity.TOP, Gravity.CENTER, Gravity.BOTTOM})
@Retention(RetentionPolicy.SOURCE)
public @interface GravityDef {
}
public static class Gravity {
public final static int TOP = 0;
public final static int BOTTOM = 1;
public final static int CENTER = 2;
}
private int progressTop, progressBottom, progressLeft, progressRight;
private int seekBarMode;
//number mode: display based on the actual number ratio //other mode: display averagely private int tickMarkMode;
//The spacing between the tick mark and the progress bar private int tickMarkTextMargin;
//tick mark text and prompt text size private int tickMarkTextSize;
private int tickMarkGravity;
private int tickMarkLayoutGravity;
private int tickMarkTextColor;
private int tickMarkInRangeTextColor;
//The texts displayed on the scale private CharSequence[] tickMarkTextArray;
//radius of progress bar private float progressRadius;
//the color of seekBar in progress private int progressColor;
//the default color of the progress bar private int progressDefaultColor;
//the drawable of seekBar in progress private int progressDrawableId;
//the default Drawable of the progress bar private int progressDefaultDrawableId;
//the progress height private int progressHeight;
// the progress width private int progressWidth;
//the range interval of RangeSeekBar private float minInterval;
private int gravity;
//enable RangeSeekBar two thumb Overlap private boolean enableThumbOverlap;
//the color of step divs private int stepsColor;
//the width of each step private float stepsWidth;
//the height of each step private float stepsHeight;
//the radius of step divs private float stepsRadius;
//steps is 0 will disable StepSeekBar private int steps;
//the thumb will automatic bonding close to its value private boolean stepsAutoBonding;
private int stepsDrawableId;
//True values set by the user private float minProgress, maxProgress;
//****************** the above is attr value ******************//
private boolean isEnable = true;
float touchDownX, touchDownY;
//Progress of remaining minimum interval float reservePercent;
boolean isScaleThumb = false;
Paint paint = new Paint();
RectF progressDefaultDstRect = new RectF();
RectF progressDstRect = new RectF();
Rect progressSrcRect = new Rect();
RectF stepDivRect = new RectF();
Rect tickMarkTextRect = new Rect();
SeekBar leftSB;
SeekBar rightSB;
SeekBar currTouchSB;
Bitmap progressBitmap;
Bitmap progressDefaultBitmap;
List<Bitmap> stepsBitmaps = new ArrayList<>();
private int progressPaddingRight;
private OnRangeChangedListener callback;
public RangeSeekBar(Context context) {
this(context, null);
}
public RangeSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
this.attrs = attrs;
initAttrs(attrs);
initPaint();
initSeekBar(attrs, true);
initStepsBitmap();
}
private void initProgressBitmap() {
if (progressBitmap == null) {
progressBitmap = drawableToBitmap(getContext(), progressWidth, progressHeight, progressDrawableId);
}
if (progressDefaultBitmap == null) {
progressDefaultBitmap = drawableToBitmap(getContext(), progressWidth, progressHeight, progressDefaultDrawableId);
}
}
private boolean verifyStepsMode() {
if (steps < 1 || stepsHeight <= 0 || stepsWidth <= 0) return false;
return true;
}
private void initStepsBitmap() {
if (!verifyStepsMode() || stepsDrawableId == 0) return;
if (stepsBitmaps.isEmpty()) {
Bitmap bitmap = drawableToBitmap(getContext(), (int) stepsWidth, (int) stepsHeight, stepsDrawableId);
for (int i = 0; i <= steps; i++) {
stepsBitmaps.add(bitmap);
}
}
}
private void initSeekBar(AttributeSet attrs, boolean enableSeekbar) {
leftSB = new SeekBar(this, attrs, true, enableSeekbar);
rightSB = new SeekBar(this, attrs, false, enableSeekbar);
rightSB.setVisible(seekBarMode != SEEKBAR_MODE_SINGLE);
}
private void initAttrs(AttributeSet attrs) {
try {
TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.RangeSeekBar);
seekBarMode = t.getInt(R.styleable.RangeSeekBar_rsb_mode, SEEKBAR_MODE_RANGE);
minProgress = t.getFloat(R.styleable.RangeSeekBar_rsb_min, 0);
maxProgress = t.getFloat(R.styleable.RangeSeekBar_rsb_max, 100);
minInterval = t.getFloat(R.styleable.RangeSeekBar_rsb_min_interval, 0);
gravity = t.getInt(R.styleable.RangeSeekBar_rsb_gravity, Gravity.TOP);
progressColor = t.getColor(R.styleable.RangeSeekBar_rsb_progress_color, 0xFF4BD962);
progressRadius = (int) t.getDimension(R.styleable.RangeSeekBar_rsb_progress_radius, -1);
progressDefaultColor = t.getColor(R.styleable.RangeSeekBar_rsb_progress_default_color, 0xFFD7D7D7);
progressDrawableId = t.getResourceId(R.styleable.RangeSeekBar_rsb_progress_drawable, 0);
progressDefaultDrawableId = t.getResourceId(R.styleable.RangeSeekBar_rsb_progress_drawable_default, 0);
progressHeight = (int) t.getDimension(R.styleable.RangeSeekBar_rsb_progress_height, Display.convertDpToPx(getContext(), 2));
tickMarkMode = t.getInt(R.styleable.RangeSeekBar_rsb_tick_mark_mode, TRICK_MARK_MODE_NUMBER);
tickMarkGravity = t.getInt(R.styleable.RangeSeekBar_rsb_tick_mark_gravity, TICK_MARK_GRAVITY_CENTER);
tickMarkLayoutGravity = t.getInt(R.styleable.RangeSeekBar_rsb_tick_mark_layout_gravity, Gravity.TOP);
tickMarkTextArray = t.getTextArray(R.styleable.RangeSeekBar_rsb_tick_mark_text_array);
tickMarkTextMargin = (int) t.getDimension(R.styleable.RangeSeekBar_rsb_tick_mark_text_margin, Display.convertDpToPx(getContext(), 7));
tickMarkTextSize = (int) t.getDimension(R.styleable.RangeSeekBar_rsb_tick_mark_text_size, Display.convertDpToPx(getContext(), 12));
tickMarkTextColor = t.getColor(R.styleable.RangeSeekBar_rsb_tick_mark_text_color, progressDefaultColor);
tickMarkInRangeTextColor = t.getColor(R.styleable.RangeSeekBar_rsb_tick_mark_text_color, progressColor);
steps = t.getInt(R.styleable.RangeSeekBar_rsb_steps, 0);
stepsColor = t.getColor(R.styleable.RangeSeekBar_rsb_step_color, 0xb3ffffff);
stepsRadius = t.getDimension(R.styleable.RangeSeekBar_rsb_step_radius, 0);
stepsWidth = t.getDimension(R.styleable.RangeSeekBar_rsb_step_width, 0);
stepsHeight = t.getDimension(R.styleable.RangeSeekBar_rsb_step_height, 0);
stepsDrawableId = t.getResourceId(R.styleable.RangeSeekBar_rsb_step_drawable, 0);
stepsAutoBonding = t.getBoolean(R.styleable.RangeSeekBar_rsb_step_auto_bonding, true);
t.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}
/** * measure progress bar position */ protected void onMeasureProgress(int w, int h) {
int viewHeight = h - getPaddingBottom() - getPaddingTop();
if (h <= 0) return;
if (gravity == Gravity.TOP) {
//calculate the height of indicator and thumb exceeds the part of the progress float maxIndicatorHeight = 0;
if (leftSB.getIndicatorShowMode() != INDICATOR_ALWAYS_HIDE || rightSB.getIndicatorShowMode() != INDICATOR_ALWAYS_HIDE) {
maxIndicatorHeight = Math.max(leftSB.getIndicatorRawHeight(), rightSB.getIndicatorRawHeight());
}
float thumbHeight = Math.max(leftSB.getThumbScaleHeight(), rightSB.getThumbScaleHeight());
thumbHeight -= progressHeight / 2f;
//default height is indicator + thumb exceeds the part of the progress bar //if tickMark height is greater than (indicator + thumb exceeds the part of the progress) progressTop = (int) (maxIndicatorHeight + (thumbHeight - progressHeight) / 2f);
if (tickMarkTextArray != null && tickMarkLayoutGravity == Gravity.TOP) {
progressTop = (int) Math.max(getTickMarkRawHeight(), maxIndicatorHeight + (thumbHeight - progressHeight) / 2f);
}
progressBottom = progressTop + progressHeight;
} else if (gravity == Gravity.BOTTOM) {
if (tickMarkTextArray != null && tickMarkLayoutGravity == Gravity.BOTTOM) {
progressBottom = viewHeight - getTickMarkRawHeight();
} else {
progressBottom = (int) (viewHeight - Math.max(leftSB.getThumbScaleHeight(), rightSB.getThumbScaleHeight()) / 2f + progressHeight / 2f);
}
progressTop = progressBottom - progressHeight;
} else {
progressTop = (viewHeight - progressHeight) / 2;
progressBottom = progressTop + progressHeight;
}
int maxThumbWidth = (int) Math.max(leftSB.getThumbScaleWidth(), rightSB.getThumbScaleWidth());
progressLeft = maxThumbWidth / 2 + getPaddingLeft();
progressRight = w - maxThumbWidth / 2 - getPaddingRight();
progressWidth = progressRight - progressLeft;
progressDefaultDstRect.set(getProgressLeft(), getProgressTop(), getProgressRight(), getProgressBottom());
progressPaddingRight = w - progressRight;
//default value if (progressRadius <= 0) {
progressRadius = (int) ((getProgressBottom() - getProgressTop()) * 0.45f);
}
initProgressBitmap();
}
//the procedure below Android7.0 :onMeasure--->onSizeChanged--->onMeasure //the procedure above Android7.0 :onMeasure--->onSizeChanged @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
/* * widthMeasureSpec and heightMeasureSpec in onMeasure method are not normal size variables, but variables combined with mode and size * MeasureSpec.EXACTLY is exact size * MeasureSpec.AT_MOST us maximum size * MeasureSpec.UNSPECIFIED is undefined size */
if (heightMode == MeasureSpec.EXACTLY) {
heightSize = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
} else if (heightMode == MeasureSpec.AT_MOST && getParent() instanceof ViewGroup
&& heightSize == ViewGroup.LayoutParams.MATCH_PARENT) {
heightSize = MeasureSpec.makeMeasureSpec(((ViewGroup) getParent()).getMeasuredHeight(), MeasureSpec.AT_MOST);
} else {
int heightNeeded;
if (gravity == Gravity.CENTER) {
if (tickMarkTextArray != null && tickMarkLayoutGravity == Gravity.BOTTOM) {
heightNeeded = (int) (2 * (getRawHeight() - getTickMarkRawHeight()));
} else {
heightNeeded = (int) (2 * (getRawHeight() - Math.max(leftSB.getThumbScaleHeight(), rightSB.getThumbScaleHeight()) / 2));
}
} else {
heightNeeded = (int) getRawHeight();
}
heightSize = MeasureSpec.makeMeasureSpec(heightNeeded, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightSize);
}
protected int getTickMarkRawHeight() {
if (tickMarkTextArray != null && tickMarkTextArray.length > 0) {
return tickMarkTextMargin + measureText(String.valueOf(tickMarkTextArray[0]),paint).height() + 3;
}
return 0;
}
protected float getRawHeight() {
float rawHeight;
if (seekBarMode == SEEKBAR_MODE_SINGLE) {
rawHeight = leftSB.getRawHeight();
if (tickMarkLayoutGravity == Gravity.BOTTOM && tickMarkTextArray != null) {
float h = Math.max((leftSB.getThumbScaleHeight() - progressHeight) / 2, getTickMarkRawHeight());
rawHeight = rawHeight - leftSB.getThumbScaleHeight() / 2 + progressHeight / 2f + h;
}
} else {
rawHeight = Math.max(leftSB.getRawHeight(), rightSB.getRawHeight());
if (tickMarkLayoutGravity == Gravity.BOTTOM && tickMarkTextArray != null) {
float thumbHeight = Math.max(leftSB.getThumbScaleHeight(), rightSB.getThumbScaleHeight());
float h = Math.max((thumbHeight - progressHeight) / 2, getTickMarkRawHeight());
rawHeight = rawHeight - thumbHeight / 2 + progressHeight / 2f + h;
}
}
return rawHeight;
}
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
onMeasureProgress(w, h);
//set default value setRange(minProgress, maxProgress, minInterval);
// initializes the positions of the two thumbs int lineCenterY = (getProgressBottom() + getProgressTop()) / 2;
leftSB.onSizeChanged(getProgressLeft(), lineCenterY);
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSB.onSizeChanged(getProgressLeft(), lineCenterY);
}
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.canvas = canvas;
onDrawTickMark(canvas, paint);
onDrawProgressBar(canvas, paint);
onDrawSteps(canvas, paint);
onDrawSeekBar(canvas);
}
// Draw the scales, and according to the current position is set within // the scale range of different color display protected void onDrawTickMark(Canvas canvas, Paint paint) {
if (tickMarkTextArray != null) {
int trickPartWidth = progressWidth / (tickMarkTextArray.length - 1);
for (int i = 0; i < tickMarkTextArray.length; i++) {
final String text2Draw = tickMarkTextArray[i].toString();
if (TextUtils.isEmpty(text2Draw)) continue;
paint.getTextBounds(text2Draw, 0, text2Draw.length(), tickMarkTextRect);
paint.setColor(tickMarkTextColor);
//display averagely float x;
if (tickMarkMode == TRICK_MARK_MODE_OTHER) {
if (tickMarkGravity == TICK_MARK_GRAVITY_RIGHT) {
x = getProgressLeft() + i * trickPartWidth - tickMarkTextRect.width();
} else if (tickMarkGravity == TICK_MARK_GRAVITY_CENTER) {
x = getProgressLeft() + i * trickPartWidth - tickMarkTextRect.width() / 2f;
} else {
x = getProgressLeft() + i * trickPartWidth;
}
} else {
float num = safeParseFloat(text2Draw);
SeekBarState[] states = getRangeSeekBarState();
if (compareFloat(num, states[0].value) != -1 && compareFloat(num, states[1].value) != 1 && (seekBarMode == SEEKBAR_MODE_RANGE)) {
paint.setColor(tickMarkInRangeTextColor);
}
//display based on ratio x = getProgressLeft() + progressWidth * (num - minProgress) / (maxProgress - minProgress)
- tickMarkTextRect.width() / 2f;
}
float y;
if (tickMarkLayoutGravity == Gravity.TOP) {
y = getProgressTop() - tickMarkTextMargin;
} else {
y = getProgressBottom() + tickMarkTextMargin + tickMarkTextRect.height();
}
canvas.drawText(text2Draw, x, y, paint);
}
}
}
// draw the progress bar protected void onDrawProgressBar(Canvas canvas, Paint paint) {
//draw default progress if (verifyBitmap(progressDefaultBitmap)) {
canvas.drawBitmap(progressDefaultBitmap, null, progressDefaultDstRect, paint);
} else {
paint.setColor(progressDefaultColor);
canvas.drawRoundRect(progressDefaultDstRect, progressRadius, progressRadius, paint);
}
//draw progress if (seekBarMode == SEEKBAR_MODE_RANGE) {
progressDstRect.top = getProgressTop();
progressDstRect.left = leftSB.left + leftSB.getThumbScaleWidth() / 2f + progressWidth * leftSB.currPercent;
progressDstRect.right = rightSB.left + rightSB.getThumbScaleWidth() / 2f + progressWidth * rightSB.currPercent;
progressDstRect.bottom = getProgressBottom();
} else {
progressDstRect.top = getProgressTop();
progressDstRect.left = leftSB.left + leftSB.getThumbScaleWidth() / 2f;
progressDstRect.right = leftSB.left + leftSB.getThumbScaleWidth() / 2f + progressWidth * leftSB.currPercent;
progressDstRect.bottom = getProgressBottom();
}
if (verifyBitmap(progressBitmap)) {
progressSrcRect.top = 0;
progressSrcRect.bottom = progressBitmap.getHeight();
int bitmapWidth = progressBitmap.getWidth();
if (seekBarMode == SEEKBAR_MODE_RANGE) {
progressSrcRect.left = (int) (bitmapWidth * leftSB.currPercent);
progressSrcRect.right = (int) (bitmapWidth * rightSB.currPercent);
} else {
progressSrcRect.left = 0;
progressSrcRect.right = (int) (bitmapWidth * leftSB.currPercent);
}
canvas.drawBitmap(progressBitmap, progressSrcRect, progressDstRect, null);
} else {
paint.setColor(progressColor);
canvas.drawRoundRect(progressDstRect, progressRadius, progressRadius, paint);
}
}
//draw steps protected void onDrawSteps(Canvas canvas, Paint paint) {
if (!verifyStepsMode()) return;
int stepMarks = getProgressWidth() / (steps);
float extHeight = (stepsHeight - getProgressHeight()) / 2f;
for (int k = 0; k < steps; k++) {
float x = getProgressLeft() + k * stepMarks - stepsWidth / 2f;
stepDivRect.set(x, getProgressTop() - extHeight, x + stepsWidth, getProgressBottom() + extHeight);
if (stepsBitmaps.isEmpty() || stepsBitmaps.size() <= k) {
paint.setColor(stepsColor);
canvas.drawRoundRect(stepDivRect, stepsRadius, stepsRadius, paint);
} else {
canvas.drawBitmap(stepsBitmaps.get(k), null, stepDivRect, paint);
}
}
}
protected void onDrawSeekBar(Canvas canvas) {
//draw left SeekBar if (leftSB.getIndicatorShowMode() == INDICATOR_ALWAYS_SHOW) {
leftSB.setShowIndicatorEnable(true);
}
leftSB.draw(canvas);
//draw right SeekBar if (seekBarMode == SEEKBAR_MODE_RANGE) {
if (rightSB.getIndicatorShowMode() == INDICATOR_ALWAYS_SHOW) {
rightSB.setShowIndicatorEnable(true);
}
rightSB.draw(canvas);
}
}
private void initPaint() {
paint.setStyle(Paint.Style.FILL);
paint.setColor(progressDefaultColor);
paint.setTextSize(tickMarkTextSize);
}
private void changeThumbActivateState(boolean hasActivate) {
if (hasActivate && currTouchSB != null) {
boolean state = currTouchSB == leftSB;
leftSB.setActivate(state);
if (seekBarMode == SEEKBAR_MODE_RANGE)
rightSB.setActivate(!state);
} else {
leftSB.setActivate(false);
if (seekBarMode == SEEKBAR_MODE_RANGE)
rightSB.setActivate(false);
}
}
protected float getEventX(MotionEvent event) {
return event.getX();
}
protected float getEventY(MotionEvent event) {
return event.getY();
}
/** * scale the touch seekBar thumb */ private void scaleCurrentSeekBarThumb() {
if (currTouchSB != null && currTouchSB.getThumbScaleRatio() > 1f && !isScaleThumb) {
isScaleThumb = true;
currTouchSB.scaleThumb();
}
}
/** * reset the touch seekBar thumb */ private void resetCurrentSeekBarThumb() {
if (currTouchSB != null && currTouchSB.getThumbScaleRatio() > 1f && isScaleThumb) {
isScaleThumb = false;
currTouchSB.resetThumb();
}
}
//calculate currTouchSB percent by MotionEvent protected float calculateCurrentSeekBarPercent(float touchDownX) {
if (currTouchSB == null) return 0;
float percent = (touchDownX - getProgressLeft()) * 1f / (progressWidth);
if (touchDownX < getProgressLeft()) {
percent = 0;
} else if (touchDownX > getProgressRight()) {
percent = 1;
}
//RangeMode minimum interval if (seekBarMode == SEEKBAR_MODE_RANGE) {
if (currTouchSB == leftSB) {
if (percent > rightSB.currPercent - reservePercent) {
percent = rightSB.currPercent - reservePercent;
}
} else if (currTouchSB == rightSB) {
if (percent < leftSB.currPercent + reservePercent) {
percent = leftSB.currPercent + reservePercent;
}
}
}
return percent;
}
@Override public boolean onTouchEvent(MotionEvent event) {
if (!isEnable) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDownX = getEventX(event);
touchDownY = getEventY(event);
if (seekBarMode == SEEKBAR_MODE_RANGE) {
if (rightSB.currPercent >= 1 && leftSB.collide(getEventX(event), getEventY(event))) {
currTouchSB = leftSB;
scaleCurrentSeekBarThumb();
} else if (rightSB.collide(getEventX(event), getEventY(event))) {
currTouchSB = rightSB;
scaleCurrentSeekBarThumb();
} else {
float performClick = (touchDownX - getProgressLeft()) * 1f / (progressWidth);
float distanceLeft = Math.abs(leftSB.currPercent - performClick);
float distanceRight = Math.abs(rightSB.currPercent - performClick);
if (distanceLeft < distanceRight) {
currTouchSB = leftSB;
} else {
currTouchSB = rightSB;
}
performClick = calculateCurrentSeekBarPercent(touchDownX);
currTouchSB.slide(performClick);
}
} else {
currTouchSB = leftSB;
scaleCurrentSeekBarThumb();
}
//Intercept parent TouchEvent if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (callback != null) {
callback.onStartTrackingTouch(this, currTouchSB == leftSB);
}
changeThumbActivateState(true);
return true;
case MotionEvent.ACTION_MOVE:
float x = getEventX(event);
if ((seekBarMode == SEEKBAR_MODE_RANGE) && leftSB.currPercent == rightSB.currPercent) {
currTouchSB.materialRestore();
if (callback != null) {
callback.onStopTrackingTouch(this, currTouchSB == leftSB);
}
if (x - touchDownX > 0) {
//method to move right if (currTouchSB != rightSB) {
currTouchSB.setShowIndicatorEnable(false);
resetCurrentSeekBarThumb();
currTouchSB = rightSB;
}
} else {
//method to move left if (currTouchSB != leftSB) {
currTouchSB.setShowIndicatorEnable(false);
resetCurrentSeekBarThumb();
currTouchSB = leftSB;
}
}
if (callback != null) {
callback.onStartTrackingTouch(this, currTouchSB == leftSB);
}
}
scaleCurrentSeekBarThumb();
currTouchSB.material = currTouchSB.material >= 1 ? 1 : currTouchSB.material + 0.1f;
touchDownX = x;
currTouchSB.slide(calculateCurrentSeekBarPercent(touchDownX));
currTouchSB.setShowIndicatorEnable(true);
if (callback != null) {
SeekBarState[] states = getRangeSeekBarState();
callback.onRangeChanged(this, states[0].value, states[1].value, true);
}
invalidate();
//Intercept parent TouchEvent if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
changeThumbActivateState(true);
break;
case MotionEvent.ACTION_CANCEL:
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSB.setShowIndicatorEnable(false);
}
if (currTouchSB == leftSB) {
resetCurrentSeekBarThumb();
} else if (currTouchSB == rightSB) {
resetCurrentSeekBarThumb();
}
leftSB.setShowIndicatorEnable(false);
if (callback != null) {
SeekBarState[] states = getRangeSeekBarState();
callback.onRangeChanged(this, states[0].value, states[1].value, false);
}
//Intercept parent TouchEvent if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
changeThumbActivateState(false);
break;
case MotionEvent.ACTION_UP:
if (verifyStepsMode() && stepsAutoBonding) {
float percent = calculateCurrentSeekBarPercent(getEventX(event));
float stepPercent = 1.0f / steps;
int stepSelected = new BigDecimal(percent / stepPercent).setScale(0, RoundingMode.HALF_UP).intValue();
currTouchSB.slide(stepSelected * stepPercent);
}
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSB.setShowIndicatorEnable(false);
}
leftSB.setShowIndicatorEnable(false);
currTouchSB.materialRestore();
resetCurrentSeekBarThumb();
if (callback != null) {
SeekBarState[] states = getRangeSeekBarState();
callback.onRangeChanged(this, states[0].value, states[1].value, false);
}
//Intercept parent TouchEvent if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (callback != null) {
callback.onStopTrackingTouch(this, currTouchSB == leftSB);
}
changeThumbActivateState(false);
break;
}
return super.onTouchEvent(event);
}
@Override public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.minValue = minProgress;
ss.maxValue = maxProgress;
ss.rangeInterval = minInterval;
SeekBarState[] results = getRangeSeekBarState();
ss.currSelectedMin = results[0].value;
ss.currSelectedMax = results[1].value;
return ss;
}
@Override public void onRestoreInstanceState(Parcelable state) {
try {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
float min = ss.minValue;
float max = ss.maxValue;
float rangeInterval = ss.rangeInterval;
setRange(min, max, rangeInterval);
float currSelectedMin = ss.currSelectedMin;
float currSelectedMax = ss.currSelectedMax;
setProgress(currSelectedMin, currSelectedMax);
} catch (Exception e) {
e.printStackTrace();
}
}
//******************* Attributes getter and setter *******************//
public void setOnRangeChangedListener(OnRangeChangedListener listener) {
callback = listener;
}
public void setProgress(float value) {
setProgress(value, maxProgress);
}
public void setProgress(float leftValue, float rightValue) {
leftValue = Math.min(leftValue, rightValue);
rightValue = Math.max(leftValue, rightValue);
if (rightValue - leftValue < minInterval) {
leftValue = rightValue - minInterval;
}
if (leftValue < minProgress) {
Log.e(RangeSeekBar.class.getName(),"setProgress() min < (preset min - offsetValue) . #min:" + leftValue + " #preset min:" + rightValue);
}
if (rightValue > maxProgress) {
Log.e(RangeSeekBar.class.getName(),"setProgress() max > (preset max - offsetValue) . #max:" + rightValue + " #preset max:" + rightValue);
}
float range = maxProgress - minProgress;
leftSB.currPercent = Math.abs(leftValue - minProgress) / range;
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSB.currPercent = Math.abs(rightValue - minProgress) / range;
}
if (callback != null) {
callback.onRangeChanged(this, leftValue, rightValue, false);
}
invalidate();
}
public void setRange(float min, float max) {
setRange(min, max, minInterval);
}
public void setRange(float min, float max, float minInterval) {
if (max <= min) {
Log.e(RangeSeekBar.class.getName(),"setRange() max must be greater than min ! #max:" + max + " #min:" + min);
}
if (minInterval < 0) {
Log.e(RangeSeekBar.class.getName(),"setRange() interval must be greater than zero ! #minInterval:" + minInterval);
}
if (minInterval >= max - min) {
Log.e(RangeSeekBar.class.getName(),"setRange() interval must be less than (max - min) ! #minInterval:" + minInterval + " #max - min:" + (max - min));
}
maxProgress = max;
minProgress = min;
this.minInterval = minInterval;
reservePercent = minInterval / (max - min);
//set default value if (seekBarMode == SEEKBAR_MODE_RANGE) {
if (leftSB.currPercent + reservePercent <= 1 && leftSB.currPercent + reservePercent > rightSB.currPercent) {
rightSB.currPercent = leftSB.currPercent + reservePercent;
} else if (rightSB.currPercent - reservePercent >= 0 && rightSB.currPercent - reservePercent < leftSB.currPercent) {
leftSB.currPercent = rightSB.currPercent - reservePercent;
}
}
invalidate();
}
/** * @return the two seekBar state , see {@link SeekBarState} */ public SeekBarState[] getRangeSeekBarState() {
SeekBarState leftSeekBarState = new SeekBarState();
leftSeekBarState.value = leftSB.getProgress();
leftSeekBarState.indicatorText = String.valueOf(leftSeekBarState.value);
if (compareFloat(leftSeekBarState.value, minProgress) == 0) {
leftSeekBarState.isMin = true;
} else if (compareFloat(leftSeekBarState.value, maxProgress) == 0) {
leftSeekBarState.isMax = true;
}
SeekBarState rightSeekBarState = new SeekBarState();
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSeekBarState.value = rightSB.getProgress();
rightSeekBarState.indicatorText = String.valueOf(rightSeekBarState.value);
if (compareFloat(rightSB.currPercent, minProgress) == 0) {
rightSeekBarState.isMin = true;
} else if (compareFloat(rightSB.currPercent, maxProgress) == 0) {
rightSeekBarState.isMax = true;
}
}
return new SeekBarState[]{leftSeekBarState, rightSeekBarState};
}
@Override public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
this.isEnable = enabled;
}
public void enableSeekbar(boolean enabled) {
setProgressDefaultColor(getResources().getColor(enabled ? R.color.red_500 : R.color.school_pin_seekbar));
setTickMarkTextColor(getResources().getColor(enabled ? R.color.grey_800 : R.color.grey_250));
initPaint();
if (attrs != null) initSeekBar(attrs, enabled);
initStepsBitmap();
invalidate();
}
public void setIndicatorText(String progress) {
leftSB.setIndicatorText(progress);
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSB.setIndicatorText(progress);
}
}
/** * format number indicator text * * @param formatPattern format rules */ public void setIndicatorTextDecimalFormat(String formatPattern) {
leftSB.setIndicatorTextDecimalFormat(formatPattern);
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSB.setIndicatorTextDecimalFormat(formatPattern);
}
}
/** * format string indicator text * * @param formatPattern format rules */ public void setIndicatorTextStringFormat(String formatPattern) {
leftSB.setIndicatorTextStringFormat(formatPattern);
if (seekBarMode == SEEKBAR_MODE_RANGE) {
rightSB.setIndicatorTextStringFormat(formatPattern);
}
}
/** * if is single mode, please use it to get the SeekBar * * @return left seek bar */ public SeekBar getLeftSeekBar() {
return leftSB;
}
public SeekBar getRightSeekBar() {
return rightSB;
}
public int getProgressTop() {
return progressTop;
}
public int getProgressBottom() {
return progressBottom;
}
public int getProgressLeft() {
return progressLeft;
}
public int getProgressRight() {
return progressRight;
}
public int getProgressPaddingRight() {
return progressPaddingRight;
}
public int getProgressHeight() {
return progressHeight;
}
public void setProgressHeight(int progressHeight) {
this.progressHeight = progressHeight;
}
public float getMinProgress() {
return minProgress;
}
public float getMaxProgress() {
return maxProgress;
}
public void setProgressColor(@ColorInt int progressDefaultColor, @ColorInt int progressColor) {
this.progressDefaultColor = progressDefaultColor;
this.progressColor = progressColor;
}
public int getTickMarkTextColor() {
return tickMarkTextColor;
}
public void setTickMarkTextColor(@ColorInt int tickMarkTextColor) {
this.tickMarkTextColor = tickMarkTextColor;
}
public int getTickMarkInRangeTextColor() {
return tickMarkInRangeTextColor;
}
public void setTickMarkInRangeTextColor(@ColorInt int tickMarkInRangeTextColor) {
this.tickMarkInRangeTextColor = tickMarkInRangeTextColor;
}
public int getSeekBarMode() {
return seekBarMode;
}
/** * {@link #SEEKBAR_MODE_SINGLE} is single SeekBar * {@link #SEEKBAR_MODE_RANGE} is range SeekBar * * @param seekBarMode */ public void setSeekBarMode(@SeekBarModeDef int seekBarMode) {
this.seekBarMode = seekBarMode;
rightSB.setVisible(seekBarMode != SEEKBAR_MODE_SINGLE);
}
public int getTickMarkMode() {
return tickMarkMode;
}
/** * {@link #TICK_MARK_GRAVITY_LEFT} is number tick mark, it will locate the position according to the value. * {@link #TICK_MARK_GRAVITY_RIGHT} is text tick mark, it will be equally positioned. * * @param tickMarkMode */ public void setTickMarkMode(@TickMarkModeDef int tickMarkMode) {
this.tickMarkMode = tickMarkMode;
}
public int getTickMarkTextMargin() {
return tickMarkTextMargin;
}
public void setTickMarkTextMargin(int tickMarkTextMargin) {
this.tickMarkTextMargin = tickMarkTextMargin;
}
public int getTickMarkTextSize() {
return tickMarkTextSize;
}
public void setTickMarkTextSize(int tickMarkTextSize) {
this.tickMarkTextSize = tickMarkTextSize;
}
public int getTickMarkGravity() {
return tickMarkGravity;
}
/** * the tick mark text gravity * {@link #TICK_MARK_GRAVITY_LEFT} * {@link #TICK_MARK_GRAVITY_RIGHT} * {@link #TICK_MARK_GRAVITY_CENTER} * * @param tickMarkGravity */ public void setTickMarkGravity(@TickMarkGravityDef int tickMarkGravity) {
this.tickMarkGravity = tickMarkGravity;
}
public CharSequence[] getTickMarkTextArray() {
return tickMarkTextArray;
}
public void setTickMarkTextArray(CharSequence[] tickMarkTextArray) {
this.tickMarkTextArray = tickMarkTextArray;
}
public float getMinInterval() {
return minInterval;
}
public float getProgressRadius() {
return progressRadius;
}
public void setProgressRadius(float progressRadius) {
this.progressRadius = progressRadius;
}
public int getProgressColor() {
return progressColor;
}
public void setProgressColor(@ColorInt int progressColor) {
this.progressColor = progressColor;
}
public int getProgressDefaultColor() {
return progressDefaultColor;
}
public void setProgressDefaultColor(@ColorInt int progressDefaultColor) {
this.progressDefaultColor = progressDefaultColor;
}
public int getProgressDrawableId() {
return progressDrawableId;
}
public void setProgressDrawableId(@DrawableRes int progressDrawableId) {
this.progressDrawableId = progressDrawableId;
progressBitmap = null;
initProgressBitmap();
}
public int getProgressDefaultDrawableId() {
return progressDefaultDrawableId;
}
public void setProgressDefaultDrawableId(@DrawableRes int progressDefaultDrawableId) {
this.progressDefaultDrawableId = progressDefaultDrawableId;
progressDefaultBitmap = null;
initProgressBitmap();
}
public int getProgressWidth() {
return progressWidth;
}
public void setProgressWidth(int progressWidth) {
this.progressWidth = progressWidth;
}
public void setTypeface(Typeface typeFace) {
paint.setTypeface(typeFace);
}
public boolean isEnableThumbOverlap() {
return enableThumbOverlap;
}
public void setEnableThumbOverlap(boolean enableThumbOverlap) {
this.enableThumbOverlap = enableThumbOverlap;
}
public void setSteps(int steps) {
this.steps = steps;
}
public int getSteps() {
return steps;
}
public int getStepsColor() {
return stepsColor;
}
public void setStepsColor(@ColorInt int stepsColor) {
this.stepsColor = stepsColor;
}
public float getStepsWidth() {
return stepsWidth;
}
public void setStepsWidth(float stepsWidth) {
this.stepsWidth = stepsWidth;
}
public float getStepsHeight() {
return stepsHeight;
}
public void setStepsHeight(float stepsHeight) {
this.stepsHeight = stepsHeight;
}
public float getStepsRadius() {
return stepsRadius;
}
public void setStepsRadius(float stepsRadius) {
this.stepsRadius = stepsRadius;
}
public void setProgressTop(int progressTop) {
this.progressTop = progressTop;
}
public void setProgressBottom(int progressBottom) {
this.progressBottom = progressBottom;
}
public void setProgressLeft(int progressLeft) {
this.progressLeft = progressLeft;
}
public void setProgressRight(int progressRight) {
this.progressRight = progressRight;
}
public int getTickMarkLayoutGravity() {
return tickMarkLayoutGravity;
}
/** * the tick mark layout gravity * Gravity.TOP and Gravity.BOTTOM * * @param tickMarkLayoutGravity */ public void setTickMarkLayoutGravity(@TickMarkLayoutGravityDef int tickMarkLayoutGravity) {
this.tickMarkLayoutGravity = tickMarkLayoutGravity;
}
public int getGravity() {
return gravity;
}
/** * the RangeSeekBar gravity * Gravity.TOP and Gravity.BOTTOM * * @param gravity */ public void setGravity(@GravityDef int gravity) {
this.gravity = gravity;
}
public boolean isStepsAutoBonding() {
return stepsAutoBonding;
}
public void setStepsAutoBonding(boolean stepsAutoBonding) {
this.stepsAutoBonding = stepsAutoBonding;
}
public int getStepsDrawableId() {
return stepsDrawableId;
}
public void setStepsDrawableId(@DrawableRes int stepsDrawableId) {
this.stepsBitmaps.clear();
this.stepsDrawableId = stepsDrawableId;
initStepsBitmap();
}
public List<Bitmap> getStepsBitmaps() {
return stepsBitmaps;
}
public void setStepsBitmaps(List<Bitmap> stepsBitmaps) {
if (stepsBitmaps == null || stepsBitmaps.isEmpty() || stepsBitmaps.size() <= steps) {
Log.e(RangeSeekBar.class.getName(),"stepsBitmaps must > steps !");
}
this.stepsBitmaps.clear();
this.stepsBitmaps.addAll(stepsBitmaps);
}
public void setStepsDrawable(List<Integer> stepsDrawableIds) {
if (stepsDrawableIds == null || stepsDrawableIds.isEmpty() || stepsDrawableIds.size() <= steps) {
Log.e(RangeSeekBar.class.getName(),"stepsDrawableIds must > steps !");
}
if (!verifyStepsMode()) {
Log.e(RangeSeekBar.class.getName(),"stepsWidth must > 0, stepsHeight must > 0,steps must > 0 First!!");
}
List<Bitmap> stepsBitmaps = new ArrayList<>();
for (int i = 0; i < stepsDrawableIds.size(); i++) {
stepsBitmaps.add(drawableToBitmap(getContext(), (int) stepsWidth, (int) stepsHeight, stepsDrawableIds.get(i)));
}
setStepsBitmaps(stepsBitmaps);
}
public interface OnRangeChangedListener {
void onRangeChanged(RangeSeekBar view, float leftValue, float rightValue, boolean isFromUser);
void onStartTrackingTouch(RangeSeekBar view, boolean isLeft);
void onStopTrackingTouch(RangeSeekBar view, boolean isLeft);
}
public class SavedState extends View.BaseSavedState {
public float minValue;
public float maxValue;
public float rangeInterval;
public int tickNumber;
public float currSelectedMin;
public float currSelectedMax;
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
minValue = in.readFloat();
maxValue = in.readFloat();
rangeInterval = in.readFloat();
tickNumber = in.readInt();
currSelectedMin = in.readFloat();
currSelectedMax = in.readFloat();
}
@Override public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeFloat(minValue);
out.writeFloat(maxValue);
out.writeFloat(rangeInterval);
out.writeInt(tickNumber);
out.writeFloat(currSelectedMin);
out.writeFloat(currSelectedMax);
}
public final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public class SeekBarState {
public String indicatorText;
public float value; //now progress value public boolean isMin;
public boolean isMax;
@Override public String toString() {
return "indicatorText: " + indicatorText + " ,isMin: " + isMin + " ,isMax: " + isMax;
}
}
}
Output :
No comments:
Post a Comment