Last active
May 25, 2019 06:10
-
-
Save rizafu/230f2e53cba49660027055526b29bc17 to your computer and use it in GitHub Desktop.
Android Review Slider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | |
| } | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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