com.leeon.blank.widget.RangeBarNew.java Source code

Java tutorial

Introduction

Here is the source code for com.leeon.blank.widget.RangeBarNew.java

Source

/*
 * Copyright (C) 2014 Roy Wang
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.leeon.blank.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Scroller;
import com.leeon.blank.R;
import com.leeon.blank.util.LogUtil;

/**
 * A seekbar contains two cursor(left and right). Multiple touch supported.
 */
public class RangeBarNew extends View {

    private boolean isInfinite = true;

    private float downY;
    private int mTouchSlop;

    private enum DIRECTION {
        LEFT, RIGHT
    }

    private double slopDistance = 2;

    private int mDuration = 100;

    /**
     * Scroller for left and right cursor
     */
    private Scroller mLeftScroller;
    private Scroller mRightScroller;

    /**
     * Background drawables for left and right cursor. State list supported.
     */
    private Drawable mLeftCursorBG;
    private Drawable mRightCursorBG;
    //
    private int cursorHeight;

    //
    private Drawable indicatorDrawable;
    //
    private int indicatorHeight;

    /**
     * Represent states.
     */
    private int[] mPressedEnableState = new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled };
    private int[] mUnPressedEanabledState = new int[] { -android.R.attr.state_pressed,
            android.R.attr.state_enabled };

    /**
     * Colors of text and rangeBar in different states.
     */
    private int mTextColorNormal;
    private int mRangeBarColorNormal;
    private int mRangeBarColorSelected;

    /**
     * Height of rangeBar
     */
    private int mRangeBarHeight;

    /**
     * Size of text mark.
     */
    private int mTextSize;

    /**
     * Space between the text and the rangeBar
     */
    private int mMarginBetween;

    /**
     * Length of every part. As we divide some parts according to marks.
     */
    private float mPartLength, mTinyPartLength;

    /**
     * Contents of text mark.
     */
    private int[] mTextArray = { 0, 5, 15, 20 };

    // 
    private float[] mTextWidthArray;

    private Rect mPaddingRect;
    private Rect mLeftCursorRect;
    private Rect mRightCursorRect;
    private Rect mLeftIndicatorRect;
    private Rect mRightIndicatorRect;

    private RectF mRangeBarRect;
    private RectF mRangeBarRectSelected;

    private float mLeftCursorIndex = 0;
    private float mRightCursorIndex = 1.0f;

    private float mLeftCursorPreIndex = 0;
    private float mRightCursorPreIndex = 1;

    private float mLeftCursorNextIndex = 0;
    private float mRightCursorNextIndex = 1;

    private int mValueLeft = 0, mValueRight = -1;

    private Paint mPaint;

    private int mLeftPointerLastX;
    private int mRightPointerLastX;

    private int mLeftPointerID = -1;
    private int mRightPointerID = -1;

    private boolean mLeftHit;
    private boolean mRightHit;

    private float mLeftBoundary, mRightBoundary;

    private OnCursorChangeListener mListener;

    public RangeBarNew(Context context) {
        this(context, null, 0);
    }

    public RangeBarNew(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

        applyConfig(context, attrs);

        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(ViewConfiguration.get(context));

        mPaddingRect = new Rect();
        mLeftCursorRect = new Rect();
        mRightCursorRect = new Rect();
        mLeftIndicatorRect = new Rect();
        mRightIndicatorRect = new Rect();

        mPaddingRect.left = getPaddingLeft();
        mPaddingRect.top = getPaddingTop();
        mPaddingRect.right = getPaddingRight();
        mPaddingRect.bottom = getPaddingBottom();

        mRangeBarRect = new RectF();
        mRangeBarRectSelected = new RectF();

        if (mTextArray != null) {
            mTextWidthArray = new float[mTextArray.length];
        }

        mLeftScroller = new Scroller(context);
        mRightScroller = new Scroller(context);

        initPaint();
        initTextWidthArray();

        setWillNotDraw(false);
        setFocusable(true);
        setClickable(true);
    }

    public void restView() {
        initPaint();
        initTextWidthArray();
        setWillNotDraw(false);
        setFocusable(true);
        setClickable(true);
    }

    private void applyConfig(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RangeBar);
        indicatorDrawable = a.getDrawable(R.styleable.RangeBar_indicatorDrawable);
        mLeftCursorBG = a.getDrawable(R.styleable.RangeBar_leftCursorBackground);
        mRightCursorBG = a.getDrawable(R.styleable.RangeBar_rightCursorBackground);
        mTextColorNormal = a.getColor(R.styleable.RangeBar_textColorNormal, Color.GRAY);
        mRangeBarColorNormal = a.getColor(R.styleable.RangeBar_barColorNormal, Color.rgb(218, 215, 215));
        mRangeBarColorSelected = a.getColor(R.styleable.RangeBar_barColorSelected, Color.rgb(197, 0, 0));
        mRangeBarHeight = (int) a.getDimension(R.styleable.RangeBar_barHeight, 10);
        mTextSize = (int) a.getDimension(R.styleable.RangeBar_textSize, 14);
        mMarginBetween = (int) a.getDimension(R.styleable.RangeBar_spaceBetween, 16);
        isInfinite = a.getBoolean(R.styleable.RangeBar_isInfinite, true);

        CharSequence[] charArray = a.getTextArray(R.styleable.RangeBar_markTextArray);

        if (null == charArray || charArray.length <= 1) {
            throw new IllegalArgumentException("markTextArray should at least have 2 number!");
        }

        mTextArray = new int[isInfinite ? charArray.length + 1 : charArray.length];

        for (int i = 0; i < charArray.length; i++) {
            mTextArray[i] = Integer.parseInt(charArray[i].toString());
            if (mTextArray[i] < 0) {
                throw new IllegalArgumentException("markTextArray must be a positive number array");
            }
        }

        if (isInfinite) {
            mTextArray[charArray.length] = -1;
        }

        mLeftCursorIndex = 0;
        mRightCursorIndex = (mTextArray.length - 1) * 5;
        mRightCursorNextIndex = mRightCursorIndex;
        a.recycle();
    }

    private void initPaint() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Style.FILL);
        mPaint.setTextSize(mTextSize);
    }

    private void initTextWidthArray() {
        if (mTextArray != null && mTextArray.length > 0) {
            final int length = mTextArray.length;
            for (int i = 0; i < length; i++) {
                mTextWidthArray[i] = mPaint.measureText(mTextArray[i] == -1 ? "" : mTextArray[i] + "");
            }
        }
    }

    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        super.setPadding(left, top, right, bottom);
        if (mPaddingRect == null) {
            mPaddingRect = new Rect();
        }
        mPaddingRect.left = left;
        mPaddingRect.top = top;
        mPaddingRect.right = right;
        mPaddingRect.bottom = bottom;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int leftPointerH = mLeftCursorBG.getIntrinsicHeight();
        final int rightPointerH = mRightCursorBG.getIntrinsicHeight();
        // Get max height between left and right cursor.
        cursorHeight = Math.max(leftPointerH, rightPointerH);

        indicatorHeight = Math.max(indicatorDrawable.getIntrinsicHeight(), 2 * mTextSize);

        // Then get max height between seekbar and cursor.
        //final int maxOfCursorAndSeekbar = Math.max(mRangeBarHeight, maxOfCursor);
        // So we get the needed height.
        int heightNeeded = mRangeBarHeight + indicatorHeight + cursorHeight + 2 * mMarginBetween + mPaddingRect.top
                + mPaddingRect.bottom;

        heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightNeeded, MeasureSpec.EXACTLY);

        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        mRangeBarRect.left = mPaddingRect.left + mLeftCursorBG.getIntrinsicWidth() / 2;
        mRangeBarRect.right = widthSize - mPaddingRect.right - mRightCursorBG.getIntrinsicWidth() / 2;
        mRangeBarRect.top = mPaddingRect.top + indicatorHeight + mMarginBetween;
        mRangeBarRect.bottom = mRangeBarRect.top + mRangeBarHeight;

        mRangeBarRectSelected.top = mRangeBarRect.top;
        mRangeBarRectSelected.bottom = mRangeBarRect.bottom;
        if (0 != mTextArray.length - 1) {
            mPartLength = (mRangeBarRect.right - mRangeBarRect.left) / (mTextArray.length - 1);
            mTinyPartLength = mPartLength / 5;
        }
        mLeftBoundary = mRangeBarRect.left - mLeftCursorBG.getIntrinsicWidth() / 2;
        mRightBoundary = mRangeBarRect.right + mRightCursorBG.getIntrinsicWidth() / 2;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int length = mTextArray.length;

        /*** Draw text marks ***/
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColorNormal);
        for (int i = 0; i < length; i++) {
            final String text2draw = mTextArray[i] == -1 ? "" : mTextArray[i] + "";
            final float textWidth = mTextWidthArray[i];
            float textDrawLeft;
            // The last text mark's draw location should be adjust.
            if (i == length - 1) {
                textDrawLeft = mRangeBarRect.right - textWidth / 2;
            } else {
                textDrawLeft = mRangeBarRect.left + i * mPartLength - textWidth / 2;
            }
            canvas.drawText(text2draw, textDrawLeft, mRangeBarRect.top - 24 - mMarginBetween, mPaint);

            //draw lines
            float lineDrawLeft = mRangeBarRect.left + i * mPartLength;
            canvas.drawLine(lineDrawLeft, mRangeBarRect.top, lineDrawLeft, mRangeBarRect.top - 24, mPaint);

            //draw short lines
            if (i != length - 1) {
                for (int j = 1; j < 5; j++) {
                    float lineDrawLeft_short = lineDrawLeft + j * mTinyPartLength;
                    canvas.drawLine(lineDrawLeft_short, mRangeBarRect.top, lineDrawLeft_short,
                            mRangeBarRect.top - 18, mPaint);
                }
            }
        }

        /*** Draw seekBar ***/
        final float radius = mRangeBarHeight / 4f;
        mRangeBarRectSelected.left = mRangeBarRect.left + mTinyPartLength * mLeftCursorIndex;
        mRangeBarRectSelected.right = mRangeBarRect.left + mTinyPartLength * mRightCursorIndex;
        // If whole of seekbar is selected, just draw seekbar with selectedcolor.
        if (mLeftCursorIndex == 0 && mRightCursorIndex == (length - 1) * 5) {
            mPaint.setColor(mRangeBarColorSelected);
            canvas.drawRoundRect(mRangeBarRect, radius, radius, mPaint);
        } else {
            // Draw background first.
            mPaint.setColor(mRangeBarColorNormal);
            canvas.drawRoundRect(mRangeBarRect, radius, radius, mPaint);

            // Draw selected part.
            mPaint.setColor(mRangeBarColorSelected);
            // Can draw rounded rectangle, but original rectangle is enough.
            // Because edges of selected part will be covered by cursors.
            canvas.drawRect(mRangeBarRectSelected, mPaint);
        }

        /*** Draw cursors ***/
        // left cursor first
        final int leftWidth = mLeftCursorBG.getIntrinsicWidth();
        final int leftHeight = mLeftCursorBG.getIntrinsicHeight();
        final int leftLeft = (int) (mRangeBarRectSelected.left - leftWidth / 2f);
        final int leftTop = (int) (mRangeBarRect.bottom + mMarginBetween);
        mLeftCursorRect.left = leftLeft;
        mLeftCursorRect.top = leftTop;
        mLeftCursorRect.right = leftLeft + leftWidth;
        mLeftCursorRect.bottom = leftTop + leftHeight;
        mLeftCursorBG.setBounds(mLeftCursorRect);
        mLeftCursorBG.draw(canvas);

        // right cursor second
        final int rightWidth = mRightCursorBG.getIntrinsicWidth();
        final int rightHeight = mRightCursorBG.getIntrinsicHeight();
        final int rightLeft = (int) (mRangeBarRectSelected.right - rightWidth / 2f);
        final int rightTop = (int) (mRangeBarRect.bottom + mMarginBetween);
        mRightCursorRect.left = rightLeft;
        mRightCursorRect.top = rightTop;
        mRightCursorRect.right = rightLeft + rightWidth;
        mRightCursorRect.bottom = rightTop + rightHeight;
        mRightCursorBG.setBounds(mRightCursorRect);
        mRightCursorBG.draw(canvas);

        //Draw indicators
        final int indicatorWidth = Math.max(indicatorDrawable.getIntrinsicWidth(), (int) mPaint.measureText("888"));
        if (mLeftHit && mLeftPointerID != -1) {
            final int indicatorLeft = (int) (mRangeBarRectSelected.left - (float) indicatorWidth / 2);
            final int indicatorTop = mPaddingRect.top;
            mLeftIndicatorRect.left = indicatorLeft;
            mLeftIndicatorRect.top = indicatorTop;
            mLeftIndicatorRect.right = indicatorLeft + indicatorWidth;
            mLeftIndicatorRect.bottom = indicatorTop + indicatorHeight;
            indicatorDrawable.setBounds(mLeftIndicatorRect);
            indicatorDrawable.draw(canvas);
            mPaint.setColor(Color.WHITE);
            mValueLeft = getValueByIndex(mLeftCursorIndex);
            canvas.drawText(mValueLeft + "", mLeftIndicatorRect.centerX() - mPaint.measureText(mValueLeft + "") / 2,
                    mLeftIndicatorRect.centerY() + mTextSize / 4, mPaint);
        }

        if (mRightHit && mRightPointerID != -1) {
            final int indicatorRightLeft = (int) (mRangeBarRectSelected.right - (float) rightWidth / 2);
            final int indicatorRightTop = mPaddingRect.top;
            mRightIndicatorRect.left = indicatorRightLeft;
            mRightIndicatorRect.top = indicatorRightTop;
            mRightIndicatorRect.right = indicatorRightLeft + indicatorWidth;
            mRightIndicatorRect.bottom = indicatorRightTop + indicatorHeight;
            indicatorDrawable.setBounds(mRightIndicatorRect);
            indicatorDrawable.draw(canvas);
            mPaint.setColor(Color.WHITE);
            mValueRight = getValueByIndex(mRightCursorIndex);
            if (mValueRight == -1) {
                canvas.drawText("", mRightIndicatorRect.centerX() - mPaint.measureText("") / 2,
                        mRightIndicatorRect.centerY() + mTextSize / 4, mPaint);
            } else {
                canvas.drawText(mValueRight + "",
                        mRightIndicatorRect.centerX() - mPaint.measureText(mValueRight + "") / 2,
                        mRightIndicatorRect.centerY() + mTextSize / 4, mPaint);
            }
        }

        triggerCallback();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        getParent().requestDisallowInterceptTouchEvent(false);
        // For multiple touch
        final int action = event.getActionMasked();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            downY = event.getY();
            handleTouchDown(event);
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            handleTouchDown(event);
            break;
        case MotionEvent.ACTION_MOVE:
            final float yDiff = Math.abs(event.getY() - downY);
            if (yDiff > mTouchSlop) {
                return false;
            } else {
                getParent().requestDisallowInterceptTouchEvent(true);
                handleTouchMove(event);
            }
            break;
        case MotionEvent.ACTION_POINTER_UP:
            handleTouchUp(event);
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            handleTouchUp(event);
            break;
        }
        return super.onTouchEvent(event);
    }

    private void handleTouchDown(MotionEvent event) {
        final int actionIndex = (event.getAction()
                & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        final int downX = (int) event.getX(actionIndex);
        final int downY = (int) event.getY(actionIndex);

        if (mLeftCursorRect.contains(downX, downY)) {
            if (mLeftHit) {
                return;
            }
            // If hit, change state of drawable, and record id of touch pointer.
            mLeftPointerLastX = downX;
            mLeftCursorBG.setState(mPressedEnableState);
            mLeftPointerID = event.getPointerId(actionIndex);
            mLeftHit = true;
            invalidate();
        } else if (mRightCursorRect.contains(downX, downY)) {
            if (mRightHit) {
                return;
            }
            mRightPointerLastX = downX;
            mRightCursorBG.setState(mPressedEnableState);
            mRightPointerID = event.getPointerId(actionIndex);
            mRightHit = true;
            invalidate();
        }
    }

    private void handleTouchUp(MotionEvent event) {
        final int actionIndex = (event.getAction()
                & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        final int actionID = event.getPointerId(actionIndex);
        if (actionID == mLeftPointerID) {
            if (!mLeftHit) {
                return;
            }
            // If cursor between in tow mark locations, it should be located on the lower or higher one.
            if (getDiffByX(event.getX()) == 1) {
                // step 1:Calculate the offset with lower mark.
                final float offset = mLeftCursorIndex % 5;
                // step 2:Decide which mark will go to.
                if (offset < 2.5f) {
                    // If left cursor want to be located on lower mark, go ahead
                    // guys.
                    // Because right cursor will never appear lower than the
                    // left one.
                    mLeftCursorNextIndex = (int) (mLeftCursorIndex / 5) * 5;
                } else {
                    mLeftCursorNextIndex = (int) (mLeftCursorIndex / 5) * 5 + 1;
                    // If left cursor want to be located on higher mark,
                    // situation becomes a little complicated.
                    // We should check that whether distance between left and
                    // right cursor is less than 1, and next index of left
                    // cursor is difference with current
                    // of right one.
                    if (Math.abs(mLeftCursorIndex - mRightCursorIndex) <= 5
                            && mLeftCursorNextIndex == mRightCursorNextIndex) {
                        // Left can not go to the higher, just to the lower one.
                        mLeftCursorNextIndex = (int) (mLeftCursorIndex / 5) * 5;
                    }
                }
                // step 3: Move to.
                if (!mLeftScroller.computeScrollOffset()) {
                    final int fromX = (int) (mLeftCursorIndex * mTinyPartLength);
                    mLeftScroller.startScroll(fromX, 0, (int) (mLeftCursorNextIndex * mTinyPartLength - fromX), 0,
                            mDuration);
                }
                if (mLeftCursorNextIndex != mLeftCursorPreIndex) {
                    mLeftCursorPreIndex = mLeftCursorNextIndex;
                }
            }

            // Reset values of parameters
            mLeftPointerLastX = 0;
            mLeftCursorBG.setState(mUnPressedEanabledState);
            mLeftPointerID = -1;
            mLeftHit = false;
            invalidate();
        } else if (actionID == mRightPointerID) {
            if (!mRightHit) {
                return;
            }

            if (getDiffByX(event.getX()) == 1) {
                final float offset = mRightCursorIndex % 5;
                if (offset > 2.5f) {
                    mRightCursorNextIndex = (int) (mRightCursorIndex / 5) * 5 + 1;
                } else {
                    mRightCursorNextIndex = (int) (mRightCursorIndex / 5) * 5;
                    if (Math.abs(mLeftCursorIndex - mRightCursorIndex) <= 5
                            && mRightCursorNextIndex == mLeftCursorNextIndex) {
                        mRightCursorNextIndex = (int) (mRightCursorIndex / 5) * 5 + 1;
                    }
                }
                if (!mRightScroller.computeScrollOffset()) {
                    final int fromX = (int) (mRightCursorIndex * mTinyPartLength);
                    mRightScroller.startScroll(fromX, 0, (int) (mRightCursorNextIndex * mTinyPartLength - fromX), 0,
                            mDuration);
                }
                if (mRightCursorNextIndex != mRightCursorPreIndex) {
                    mRightCursorPreIndex = mRightCursorNextIndex;
                }
            }

            mRightPointerLastX = 0;
            mLeftCursorBG.setState(mUnPressedEanabledState);
            mRightPointerID = -1;
            mRightHit = false;
            invalidate();
        }
    }

    private void handleTouchMove(MotionEvent event) {
        if (mLeftHit && mLeftPointerID != -1) {
            final int index = event.findPointerIndex(mLeftPointerID);
            final float x = event.getX(index);
            float deltaX = x - mLeftPointerLastX;
            mLeftPointerLastX = (int) x;
            DIRECTION direction = (deltaX < 0 ? DIRECTION.LEFT : DIRECTION.RIGHT);
            if (direction == DIRECTION.LEFT && mLeftCursorIndex == 0) {
                return;
            }
            if (mLeftCursorRect.left + deltaX < mLeftBoundary) {
                deltaX = mLeftBoundary - mLeftCursorRect.left;
            }
            if (isInfinite && (mLeftCursorRect.right + deltaX > mRightBoundary - mPartLength - slopDistance)) {
                deltaX = (float) (mRightBoundary - mPartLength - slopDistance - mLeftCursorRect.right);
            }
            // Check whether left and right cursor will collision.
            if (mLeftCursorRect.right + deltaX >= mRightCursorRect.left) {
                // Check whether right cursor is in "Touch" mode( if in touch
                // mode, represent that we can not move it at all), or right
                // cursor reach the boundary.
                if (mRightHit || Math.round(mRightCursorIndex) == (mTextArray.length - 1) * 5
                        || mRightScroller.computeScrollOffset()) {
                    // Just move left cursor to the left side of right one.
                    deltaX = mRightCursorRect.left - mLeftCursorRect.right;
                } else {
                    // Move right cursor to higher location.
                    mRightCursorNextIndex = Math.min(mRightCursorIndex + 1, (mTextArray.length - 1) * 5);
                    if (!mRightScroller.computeScrollOffset()) {
                        final int fromX = (int) (mRightCursorIndex * mTinyPartLength);
                        mRightScroller.startScroll(fromX, 0,
                                (int) (mRightCursorNextIndex * mPartLength / 5 - fromX), 0, mDuration);
                    }
                }
            }
            // After some calculate, if deltaX is still be zero, do quick return.
            if (deltaX == 0) {
                return;
            }
            // Calculate the movement.
            final float moveX = deltaX / mTinyPartLength;
            mLeftCursorIndex += moveX;
            invalidate();
        }
        if (mRightHit && mRightPointerID != -1) {
            final int index = event.findPointerIndex(mRightPointerID);
            final float x = event.getX(index);
            float deltaX = x - mRightPointerLastX;
            mRightPointerLastX = (int) x;
            DIRECTION direction = (deltaX < 0 ? DIRECTION.LEFT : DIRECTION.RIGHT);
            final int maxIndex = mTextArray.length - 1;
            if (direction == DIRECTION.RIGHT && mRightCursorIndex == maxIndex) {
                return;
            }
            if (mRightCursorRect.right + deltaX > mRightBoundary + slopDistance) {
                deltaX = (float) (mRightBoundary + slopDistance - mRightCursorRect.right);
            }
            if (mRightCursorRect.left + deltaX < mLeftCursorRect.right) {
                if (mLeftHit || Math.round(mLeftCursorIndex) == 0 || mLeftScroller.computeScrollOffset()) {
                    deltaX = mLeftCursorRect.right - mRightCursorRect.left;
                } else {
                    mLeftCursorNextIndex = Math.max(0, mLeftCursorIndex - 1);
                    if (!mLeftScroller.computeScrollOffset()) {
                        final int fromX = (int) (mLeftCursorIndex * mPartLength / 5);
                        mLeftScroller.startScroll(fromX, 0, (int) (mLeftCursorNextIndex * mPartLength / 5 - fromX),
                                0, mDuration);
                    }
                }
            }
            if (deltaX == 0) {
                return;
            }
            final float moveX = deltaX / mTinyPartLength;
            mRightCursorIndex += moveX;
            invalidate();
        }
    }

    public void rangeBarInit(int left, int right) {
        LogUtil.d("rangeBarInit,left:" + left + ",right:" + right);
        LogUtil.d("getIndexByValue left:" + getIndexByValue(left));
        LogUtil.d("getIndexByValue right:" + getIndexByValue(right));
        mLeftCursorIndex = getIndexByValue(left);
        mRightCursorIndex = getIndexByValue(right);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mLeftScroller.computeScrollOffset()) {
            final int deltaX = mLeftScroller.getCurrX();
            mLeftCursorIndex = (float) deltaX / mTinyPartLength;
            invalidate();
        }
        if (mRightScroller.computeScrollOffset()) {
            final int deltaX = mRightScroller.getCurrX();
            mRightCursorIndex = (float) deltaX / mTinyPartLength;
            invalidate();
        }
    }

    private void triggerCallback() {
        if (mListener == null) {
            return;
        }
        mListener.onCursorChanged(getValueByIndex(mLeftCursorIndex), getValueByIndex(mRightCursorIndex),
                !mLeftHit && !mRightHit);//touchup??
    }

    public void setLeftSelection(int partIndex) {
        if (partIndex >= mTextArray.length - 1 || partIndex <= 0) {
            throw new IllegalArgumentException("Index should from 0 to size of text array minus 2!");
        }
        if (partIndex != mLeftCursorIndex) {
            if (!mLeftScroller.isFinished()) {
                mLeftScroller.abortAnimation();
            }
            mLeftCursorNextIndex = partIndex;
            final int leftFromX = (int) (mLeftCursorIndex * mPartLength / 5);
            mLeftScroller.startScroll(leftFromX, 0, (int) (mLeftCursorNextIndex * mPartLength / 5 - leftFromX), 0,
                    mDuration);
            if (mRightCursorIndex <= mLeftCursorNextIndex) {
                if (!mRightScroller.isFinished()) {
                    mRightScroller.abortAnimation();
                }
                mRightCursorNextIndex = mLeftCursorNextIndex + 1;
                final int rightFromX = (int) (mRightCursorIndex * mPartLength / 5);
                mRightScroller.startScroll(rightFromX, 0,
                        (int) (mRightCursorNextIndex * mPartLength / 5 - rightFromX), 0, mDuration);
            }
            invalidate();
        }
    }

    public void setRightSelection(int partIndex) {
        if (partIndex >= mTextArray.length || partIndex <= 0) {
            throw new IllegalArgumentException("Index should from 1 to size of text array minus 1!");
        }
        if (partIndex != mRightCursorIndex) {
            if (!mRightScroller.isFinished()) {
                mRightScroller.abortAnimation();
            }
            mRightCursorNextIndex = partIndex;
            final int rightFromX = (int) (mPartLength / 5 * mRightCursorIndex);
            mRightScroller.startScroll(rightFromX, 0, (int) (mRightCursorNextIndex * mPartLength / 5 - rightFromX),
                    0, mDuration);
            if (mLeftCursorIndex >= mRightCursorNextIndex) {
                if (!mLeftScroller.isFinished()) {
                    mLeftScroller.abortAnimation();
                }
                mLeftCursorNextIndex = mRightCursorNextIndex - 1;
                final int leftFromX = (int) (mLeftCursorIndex * mPartLength / 5);
                mLeftScroller.startScroll(leftFromX, 0, (int) (mLeftCursorNextIndex * mPartLength / 5 - leftFromX),
                        0, mDuration);
            }
            invalidate();
        }
    }

    public void setLeftCursorBackground(Drawable drawable) {
        if (drawable == null) {
            throw new IllegalArgumentException("Do you want to make left cursor invisible?");
        }
        mLeftCursorBG = drawable;
        requestLayout();
        invalidate();
    }

    public void setLeftCursorBackground(int resID) {
        if (resID < 0) {
            throw new IllegalArgumentException("Do you want to make left cursor invisible?");
        }
        mLeftCursorBG = getResources().getDrawable(resID);
        requestLayout();
        invalidate();
    }

    public void setRightCursorBackground(Drawable drawable) {
        if (drawable == null) {
            throw new IllegalArgumentException("Do you want to make right cursor invisible?");
        }
        mRightCursorBG = drawable;
        requestLayout();
        invalidate();
    }

    public void setRightCursorBackground(int resID) {
        if (resID < 0) {
            throw new IllegalArgumentException("Do you want to make right cursor invisible?");
        }
        mRightCursorBG = getResources().getDrawable(resID);
        requestLayout();
        invalidate();
    }

    public void setTextMarkColorNormal(int color) {
        if (color == Color.TRANSPARENT) {
            throw new IllegalArgumentException("Do you want to make text mark invisible?");
        }
        mTextColorNormal = color;
        invalidate();
    }

    public void setSeekbarColorNormal(int color) {
        if (color == Color.TRANSPARENT) {
            throw new IllegalArgumentException("Do you want to make seekbar invisible?");
        }
        mRangeBarColorNormal = color;
        invalidate();
    }

    public void setSeekbarColorSelected(int color) {
        if (color <= Color.TRANSPARENT) {
            throw new IllegalArgumentException("Do you want to make seekbar invisible?");
        }
        mRangeBarColorSelected = color;
        invalidate();
    }

    /**
     * In pixels. Users should call this method before view is added to parent.
     */
    public void setSeekbarHeight(int height) {
        if (height <= 0) {
            throw new IllegalArgumentException("Height of seekbar can not less than 0!");
        }
        mRangeBarHeight = height;
    }

    /**
     * To set space between text mark and seekbar.
     */
    public void setSpaceBetween(int space) {
        if (space < 0) {
            throw new IllegalArgumentException("Space between text mark and seekbar can not less than 0!");
        }
        mMarginBetween = space;
        requestLayout();
        invalidate();
    }

    /**
     * This method should be called after {@link #setTextMarkSize(int)}, because
     * view will measure size of text mark by paint.
     */
    public void setTextMarks(int... marks) {
        if (marks == null || marks.length == 0) {
            throw new IllegalArgumentException("Text array is null, how can i do...");
        }
        mTextArray = marks;
        mLeftCursorIndex = 0;
        mRightCursorIndex = mTextArray.length - 1;
        mRightCursorNextIndex = (int) mRightCursorIndex;
        mTextWidthArray = new float[marks.length];
        initTextWidthArray();
        requestLayout();
        invalidate();
    }

    /**
     * Users should call this method before view is added to parent.
     * 
     * @param size
     *            in pixels
     */
    public void setTextMarkSize(int size) {
        if (size < 0) {
            return;
        }
        mTextSize = size;
        mPaint.setTextSize(size);
    }

    public int getLeftCursorIndex() {
        return (int) mLeftCursorIndex;
    }

    public int getRightCursorIndex() {
        return (int) mRightCursorIndex;
    }

    public void setOnCursorChangeListener(OnCursorChangeListener l) {
        mListener = l;
    }

    public interface OnCursorChangeListener {
        void onCursorChanged(int left, int right, boolean needRefresh);
    }

    public int getValueByIndex(float index) {
        if (index < 0) {
            return mTextArray[0];
        }
        float a = index / 5;
        int floor = (int) Math.floor(a);
        int ceil = (int) Math.ceil(a);
        if (ceil > -1 && ceil < mTextArray.length - 1) {
            int delta = (int) (index % 5 * (mTextArray[ceil] - mTextArray[floor]) / 5);
            return mTextArray[floor] + delta;
        } else if (ceil == mTextArray.length - 1) {
            if (isInfinite) {
                return -1;
            } else {
                int delta = (int) (index % 5 * (mTextArray[ceil] - mTextArray[floor]) / 5);
                return mTextArray[floor] + delta;
            }
        } else {
            if (isInfinite) {
                return -1;
            } else {
                return mTextArray[mTextArray.length - 1];
            }
        }
    }

    public float getIndexByValue(int value) {
        if (value == -1 || value > mTextArray[mTextArray.length - 2]) {
            return (mTextArray.length - 1) * 5;
        }
        int j = 1;
        float diff = 0f;
        for (int i = 1; i < mTextArray.length - 1; i++) {
            if (mTextArray[i] > value) {
                j = i;
                diff = value - mTextArray[j - 1];
                break;
            }
        }
        return (j - 1) * 5 + diff / (mTextArray[j] - mTextArray[j - 1]) * 5;
    }

    private float getDiffByX(float x) {
        int a = (int) ((x - mPaddingRect.left) / mPartLength);
        if (a + 1 > -1 && a + 1 < mTextArray.length - 1) {
            return mTextArray[a + 1] - mTextArray[a];
        } else {
            return 5.0f;
        }
    }
}