Skip to content

Instantly share code, notes, and snippets.

@rizafu
Last active May 25, 2019 06:10
Show Gist options
  • Select an option

  • Save rizafu/230f2e53cba49660027055526b29bc17 to your computer and use it in GitHub Desktop.

Select an option

Save rizafu/230f2e53cba49660027055526b29bc17 to your computer and use it in GitHub Desktop.
Android Review Slider
public class ReviewSlider extends View {
private Paint paint = new Paint();
private Paint circleDefaultPaint = new Paint();
private Paint circleWhitePaint = new Paint();
private Paint selectedPaint = new Paint();
private int mNumOfCircle = 2;
private Bitmap mThumbImage = null;
private float mLineHeight;
private float mThumbRadius;
private float mCircleRadius;
private float mPadding;
private float mThumbXPosition;
private int mActivePointerId = 255;
private boolean mPressedThumb = false;
private float mCenterY;
private float mLeftX;
private float mLeftY;
private float mRightX;
private float mRightY;
private float mMarginLeft;
private float mDelta;
private List<Float> mThumbContainerXPosition = new ArrayList<>();
private OnValueChangeListener listener;
private boolean mRunableListener = false;
private int mPosition;
private float mThumbBestXPosition;
private int mValue;
public ReviewSlider(Context context) {
super(context);
}
public ReviewSlider(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ReviewSlider);
int N = a.getIndexCount();
for (int i = 0; i < N; ++i) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.ReviewSlider_numOfCircle:
mNumOfCircle = a.getInt(attr, 2);
break;
case R.styleable.ReviewSlider_thumbImageSrc:
int bitmap = a.getResourceId(attr, 0);
mThumbImage = BitmapFactory.decodeResource(getResources(), bitmap);
mLineHeight = 0.1f * mThumbImage.getHeight();
mThumbRadius = 0.3f * mThumbImage.getHeight();
mCircleRadius = 0.2f * mThumbRadius;
mPadding = 0.5f * mThumbImage.getHeight();
break;
}
}
a.recycle();
}
public ReviewSlider(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setSize(int size) {
mNumOfCircle = size;
invalidate();
}
public void setOnValueChangeListener(OnValueChangeListener listener) {
this.listener = listener;
}
public float getThumbXPosition() {
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
mMarginLeft = lp.leftMargin;
return (mThumbBestXPosition + mMarginLeft) - (mPadding / 2);
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterY = 0.5f * getHeight();
mLeftX = mPadding;
mLeftY = mCenterY - (mLineHeight / 2);
mRightX = getWidth() - mPadding;
mRightY = 0.5f * (getHeight() + mLineHeight);
mDelta = (mRightX - mLeftX) / (mNumOfCircle - 1);
mThumbXPosition = mPadding;
mThumbContainerXPosition.add(mLeftX);
for (int i = 1; i < mNumOfCircle - 1; i++) {
mThumbContainerXPosition.add(mLeftX + (i * mDelta));
}
mThumbContainerXPosition.add(mRightX);
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 200;
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
width = MeasureSpec.getSize(widthMeasureSpec);
}
int height = mThumbImage.getHeight() + 20;
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
setMeasuredDimension(width, height);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
int pointerIndex = event.findPointerIndex(mActivePointerId);
float mDownMotionX = event.getX(pointerIndex);
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mPressedThumb = isInThumbRange(mDownMotionX);
mThumbBestXPosition = mDownMotionX;
if (!mPressedThumb) {
Integer position = isInThumbContainerPosition(mDownMotionX);
if (position != null) {
setPosition(position);
}
}
break;
case MotionEvent.ACTION_MOVE:
mThumbXPosition = mDownMotionX;
if (mThumbXPosition < mPadding) {
mThumbXPosition = mPadding;
}
if (mThumbXPosition > (getWidth() - mPadding)) {
mThumbXPosition = (getWidth() - mPadding);
}
invalidate();
break;
case MotionEvent.ACTION_UP:
doAction(mDownMotionX);
if (listener != null) {
listener.onFinished();
}
break;
case MotionEvent.ACTION_CANCEL:
doAction(mDownMotionX);
break;
}
return true;
}
public void doAction(float motionX) {
int idx;
if (mPressedThumb) {
idx = moveToNearestThumbContainer(motionX);
mThumbXPosition = mThumbContainerXPosition.get(idx);
mPressedThumb = false;
mRunableListener = true;
invalidate();
} else {
Integer position = isInThumbContainerPosition(motionX);
if (position != null) {
setPosition(position);
}
}
}
public void setPosition(int position) {
Log.d("CustomBar", "setPosition(position=" + position + ")");
mPosition = position;
recalculateThumb();
invalidate();
}
public int getValue() {
return mValue;
}
private void recalculateThumb() {
mThumbXPosition = mThumbContainerXPosition.size() > 0 ? mThumbContainerXPosition.get(mPosition) : 0;
mRunableListener = true;
Log.d("CustomBar", "recalculateThumb() => mThumbXPosition=" + mThumbXPosition + "; mPosition=" + mPosition + "; size=" + mThumbContainerXPosition.size());
}
public void reset() {
setPosition(0);
}
private boolean isInThumbRange(float touchX) {
return (touchX <= mThumbXPosition + mThumbRadius) && (touchX >= mThumbXPosition - mThumbRadius);
}
private Integer isInThumbContainerPosition(float touchX) {
for (int i = 0; i < mNumOfCircle; i++) {
float xPosition = mThumbContainerXPosition.get(i);
if ((touchX <= xPosition + mThumbRadius) && (touchX >= xPosition - mThumbRadius)) {
return i;
}
}
return null;
}
private int moveToNearestThumbContainer(float touchX) {
int tmp = 0;
for (int i = 0; i < mThumbContainerXPosition.size(); i++) {
float t = mThumbContainerXPosition.get(i);
float delta = mDelta / 2;
if (i == mThumbContainerXPosition.size() - 1 && touchX > t + delta) {
tmp = i;
break;
}
if ((touchX >= t - delta) && (touchX <= t + delta)) {
tmp = i;
break;
}
}
return tmp;
}
@SuppressLint("DrawAllocation")
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw rect bounds
paint.setAntiAlias(true);
paint.setColor(Color.LTGRAY);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(2);
canvas.drawRect(mLeftX, mLeftY, mRightX, mRightY, paint);
circleDefaultPaint.setAntiAlias(true);
circleDefaultPaint.setColor(Color.parseColor("#2b3e4f"));
circleDefaultPaint.setStyle(Paint.Style.FILL);
circleDefaultPaint.setStrokeWidth(2);
circleWhitePaint.setAntiAlias(true);
circleWhitePaint.setColor(Color.WHITE);
circleWhitePaint.setStyle(Paint.Style.FILL);
circleWhitePaint.setStrokeWidth(2);
selectedPaint.setAntiAlias(true);
selectedPaint.setColor(Color.parseColor("#ff004d"));
selectedPaint.setStyle(Paint.Style.FILL);
selectedPaint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
selectedPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(mLeftX, mLeftY, mThumbXPosition, mRightY, selectedPaint);
// Draw rest of the circle'Bounds
for (int i = 0; i < mThumbContainerXPosition.size(); i++) {
canvas.drawCircle(mThumbContainerXPosition.get(i), mCenterY, mCircleRadius, i <= moveToNearestThumbContainer(mThumbXPosition) ? circleWhitePaint : circleDefaultPaint);
}
// Draw Thumb
paint.setShader(null);
drawThumb(mThumbXPosition - mPadding, mCenterY - mPadding, canvas);
mThumbBestXPosition = mThumbXPosition - mPadding;
mValue = moveToNearestThumbContainer(mThumbXPosition);
if (listener != null) {
listener.onValuesChanged(this, mValue, (mThumbBestXPosition + mMarginLeft) - (mPadding / 2));
}
// Run listener after redrawn
//if (mRunnableListener) runListener();
}
public void runListener() {
int idx = moveToNearestThumbContainer(mThumbXPosition);
mRunableListener = false;
if (listener != null) {
listener.onValuesChanged(this, idx, (mThumbBestXPosition + mMarginLeft) - (mPadding / 2));
}
}
private void drawThumb(float leftX, float leftY, Canvas canvas) {
canvas.drawBitmap(mThumbImage, leftX, leftY, paint);
}
public interface OnValueChangeListener {
void onValuesChanged(ReviewSlider bar, int position, float xPosition);
void onFinished();
}
}
<declare-styleable name="ReviewSlider">
<attr name="numOfCircle" format="integer" />
<attr name="thumbImageSrc" format="reference" />
</declare-styleable>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment