Advertisement
Guest User

Untitled

a guest
Apr 26th, 2016
446
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 33.23 KB | None | 0 0
  1. public class RangeSeekBar<T extends Number> extends ImageView {
  2. /**
  3. * Default color of a {@link RangeSeekBar}, #FF33B5E5. This is also known as "Ice Cream Sandwich" blue.
  4. */
  5. public static final int ACTIVE_COLOR = Color.argb(0xFF, 0x33, 0xB5, 0xE5);
  6. /**
  7. * An invalid pointer id.
  8. */
  9. public static final int INVALID_POINTER_ID = 255;
  10.  
  11. // Localized constants from MotionEvent for compatibility
  12. // with API < 8 "Froyo".
  13. public static final int ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8;
  14.  
  15. public static final Integer DEFAULT_MINIMUM = 0;
  16. public static final Integer DEFAULT_MAXIMUM = 100;
  17. public static final int HEIGHT_IN_DP = 30;
  18. public static final int TEXT_LATERAL_PADDING_IN_DP = 3;
  19.  
  20. private static final int INITIAL_PADDING_IN_DP = 8;
  21. private static final int DEFAULT_TEXT_SIZE_IN_DP = 14;
  22. private static final int DEFAULT_TEXT_DISTANCE_TO_BUTTON_IN_DP = 8;
  23. private static final int DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP = 8;
  24.  
  25. private static final int LINE_HEIGHT_IN_DP = 1;
  26. private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  27. private final Paint shadowPaint = new Paint();
  28.  
  29. private Bitmap thumbImage;
  30. private Bitmap thumbPressedImage;
  31. private Bitmap thumbDisabledImage;
  32.  
  33. private float mThumbHalfWidth;
  34. private float mThumbHalfHeight;
  35.  
  36. private float padding;
  37. private T absoluteMinValue, absoluteMaxValue;
  38. private NumberType numberType;
  39. private double absoluteMinValuePrim, absoluteMaxValuePrim;
  40. private double normalizedMinValue = 0d;
  41. private double normalizedMaxValue = 1d;
  42. private Thumb pressedThumb = null;
  43. private boolean notifyWhileDragging = false;
  44. private OnRangeSeekBarChangeListener<T> listener;
  45.  
  46. private float mDownMotionX;
  47.  
  48. private int mActivePointerId = INVALID_POINTER_ID;
  49.  
  50. private int mScaledTouchSlop;
  51.  
  52. private boolean mIsDragging;
  53.  
  54. private int mTextOffset;
  55. private int mTextSize;
  56. private int mDistanceToTop;
  57. private RectF mRect;
  58.  
  59. private boolean mSingleThumb;
  60. private boolean mAlwaysActive;
  61. private boolean mShowLabels;
  62. private boolean mShowTextAboveThumbs;
  63. private float mInternalPad;
  64. private int mActiveColor;
  65. private int mDefaultColor;
  66. private int mTextAboveThumbsColor;
  67.  
  68. private boolean mThumbShadow;
  69. private int mThumbShadowXOffset;
  70. private int mThumbShadowYOffset;
  71. private int mThumbShadowBlur;
  72. private Path mThumbShadowPath;
  73. private Path mTranslatedThumbShadowPath = new Path();
  74. private Matrix mThumbShadowMatrix = new Matrix();
  75.  
  76. private boolean mActivateOnDefaultValues;
  77.  
  78.  
  79. public RangeSeekBar(Context context) {
  80. super(context);
  81. init(context, null);
  82. }
  83.  
  84. public RangeSeekBar(Context context, AttributeSet attrs) {
  85. super(context, attrs);
  86. init(context, attrs);
  87. }
  88.  
  89. public RangeSeekBar(Context context, AttributeSet attrs, int defStyle) {
  90. super(context, attrs, defStyle);
  91. init(context, attrs);
  92. }
  93.  
  94. @SuppressWarnings("unchecked")
  95. private T extractNumericValueFromAttributes(TypedArray a, int attribute, int defaultValue) {
  96. TypedValue tv = a.peekValue(attribute);
  97. if (tv == null) {
  98. return (T) Integer.valueOf(defaultValue);
  99. }
  100.  
  101. int type = tv.type;
  102. if (type == TypedValue.TYPE_FLOAT) {
  103. return (T) Float.valueOf(a.getFloat(attribute, defaultValue));
  104. } else {
  105. return (T) Integer.valueOf(a.getInteger(attribute, defaultValue));
  106. }
  107. }
  108.  
  109. private void init(Context context, AttributeSet attrs) {
  110. float barHeight;
  111. int thumbNormal = R.drawable.seek_pressed;
  112. int thumbPressed = R.drawable.seek_pressed;
  113. int thumbDisabled = R.drawable.seek_pressed;
  114. int thumbShadowColor;
  115. int defaultShadowColor = Color.argb(75, 0, 0, 0);
  116. int defaultShadowYOffset = PixelUtil.dpToPx(context, 2);
  117. int defaultShadowXOffset = PixelUtil.dpToPx(context, 0);
  118. int defaultShadowBlur = PixelUtil.dpToPx(context, 2);
  119.  
  120. if (attrs == null) {
  121. setRangeToDefaultValues();
  122. mInternalPad = PixelUtil.dpToPx(context, INITIAL_PADDING_IN_DP);
  123. barHeight = PixelUtil.dpToPx(context, LINE_HEIGHT_IN_DP);
  124. mActiveColor = getResources().getColor(R.color.mauvre);
  125. mDefaultColor = Color.GRAY;
  126. mAlwaysActive = false;
  127. mShowTextAboveThumbs = true;
  128. mTextAboveThumbsColor = Color.WHITE;
  129. thumbShadowColor = defaultShadowColor;
  130. mThumbShadowXOffset = defaultShadowXOffset;
  131. mThumbShadowYOffset = defaultShadowYOffset;
  132. mThumbShadowBlur = defaultShadowBlur;
  133. mActivateOnDefaultValues = false;
  134. } else {
  135. TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RangeSeekBar, 0, 0);
  136. try {
  137. setRangeValues(
  138. extractNumericValueFromAttributes(a, R.styleable.RangeSeekBar_absoluteMinValue, DEFAULT_MINIMUM),
  139. extractNumericValueFromAttributes(a, R.styleable.RangeSeekBar_absoluteMaxValue, DEFAULT_MAXIMUM)
  140. );
  141. mShowTextAboveThumbs = a.getBoolean(R.styleable.RangeSeekBar_valuesAboveThumbs, true);
  142. mTextAboveThumbsColor = a.getColor(R.styleable.RangeSeekBar_textAboveThumbsColor, Color.WHITE);
  143. mSingleThumb = a.getBoolean(R.styleable.RangeSeekBar_singleThumb, false);
  144. mShowLabels = a.getBoolean(R.styleable.RangeSeekBar_showLabels, true);
  145. mInternalPad = a.getDimensionPixelSize(R.styleable.RangeSeekBar_internalPadding, INITIAL_PADDING_IN_DP);
  146. barHeight = a.getDimensionPixelSize(R.styleable.RangeSeekBar_barHeight, LINE_HEIGHT_IN_DP);
  147. mActiveColor = a.getColor(R.styleable.RangeSeekBar_activeColor, getResources().getColor(R.color.mauvre));
  148. mDefaultColor = a.getColor(R.styleable.RangeSeekBar_defaultColor, Color.GRAY);
  149. mAlwaysActive = a.getBoolean(R.styleable.RangeSeekBar_alwaysActive, false);
  150.  
  151. Drawable normalDrawable = a.getDrawable(R.styleable.RangeSeekBar_thumbNormal);
  152. if (normalDrawable != null) {
  153. thumbImage = BitmapUtil.drawableToBitmap(normalDrawable);
  154. }
  155. Drawable disabledDrawable = a.getDrawable(R.styleable.RangeSeekBar_thumbDisabled);
  156. if (disabledDrawable != null) {
  157. thumbDisabledImage = BitmapUtil.drawableToBitmap(disabledDrawable);
  158. }
  159. Drawable pressedDrawable = a.getDrawable(R.styleable.RangeSeekBar_thumbPressed);
  160. if (pressedDrawable != null) {
  161. thumbPressedImage = BitmapUtil.drawableToBitmap(pressedDrawable);
  162. }
  163. mThumbShadow = a.getBoolean(R.styleable.RangeSeekBar_thumbShadow, false);
  164. thumbShadowColor = a.getColor(R.styleable.RangeSeekBar_thumbShadowColor, defaultShadowColor);
  165. mThumbShadowXOffset = a.getDimensionPixelSize(R.styleable.RangeSeekBar_thumbShadowXOffset, defaultShadowXOffset);
  166. mThumbShadowYOffset = a.getDimensionPixelSize(R.styleable.RangeSeekBar_thumbShadowYOffset, defaultShadowYOffset);
  167. mThumbShadowBlur = a.getDimensionPixelSize(R.styleable.RangeSeekBar_thumbShadowBlur, defaultShadowBlur);
  168.  
  169. mActivateOnDefaultValues = a.getBoolean(R.styleable.RangeSeekBar_activateOnDefaultValues, false);
  170. } finally {
  171. a.recycle();
  172. }
  173. }
  174.  
  175. if (thumbImage == null) {
  176. thumbImage = BitmapFactory.decodeResource(getResources(), thumbNormal);
  177. }
  178. if (thumbPressedImage == null) {
  179. thumbPressedImage = BitmapFactory.decodeResource(getResources(), thumbPressed);
  180. }
  181. if (thumbDisabledImage == null) {
  182. thumbDisabledImage = BitmapFactory.decodeResource(getResources(), thumbDisabled);
  183. }
  184.  
  185. mThumbHalfWidth = 0.5f * thumbImage.getWidth();
  186. mThumbHalfHeight = 0.5f * thumbImage.getHeight();
  187.  
  188. setValuePrimAndNumberType();
  189.  
  190. mTextSize = PixelUtil.dpToPx(context, DEFAULT_TEXT_SIZE_IN_DP);
  191. mDistanceToTop = PixelUtil.dpToPx(context, DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP);
  192. mTextOffset = !mShowTextAboveThumbs ? 0 : this.mTextSize + PixelUtil.dpToPx(context,
  193. DEFAULT_TEXT_DISTANCE_TO_BUTTON_IN_DP) + this.mDistanceToTop;
  194.  
  195. mRect = new RectF(padding,
  196. mTextOffset + mThumbHalfHeight - barHeight / 2,
  197. getWidth() - padding,
  198. mTextOffset + mThumbHalfHeight + barHeight / 2);
  199.  
  200. // make RangeSeekBar focusable. This solves focus handling issues in case EditText widgets are being used along with the RangeSeekBar within ScrollViews.
  201. setFocusable(true);
  202. setFocusableInTouchMode(true);
  203. mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
  204.  
  205. if (mThumbShadow) {
  206. // We need to remove hardware acceleration in order to blur the shadow
  207. setLayerType(LAYER_TYPE_SOFTWARE, null);
  208. shadowPaint.setColor(thumbShadowColor);
  209. shadowPaint.setMaskFilter(new BlurMaskFilter(mThumbShadowBlur, BlurMaskFilter.Blur.NORMAL));
  210. mThumbShadowPath = new Path();
  211. mThumbShadowPath.addCircle(0,
  212. 0,
  213. mThumbHalfHeight,
  214. Path.Direction.CW);
  215. }
  216. }
  217.  
  218. public void setRangeValues(T minValue, T maxValue) {
  219. this.absoluteMinValue = minValue;
  220. this.absoluteMaxValue = maxValue;
  221. setValuePrimAndNumberType();
  222. }
  223.  
  224. public void setTextAboveThumbsColor(int textAboveThumbsColor) {
  225. this.mTextAboveThumbsColor = textAboveThumbsColor;
  226. invalidate();
  227. }
  228.  
  229. public void setTextAboveThumbsColorResource(@ColorRes int resId) {
  230. setTextAboveThumbsColor(getResources().getColor(resId));
  231. }
  232.  
  233. @SuppressWarnings("unchecked")
  234. // only used to set default values when initialised from XML without any values specified
  235. private void setRangeToDefaultValues() {
  236. this.absoluteMinValue = (T) DEFAULT_MINIMUM;
  237. this.absoluteMaxValue = (T) DEFAULT_MAXIMUM;
  238. setValuePrimAndNumberType();
  239. }
  240.  
  241. private void setValuePrimAndNumberType() {
  242. absoluteMinValuePrim = absoluteMinValue.doubleValue();
  243. absoluteMaxValuePrim = absoluteMaxValue.doubleValue();
  244. numberType = NumberType.fromNumber(absoluteMinValue);
  245. }
  246.  
  247. @SuppressWarnings("unused")
  248. public void resetSelectedValues() {
  249. setSelectedMinValue(absoluteMinValue);
  250. setSelectedMaxValue(absoluteMaxValue);
  251. }
  252.  
  253. @SuppressWarnings("unused")
  254. public boolean isNotifyWhileDragging() {
  255. return notifyWhileDragging;
  256. }
  257.  
  258. /**
  259. * Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
  260. */
  261. @SuppressWarnings("unused")
  262. public void setNotifyWhileDragging(boolean flag) {
  263. this.notifyWhileDragging = flag;
  264. }
  265.  
  266. /**
  267. * Returns the absolute minimum value of the range that has been set at construction time.
  268. *
  269. * @return The absolute minimum value of the range.
  270. */
  271. public T getAbsoluteMinValue() {
  272. return absoluteMinValue;
  273. }
  274.  
  275. /**
  276. * Returns the absolute maximum value of the range that has been set at construction time.
  277. *
  278. * @return The absolute maximum value of the range.
  279. */
  280. public T getAbsoluteMaxValue() {
  281. return absoluteMaxValue;
  282. }
  283.  
  284. /**
  285. * Returns the currently selected min value.
  286. *
  287. * @return The currently selected min value.
  288. */
  289. public T getSelectedMinValue() {
  290. return normalizedToValue(normalizedMinValue);
  291. }
  292.  
  293. /**
  294. * Sets the currently selected minimum value. The widget will be invalidated and redrawn.
  295. *
  296. * @param value The Number value to set the minimum value to. Will be clamped to given absolute minimum/maximum range.
  297. */
  298. public void setSelectedMinValue(T value) {
  299. // in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
  300. if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
  301. setNormalizedMinValue(0d);
  302. } else {
  303. setNormalizedMinValue(valueToNormalized(value));
  304. }
  305. }
  306.  
  307. /**
  308. * Returns the currently selected max value.
  309. *
  310. * @return The currently selected max value.
  311. */
  312. public T getSelectedMaxValue() {
  313. return normalizedToValue(normalizedMaxValue);
  314. }
  315.  
  316. /**
  317. * Sets the currently selected maximum value. The widget will be invalidated and redrawn.
  318. *
  319. * @param value The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
  320. */
  321. public void setSelectedMaxValue(T value) {
  322. // in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
  323. if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
  324. setNormalizedMaxValue(1d);
  325. } else {
  326. setNormalizedMaxValue(valueToNormalized(value));
  327. }
  328. }
  329.  
  330. /**
  331. * Registers given listener callback to notify about changed selected values.
  332. *
  333. * @param listener The listener to notify about changed selected values.
  334. */
  335. @SuppressWarnings("unused")
  336. public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener<T> listener) {
  337. this.listener = listener;
  338. }
  339.  
  340. /**
  341. * Set the path that defines the shadow of the thumb. This path should be defined assuming
  342. * that the center of the shadow is at the top left corner (0,0) of the canvas. The
  343. * {@link #drawThumbShadow(float, Canvas)} method will place the shadow appropriately.
  344. *
  345. * @param thumbShadowPath The path defining the thumb shadow
  346. */
  347. @SuppressWarnings("unused")
  348. public void setThumbShadowPath(Path thumbShadowPath) {
  349. this.mThumbShadowPath = thumbShadowPath;
  350. }
  351.  
  352. /**
  353. * Handles thumb selection and movement. Notifies listener callback on certain events.
  354. */
  355. @Override
  356. public boolean onTouchEvent(@NonNull MotionEvent event) {
  357.  
  358. if (!isEnabled()) {
  359. return false;
  360. }
  361.  
  362. int pointerIndex;
  363.  
  364. final int action = event.getAction();
  365. switch (action & MotionEvent.ACTION_MASK) {
  366.  
  367. case MotionEvent.ACTION_DOWN:
  368. // Remember where the motion event started
  369. mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
  370. pointerIndex = event.findPointerIndex(mActivePointerId);
  371. mDownMotionX = event.getX(pointerIndex);
  372.  
  373. pressedThumb = evalPressedThumb(mDownMotionX);
  374.  
  375. // Only handle thumb presses.
  376. if (pressedThumb == null) {
  377. return super.onTouchEvent(event);
  378. }
  379.  
  380. setPressed(true);
  381. invalidate();
  382. onStartTrackingTouch();
  383. trackTouchEvent(event);
  384. attemptClaimDrag();
  385.  
  386. break;
  387. case MotionEvent.ACTION_MOVE:
  388. if (pressedThumb != null) {
  389.  
  390. if (mIsDragging) {
  391. trackTouchEvent(event);
  392. } else {
  393. // Scroll to follow the motion event
  394. pointerIndex = event.findPointerIndex(mActivePointerId);
  395. final float x = event.getX(pointerIndex);
  396.  
  397. if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
  398. setPressed(true);
  399. invalidate();
  400. onStartTrackingTouch();
  401. trackTouchEvent(event);
  402. attemptClaimDrag();
  403. }
  404. }
  405.  
  406. if (notifyWhileDragging && listener != null) {
  407. listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
  408. }
  409. }
  410. break;
  411. case MotionEvent.ACTION_UP:
  412. if (mIsDragging) {
  413. trackTouchEvent(event);
  414. onStopTrackingTouch();
  415. setPressed(false);
  416. } else {
  417. // Touch up when we never crossed the touch slop threshold
  418. // should be interpreted as a tap-seek to that location.
  419. onStartTrackingTouch();
  420. trackTouchEvent(event);
  421. onStopTrackingTouch();
  422. }
  423.  
  424. pressedThumb = null;
  425. invalidate();
  426. if (listener != null) {
  427. listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
  428. }
  429. break;
  430. case MotionEvent.ACTION_POINTER_DOWN: {
  431. final int index = event.getPointerCount() - 1;
  432. // final int index = ev.getActionIndex();
  433. mDownMotionX = event.getX(index);
  434. mActivePointerId = event.getPointerId(index);
  435. invalidate();
  436. break;
  437. }
  438. case MotionEvent.ACTION_POINTER_UP:
  439. onSecondaryPointerUp(event);
  440. invalidate();
  441. break;
  442. case MotionEvent.ACTION_CANCEL:
  443. if (mIsDragging) {
  444. onStopTrackingTouch();
  445. setPressed(false);
  446. }
  447. invalidate(); // see above explanation
  448. break;
  449. }
  450. return true;
  451. }
  452.  
  453. private void onSecondaryPointerUp(MotionEvent ev) {
  454. final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
  455.  
  456. final int pointerId = ev.getPointerId(pointerIndex);
  457. if (pointerId == mActivePointerId) {
  458. // This was our active pointer going up. Choose
  459. // a new active pointer and adjust accordingly.
  460. // TODO: Make this decision more intelligent.
  461. final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
  462. mDownMotionX = ev.getX(newPointerIndex);
  463. mActivePointerId = ev.getPointerId(newPointerIndex);
  464. }
  465. }
  466.  
  467. private void trackTouchEvent(MotionEvent event) {
  468. final int pointerIndex = event.findPointerIndex(mActivePointerId);
  469. final float x = event.getX(pointerIndex);
  470.  
  471. if (Thumb.MIN.equals(pressedThumb) && !mSingleThumb) {
  472. setNormalizedMinValue(screenToNormalized(x));
  473. } else if (Thumb.MAX.equals(pressedThumb)) {
  474. setNormalizedMaxValue(screenToNormalized(x));
  475. }
  476. }
  477.  
  478. /**
  479. * Tries to claim the user's drag motion, and requests disallowing any ancestors from stealing events in the drag.
  480. */
  481. private void attemptClaimDrag() {
  482. if (getParent() != null) {
  483. getParent().requestDisallowInterceptTouchEvent(true);
  484. }
  485. }
  486.  
  487. /**
  488. * This is called when the user has started touching this widget.
  489. */
  490. void onStartTrackingTouch() {
  491. mIsDragging = true;
  492. }
  493.  
  494. /**
  495. * This is called when the user either releases his touch or the touch is canceled.
  496. */
  497. void onStopTrackingTouch() {
  498. mIsDragging = false;
  499. }
  500.  
  501. /**
  502. * Ensures correct size of the widget.
  503. */
  504. @Override
  505. protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  506. int width = 200;
  507. if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
  508. width = MeasureSpec.getSize(widthMeasureSpec);
  509. }
  510.  
  511. int height = thumbImage.getHeight()
  512. + (!mShowTextAboveThumbs ? 0 : PixelUtil.dpToPx(getContext(), HEIGHT_IN_DP))
  513. + (mThumbShadow ? mThumbShadowYOffset + mThumbShadowBlur : 0);
  514. if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
  515. height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
  516. }
  517. setMeasuredDimension(width, height);
  518. }
  519.  
  520. /**
  521. * Draws the widget on the given canvas.
  522. */
  523. @Override
  524. protected synchronized void onDraw(@NonNull Canvas canvas) {
  525. super.onDraw(canvas);
  526.  
  527. paint.setTextSize(mTextSize);
  528. paint.setStyle(Style.FILL);
  529. paint.setColor(mDefaultColor);
  530. paint.setAntiAlias(true);
  531. float minMaxLabelSize = 0;
  532.  
  533. if (mShowLabels) {
  534. // draw min and max labels
  535. String minLabel = getContext().getString(R.string.demo_min_label);
  536. String maxLabel = getContext().getString(R.string.demo_max_label);
  537. minMaxLabelSize = Math.max(paint.measureText(minLabel), paint.measureText(maxLabel));
  538. float minMaxHeight = mTextOffset + mThumbHalfHeight + mTextSize / 3;
  539. canvas.drawText(minLabel, 0, minMaxHeight, paint);
  540. canvas.drawText(maxLabel, getWidth() - minMaxLabelSize, minMaxHeight, paint);
  541. }
  542. padding = mInternalPad + minMaxLabelSize + mThumbHalfWidth;
  543.  
  544. // draw seek bar background line
  545. mRect.left = padding;
  546. mRect.right = getWidth() - padding;
  547. canvas.drawRect(mRect, paint);
  548.  
  549. boolean selectedValuesAreDefault = (getSelectedMinValue().equals(getAbsoluteMinValue()) &&
  550. getSelectedMaxValue().equals(getAbsoluteMaxValue()));
  551.  
  552. int colorToUseForButtonsAndHighlightedLine = !mAlwaysActive && !mActivateOnDefaultValues && selectedValuesAreDefault ?
  553. mDefaultColor : // default values
  554. mActiveColor; // non default, filter is active
  555.  
  556. // draw seek bar active range line
  557. mRect.left = normalizedToScreen(normalizedMinValue);
  558. mRect.right = normalizedToScreen(normalizedMaxValue);
  559.  
  560. paint.setColor(colorToUseForButtonsAndHighlightedLine);
  561. canvas.drawRect(mRect, paint);
  562.  
  563. // draw minimum thumb (& shadow if requested) if not a single thumb control
  564. if (!mSingleThumb) {
  565. if (mThumbShadow) {
  566. drawThumbShadow(normalizedToScreen(normalizedMinValue), canvas);
  567. }
  568. drawThumb(normalizedToScreen(normalizedMinValue), Thumb.MIN.equals(pressedThumb), canvas,
  569. selectedValuesAreDefault);
  570. }
  571.  
  572. // draw maximum thumb & shadow (if necessary)
  573. if (mThumbShadow) {
  574. drawThumbShadow(normalizedToScreen(normalizedMaxValue), canvas);
  575. }
  576. drawThumb(normalizedToScreen(normalizedMaxValue), Thumb.MAX.equals(pressedThumb), canvas,
  577. selectedValuesAreDefault);
  578.  
  579. // draw the text if sliders have moved from default edges
  580. if (mShowTextAboveThumbs && (mActivateOnDefaultValues || !selectedValuesAreDefault)) {
  581. paint.setTextSize(mTextSize);
  582. paint.setColor(mTextAboveThumbsColor);
  583. // give text a bit more space here so it doesn't get cut off
  584. int offset = PixelUtil.dpToPx(getContext(), TEXT_LATERAL_PADDING_IN_DP);
  585.  
  586. String minText = String.valueOf(getSelectedMinValue());
  587. String maxText = String.valueOf(getSelectedMaxValue());
  588.  
  589. float minTextWidth = paint.measureText(minText) + offset;
  590. float maxTextWidth = paint.measureText(maxText) + offset;
  591.  
  592. if (!mSingleThumb) {
  593. canvas.drawText(minText,
  594. normalizedToScreen(normalizedMinValue) - minTextWidth * 0.5f,
  595. mDistanceToTop + mTextSize,
  596. paint);
  597.  
  598. }
  599.  
  600. canvas.drawText(maxText,
  601. normalizedToScreen(normalizedMaxValue) - maxTextWidth * 0.5f,
  602. mDistanceToTop + mTextSize,
  603. paint);
  604. }
  605.  
  606. }
  607.  
  608. /**
  609. * Overridden to save instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {@link #setId(int)} method. Other members of this class than the normalized min and max values don't need to be saved.
  610. */
  611. @Override
  612. protected Parcelable onSaveInstanceState() {
  613. final Bundle bundle = new Bundle();
  614. bundle.putParcelable("SUPER", super.onSaveInstanceState());
  615. bundle.putDouble("MIN", normalizedMinValue);
  616. bundle.putDouble("MAX", normalizedMaxValue);
  617. return bundle;
  618. }
  619.  
  620. /**
  621. * Overridden to restore instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {@link #setId(int)} method.
  622. */
  623. @Override
  624. protected void onRestoreInstanceState(Parcelable parcel) {
  625. final Bundle bundle = (Bundle) parcel;
  626. super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
  627. normalizedMinValue = bundle.getDouble("MIN");
  628. normalizedMaxValue = bundle.getDouble("MAX");
  629. }
  630.  
  631. /**
  632. * Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
  633. *
  634. * @param screenCoord The x-coordinate in screen space where to draw the image.
  635. * @param pressed Is the thumb currently in "pressed" state?
  636. * @param canvas The canvas to draw upon.
  637. */
  638. private void drawThumb(float screenCoord, boolean pressed, Canvas canvas, boolean areSelectedValuesDefault) {
  639. Bitmap buttonToDraw;
  640. if (!mActivateOnDefaultValues && areSelectedValuesDefault) {
  641. buttonToDraw = thumbDisabledImage;
  642. } else {
  643. buttonToDraw = pressed ? thumbPressedImage : thumbImage;
  644. }
  645.  
  646. canvas.drawBitmap(buttonToDraw, screenCoord - mThumbHalfWidth,
  647. mTextOffset,
  648. paint);
  649. }
  650.  
  651. /**
  652. * Draws a drop shadow beneath the slider thumb.
  653. *
  654. * @param screenCoord the x-coordinate of the slider thumb
  655. * @param canvas the canvas on which to draw the shadow
  656. */
  657. private void drawThumbShadow(float screenCoord, Canvas canvas) {
  658. mThumbShadowMatrix.setTranslate(screenCoord + mThumbShadowXOffset, mTextOffset + mThumbHalfHeight + mThumbShadowYOffset);
  659. mTranslatedThumbShadowPath.set(mThumbShadowPath);
  660. mTranslatedThumbShadowPath.transform(mThumbShadowMatrix);
  661. canvas.drawPath(mTranslatedThumbShadowPath, shadowPaint);
  662. }
  663.  
  664. /**
  665. * Decides which (if any) thumb is touched by the given x-coordinate.
  666. *
  667. * @param touchX The x-coordinate of a touch event in screen space.
  668. * @return The pressed thumb or null if none has been touched.
  669. */
  670. private Thumb evalPressedThumb(float touchX) {
  671. Thumb result = null;
  672. boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue);
  673. boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue);
  674. if (minThumbPressed && maxThumbPressed) {
  675. // if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore.
  676. result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
  677. } else if (minThumbPressed) {
  678. result = Thumb.MIN;
  679. } else if (maxThumbPressed) {
  680. result = Thumb.MAX;
  681. }
  682. return result;
  683. }
  684.  
  685. /**
  686. * Decides if given x-coordinate in screen space needs to be interpreted as "within" the normalized thumb x-coordinate.
  687. *
  688. * @param touchX The x-coordinate in screen space to check.
  689. * @param normalizedThumbValue The normalized x-coordinate of the thumb to check.
  690. * @return true if x-coordinate is in thumb range, false otherwise.
  691. */
  692. private boolean isInThumbRange(float touchX, double normalizedThumbValue) {
  693. return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= mThumbHalfWidth;
  694. }
  695.  
  696. /**
  697. * Sets normalized min value to value so that 0 <= value <= normalized max value <= 1. The View will get invalidated when calling this method.
  698. *
  699. * @param value The new normalized min value to set.
  700. */
  701. private void setNormalizedMinValue(double value) {
  702. normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
  703. invalidate();
  704. }
  705.  
  706. /**
  707. * Sets normalized max value to value so that 0 <= normalized min value <= value <= 1. The View will get invalidated when calling this method.
  708. *
  709. * @param value The new normalized max value to set.
  710. */
  711. private void setNormalizedMaxValue(double value) {
  712. normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
  713. invalidate();
  714. }
  715.  
  716. /**
  717. * Converts a normalized value to a Number object in the value space between absolute minimum and maximum.
  718. */
  719. @SuppressWarnings("unchecked")
  720. private T normalizedToValue(double normalized) {
  721. double v = absoluteMinValuePrim + normalized * (absoluteMaxValuePrim - absoluteMinValuePrim);
  722. // TODO parameterize this rounding to allow variable decimal points
  723. return (T) numberType.toNumber(Math.round(v * 100) / 100d);
  724. }
  725.  
  726. /**
  727. * Converts the given Number value to a normalized double.
  728. *
  729. * @param value The Number value to normalize.
  730. * @return The normalized double.
  731. */
  732. private double valueToNormalized(T value) {
  733. if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {
  734. // prevent division by zero, simply return 0.
  735. return 0d;
  736. }
  737. return (value.doubleValue() - absoluteMinValuePrim) / (absoluteMaxValuePrim - absoluteMinValuePrim);
  738. }
  739.  
  740. /**
  741. * Converts a normalized value into screen space.
  742. *
  743. * @param normalizedCoord The normalized value to convert.
  744. * @return The converted value in screen space.
  745. */
  746. private float normalizedToScreen(double normalizedCoord) {
  747. return (float) (padding + normalizedCoord * (getWidth() - 2 * padding));
  748. }
  749.  
  750. /**
  751. * Converts screen space x-coordinates into normalized values.
  752. *
  753. * @param screenCoord The x-coordinate in screen space to convert.
  754. * @return The normalized value.
  755. */
  756. private double screenToNormalized(float screenCoord) {
  757. int width = getWidth();
  758. if (width <= 2 * padding) {
  759. // prevent division by zero, simply return 0.
  760. return 0d;
  761. } else {
  762. double result = (screenCoord - padding) / (width - 2 * padding);
  763. return Math.min(1d, Math.max(0d, result));
  764. }
  765. }
  766.  
  767. /**
  768. * Thumb constants (min and max).
  769. */
  770. private enum Thumb {
  771. MIN, MAX
  772. }
  773.  
  774. /**
  775. * Utility enumeration used to convert between Numbers and doubles.
  776. *
  777. * @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
  778. */
  779. private enum NumberType {
  780. LONG, DOUBLE, INTEGER, FLOAT, SHORT, BYTE, BIG_DECIMAL;
  781.  
  782. public static <E extends Number> NumberType fromNumber(E value) throws IllegalArgumentException {
  783. if (value instanceof Long) {
  784. return LONG;
  785. }
  786. if (value instanceof Double) {
  787. return DOUBLE;
  788. }
  789. if (value instanceof Integer) {
  790. return INTEGER;
  791. }
  792. if (value instanceof Float) {
  793. return FLOAT;
  794. }
  795. if (value instanceof Short) {
  796. return SHORT;
  797. }
  798. if (value instanceof Byte) {
  799. return BYTE;
  800. }
  801. if (value instanceof BigDecimal) {
  802. return BIG_DECIMAL;
  803. }
  804. throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
  805. }
  806.  
  807. public Number toNumber(double value) {
  808. switch (this) {
  809. case LONG:
  810. return (long) value;
  811. case DOUBLE:
  812. return value;
  813. case INTEGER:
  814. return (int) value;
  815. case FLOAT:
  816. return (float) value;
  817. case SHORT:
  818. return (short) value;
  819. case BYTE:
  820. return (byte) value;
  821. case BIG_DECIMAL:
  822. return BigDecimal.valueOf(value);
  823. }
  824. throw new InstantiationError("can't convert " + this + " to a Number object");
  825. }
  826. }
  827.  
  828. /**
  829. * Callback listener interface to notify about changed range values.
  830. *
  831. * @param <T> The Number type the RangeSeekBar has been declared with.
  832. * @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
  833. */
  834. public interface OnRangeSeekBarChangeListener<T extends Number> {
  835.  
  836. void onRangeSeekBarValuesChanged(RangeSeekBar<T> bar, T minValue, T maxValue);
  837. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement