blob: 078690966595e39e9d5c6ac8837322190840bc84 [file] [log] [blame]
Adam Powell12190b32010-11-28 19:07:53 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
Adam Powell12190b32010-11-28 19:07:53 -080019import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.Rect;
26import android.graphics.Typeface;
27import android.graphics.drawable.Drawable;
28import android.text.Layout;
29import android.text.StaticLayout;
30import android.text.TextPaint;
31import android.text.TextUtils;
32import android.util.AttributeSet;
33import android.view.Gravity;
34import android.view.MotionEvent;
35import android.view.VelocityTracker;
36import android.view.ViewConfiguration;
Svetoslav Ganov63bce032011-07-23 19:52:17 -070037import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080038import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell12190b32010-11-28 19:07:53 -080039
Adam Powellbe0a4532010-11-29 17:47:48 -080040import com.android.internal.R;
41
Adam Powell12190b32010-11-28 19:07:53 -080042/**
43 * A Switch is a two-state toggle switch widget that can select between two
44 * options. The user may drag the "thumb" back and forth to choose the selected option,
Chet Haase150176d2011-08-26 09:54:06 -070045 * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text}
46 * property controls the text displayed in the label for the switch, whereas the
47 * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text
48 * controls the text on the thumb. Similarly, the
49 * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related
50 * setTypeface() methods control the typeface and style of label text, whereas the
51 * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
52 * the related seSwitchTypeface() methods control that of the thumb.
Adam Powell12190b32010-11-28 19:07:53 -080053 *
Adam Powell12190b32010-11-28 19:07:53 -080054 */
55public class Switch extends CompoundButton {
56 private static final int TOUCH_MODE_IDLE = 0;
57 private static final int TOUCH_MODE_DOWN = 1;
58 private static final int TOUCH_MODE_DRAGGING = 2;
59
60 // Enum for the "typeface" XML parameter.
61 private static final int SANS = 1;
62 private static final int SERIF = 2;
63 private static final int MONOSPACE = 3;
64
65 private Drawable mThumbDrawable;
66 private Drawable mTrackDrawable;
67 private int mThumbTextPadding;
68 private int mSwitchMinWidth;
69 private int mSwitchPadding;
70 private CharSequence mTextOn;
71 private CharSequence mTextOff;
72
73 private int mTouchMode;
74 private int mTouchSlop;
75 private float mTouchX;
76 private float mTouchY;
77 private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
78 private int mMinFlingVelocity;
79
80 private float mThumbPosition;
81 private int mSwitchWidth;
82 private int mSwitchHeight;
83 private int mThumbWidth; // Does not include padding
84
85 private int mSwitchLeft;
86 private int mSwitchTop;
87 private int mSwitchRight;
88 private int mSwitchBottom;
89
90 private TextPaint mTextPaint;
91 private ColorStateList mTextColors;
92 private Layout mOnLayout;
93 private Layout mOffLayout;
94
Adam Powellbe0a4532010-11-29 17:47:48 -080095 @SuppressWarnings("hiding")
Adam Powell12190b32010-11-28 19:07:53 -080096 private final Rect mTempRect = new Rect();
97
98 private static final int[] CHECKED_STATE_SET = {
99 R.attr.state_checked
100 };
101
102 /**
103 * Construct a new Switch with default styling.
104 *
105 * @param context The Context that will determine this widget's theming.
106 */
107 public Switch(Context context) {
108 this(context, null);
109 }
110
111 /**
112 * Construct a new Switch with default styling, overriding specific style
113 * attributes as requested.
114 *
115 * @param context The Context that will determine this widget's theming.
116 * @param attrs Specification of attributes that should deviate from default styling.
117 */
118 public Switch(Context context, AttributeSet attrs) {
119 this(context, attrs, com.android.internal.R.attr.switchStyle);
120 }
121
122 /**
123 * Construct a new Switch with a default style determined by the given theme attribute,
124 * overriding specific style attributes as requested.
125 *
126 * @param context The Context that will determine this widget's theming.
127 * @param attrs Specification of attributes that should deviate from the default styling.
128 * @param defStyle An attribute ID within the active theme containing a reference to the
129 * default style for this widget. e.g. android.R.attr.switchStyle.
130 */
131 public Switch(Context context, AttributeSet attrs, int defStyle) {
132 super(context, attrs, defStyle);
133
134 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
135 Resources res = getResources();
136 mTextPaint.density = res.getDisplayMetrics().density;
137 mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
138
139 TypedArray a = context.obtainStyledAttributes(attrs,
140 com.android.internal.R.styleable.Switch, defStyle, 0);
141
Chet Haase150176d2011-08-26 09:54:06 -0700142 mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
143 mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
Adam Powell12190b32010-11-28 19:07:53 -0800144 mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
145 mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
146 mThumbTextPadding = a.getDimensionPixelSize(
147 com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
148 mSwitchMinWidth = a.getDimensionPixelSize(
149 com.android.internal.R.styleable.Switch_switchMinWidth, 0);
150 mSwitchPadding = a.getDimensionPixelSize(
151 com.android.internal.R.styleable.Switch_switchPadding, 0);
152
153 int appearance = a.getResourceId(
154 com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
155 if (appearance != 0) {
Chet Haase150176d2011-08-26 09:54:06 -0700156 setSwitchTextAppearance(context, appearance);
Adam Powell12190b32010-11-28 19:07:53 -0800157 }
158 a.recycle();
159
160 ViewConfiguration config = ViewConfiguration.get(context);
161 mTouchSlop = config.getScaledTouchSlop();
162 mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
163
164 // Refresh display with current params
Gilles Debunnee724ee42011-08-31 11:20:27 -0700165 refreshDrawableState();
Adam Powell12190b32010-11-28 19:07:53 -0800166 setChecked(isChecked());
167 }
168
169 /**
170 * Sets the switch text color, size, style, hint color, and highlight color
171 * from the specified TextAppearance resource.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800172 *
173 * @attr ref android.R.styleable#Switch_switchTextAppearance
Adam Powell12190b32010-11-28 19:07:53 -0800174 */
Chet Haase150176d2011-08-26 09:54:06 -0700175 public void setSwitchTextAppearance(Context context, int resid) {
Adam Powell12190b32010-11-28 19:07:53 -0800176 TypedArray appearance =
Chet Haase150176d2011-08-26 09:54:06 -0700177 context.obtainStyledAttributes(resid,
Adam Powell12190b32010-11-28 19:07:53 -0800178 com.android.internal.R.styleable.TextAppearance);
179
180 ColorStateList colors;
181 int ts;
182
183 colors = appearance.getColorStateList(com.android.internal.R.styleable.
184 TextAppearance_textColor);
185 if (colors != null) {
186 mTextColors = colors;
Chet Haase150176d2011-08-26 09:54:06 -0700187 } else {
188 // If no color set in TextAppearance, default to the view's textColor
189 mTextColors = getTextColors();
Adam Powell12190b32010-11-28 19:07:53 -0800190 }
191
192 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
193 TextAppearance_textSize, 0);
194 if (ts != 0) {
195 if (ts != mTextPaint.getTextSize()) {
196 mTextPaint.setTextSize(ts);
197 requestLayout();
198 }
199 }
200
201 int typefaceIndex, styleIndex;
202
203 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
204 TextAppearance_typeface, -1);
205 styleIndex = appearance.getInt(com.android.internal.R.styleable.
206 TextAppearance_textStyle, -1);
207
208 setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
209
Adam Powell12190b32010-11-28 19:07:53 -0800210 appearance.recycle();
211 }
212
213 private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
214 Typeface tf = null;
215 switch (typefaceIndex) {
216 case SANS:
217 tf = Typeface.SANS_SERIF;
218 break;
219
220 case SERIF:
221 tf = Typeface.SERIF;
222 break;
223
224 case MONOSPACE:
225 tf = Typeface.MONOSPACE;
226 break;
227 }
228
229 setSwitchTypeface(tf, styleIndex);
230 }
231
232 /**
233 * Sets the typeface and style in which the text should be displayed on the
234 * switch, and turns on the fake bold and italic bits in the Paint if the
235 * Typeface that you provided does not have all the bits in the
236 * style that you specified.
237 */
238 public void setSwitchTypeface(Typeface tf, int style) {
239 if (style > 0) {
240 if (tf == null) {
241 tf = Typeface.defaultFromStyle(style);
242 } else {
243 tf = Typeface.create(tf, style);
244 }
245
246 setSwitchTypeface(tf);
247 // now compute what (if any) algorithmic styling is needed
248 int typefaceStyle = tf != null ? tf.getStyle() : 0;
249 int need = style & ~typefaceStyle;
250 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
251 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
252 } else {
253 mTextPaint.setFakeBoldText(false);
254 mTextPaint.setTextSkewX(0);
255 setSwitchTypeface(tf);
256 }
257 }
258
259 /**
Chet Haase150176d2011-08-26 09:54:06 -0700260 * Sets the typeface in which the text should be displayed on the switch.
Adam Powell12190b32010-11-28 19:07:53 -0800261 * Note that not all Typeface families actually have bold and italic
262 * variants, so you may need to use
263 * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
264 * that you actually want.
265 *
266 * @attr ref android.R.styleable#TextView_typeface
267 * @attr ref android.R.styleable#TextView_textStyle
268 */
269 public void setSwitchTypeface(Typeface tf) {
270 if (mTextPaint.getTypeface() != tf) {
271 mTextPaint.setTypeface(tf);
272
273 requestLayout();
274 invalidate();
275 }
276 }
277
278 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800279 * Set the amount of horizontal padding between the switch and the associated text.
280 *
281 * @param pixels Amount of padding in pixels
282 *
283 * @attr ref android.R.styleable#Switch_switchPadding
284 */
285 public void setSwitchPadding(int pixels) {
286 mSwitchPadding = pixels;
287 requestLayout();
288 }
289
290 /**
291 * Get the amount of horizontal padding between the switch and the associated text.
292 *
293 * @return Amount of padding in pixels
294 *
295 * @attr ref android.R.styleable#Switch_switchPadding
296 */
297 public int getSwitchPadding() {
298 return mSwitchPadding;
299 }
300
301 /**
302 * Set the minimum width of the switch in pixels. The switch's width will be the maximum
303 * of this value and its measured width as determined by the switch drawables and text used.
304 *
305 * @param pixels Minimum width of the switch in pixels
306 *
307 * @attr ref android.R.styleable#Switch_switchMinWidth
308 */
309 public void setSwitchMinWidth(int pixels) {
310 mSwitchMinWidth = pixels;
311 requestLayout();
312 }
313
314 /**
315 * Get the minimum width of the switch in pixels. The switch's width will be the maximum
316 * of this value and its measured width as determined by the switch drawables and text used.
317 *
318 * @return Minimum width of the switch in pixels
319 *
320 * @attr ref android.R.styleable#Switch_switchMinWidth
321 */
322 public int getSwitchMinWidth() {
323 return mSwitchMinWidth;
324 }
325
326 /**
327 * Set the horizontal padding around the text drawn on the switch itself.
328 *
329 * @param pixels Horizontal padding for switch thumb text in pixels
330 *
331 * @attr ref android.R.styleable#Switch_thumbTextPadding
332 */
333 public void setThumbTextPadding(int pixels) {
334 mThumbTextPadding = pixels;
335 requestLayout();
336 }
337
338 /**
339 * Get the horizontal padding around the text drawn on the switch itself.
340 *
341 * @return Horizontal padding for switch thumb text in pixels
342 *
343 * @attr ref android.R.styleable#Switch_thumbTextPadding
344 */
345 public int getThumbTextPadding() {
346 return mThumbTextPadding;
347 }
348
349 /**
350 * Set the drawable used for the track that the switch slides within.
351 *
352 * @param track Track drawable
353 *
354 * @attr ref android.R.styleable#Switch_track
355 */
356 public void setTrackDrawable(Drawable track) {
357 mTrackDrawable = track;
358 requestLayout();
359 }
360
361 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800362 * Set the drawable used for the track that the switch slides within.
363 *
Adam Powelldca510e2012-03-08 20:06:39 -0800364 * @param resId Resource ID of a track drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800365 *
366 * @attr ref android.R.styleable#Switch_track
367 */
368 public void setTrackResource(int resId) {
369 setTrackDrawable(getContext().getResources().getDrawable(resId));
370 }
371
372 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800373 * Get the drawable used for the track that the switch slides within.
374 *
375 * @return Track drawable
376 *
377 * @attr ref android.R.styleable#Switch_track
378 */
379 public Drawable getTrackDrawable() {
380 return mTrackDrawable;
381 }
382
383 /**
384 * Set the drawable used for the switch "thumb" - the piece that the user
385 * can physically touch and drag along the track.
386 *
387 * @param thumb Thumb drawable
388 *
389 * @attr ref android.R.styleable#Switch_thumb
390 */
391 public void setThumbDrawable(Drawable thumb) {
392 mThumbDrawable = thumb;
393 requestLayout();
394 }
395
396 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800397 * Set the drawable used for the switch "thumb" - the piece that the user
398 * can physically touch and drag along the track.
399 *
Adam Powelldca510e2012-03-08 20:06:39 -0800400 * @param resId Resource ID of a thumb drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800401 *
402 * @attr ref android.R.styleable#Switch_thumb
403 */
404 public void setThumbResource(int resId) {
405 setThumbDrawable(getContext().getResources().getDrawable(resId));
406 }
407
408 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800409 * Get the drawable used for the switch "thumb" - the piece that the user
410 * can physically touch and drag along the track.
411 *
412 * @return Thumb drawable
413 *
414 * @attr ref android.R.styleable#Switch_thumb
415 */
416 public Drawable getThumbDrawable() {
417 return mThumbDrawable;
418 }
419
420 /**
Chet Haase150176d2011-08-26 09:54:06 -0700421 * Returns the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800422 *
423 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800424 */
425 public CharSequence getTextOn() {
426 return mTextOn;
427 }
428
429 /**
Chet Haase150176d2011-08-26 09:54:06 -0700430 * Sets the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800431 *
432 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800433 */
434 public void setTextOn(CharSequence textOn) {
435 mTextOn = textOn;
436 requestLayout();
437 }
438
439 /**
Chet Haase150176d2011-08-26 09:54:06 -0700440 * Returns the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800441 *
442 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800443 */
444 public CharSequence getTextOff() {
445 return mTextOff;
446 }
447
448 /**
Chet Haase150176d2011-08-26 09:54:06 -0700449 * Sets the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800450 *
451 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800452 */
453 public void setTextOff(CharSequence textOff) {
454 mTextOff = textOff;
455 requestLayout();
456 }
457
458 @Override
459 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
460 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
461 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
462 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
463 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
464
465
466 if (mOnLayout == null) {
467 mOnLayout = makeLayout(mTextOn);
468 }
469 if (mOffLayout == null) {
470 mOffLayout = makeLayout(mTextOff);
471 }
472
473 mTrackDrawable.getPadding(mTempRect);
474 final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
475 final int switchWidth = Math.max(mSwitchMinWidth,
476 maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
477 final int switchHeight = mTrackDrawable.getIntrinsicHeight();
478
479 mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
480
481 switch (widthMode) {
482 case MeasureSpec.AT_MOST:
483 widthSize = Math.min(widthSize, switchWidth);
484 break;
485
486 case MeasureSpec.UNSPECIFIED:
487 widthSize = switchWidth;
488 break;
489
490 case MeasureSpec.EXACTLY:
491 // Just use what we were given
492 break;
493 }
494
495 switch (heightMode) {
496 case MeasureSpec.AT_MOST:
497 heightSize = Math.min(heightSize, switchHeight);
498 break;
499
500 case MeasureSpec.UNSPECIFIED:
501 heightSize = switchHeight;
502 break;
503
504 case MeasureSpec.EXACTLY:
505 // Just use what we were given
506 break;
507 }
508
509 mSwitchWidth = switchWidth;
510 mSwitchHeight = switchHeight;
511
512 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Adam Powell12190b32010-11-28 19:07:53 -0800513 final int measuredHeight = getMeasuredHeight();
514 if (measuredHeight < switchHeight) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800515 setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
Adam Powell12190b32010-11-28 19:07:53 -0800516 }
517 }
518
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700519 @Override
520 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
521 super.onPopulateAccessibilityEvent(event);
Svetoslav Ganov55249c82012-03-09 10:09:03 -0800522 CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
523 if (!TextUtils.isEmpty(text)) {
Svetoslav Ganov76502592011-07-29 10:44:59 -0700524 event.getText().add(text);
525 }
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700526 }
527
Adam Powell12190b32010-11-28 19:07:53 -0800528 private Layout makeLayout(CharSequence text) {
529 return new StaticLayout(text, mTextPaint,
530 (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)),
531 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
532 }
533
534 /**
535 * @return true if (x, y) is within the target area of the switch thumb
536 */
537 private boolean hitThumb(float x, float y) {
538 mThumbDrawable.getPadding(mTempRect);
539 final int thumbTop = mSwitchTop - mTouchSlop;
540 final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
541 final int thumbRight = thumbLeft + mThumbWidth +
542 mTempRect.left + mTempRect.right + mTouchSlop;
543 final int thumbBottom = mSwitchBottom + mTouchSlop;
544 return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
545 }
546
547 @Override
548 public boolean onTouchEvent(MotionEvent ev) {
549 mVelocityTracker.addMovement(ev);
550 final int action = ev.getActionMasked();
551 switch (action) {
552 case MotionEvent.ACTION_DOWN: {
553 final float x = ev.getX();
554 final float y = ev.getY();
Gilles Debunnec2ab0d62011-06-13 12:52:48 -0700555 if (isEnabled() && hitThumb(x, y)) {
Adam Powell12190b32010-11-28 19:07:53 -0800556 mTouchMode = TOUCH_MODE_DOWN;
557 mTouchX = x;
558 mTouchY = y;
559 }
560 break;
561 }
562
563 case MotionEvent.ACTION_MOVE: {
564 switch (mTouchMode) {
565 case TOUCH_MODE_IDLE:
566 // Didn't target the thumb, treat normally.
567 break;
568
569 case TOUCH_MODE_DOWN: {
570 final float x = ev.getX();
571 final float y = ev.getY();
572 if (Math.abs(x - mTouchX) > mTouchSlop ||
573 Math.abs(y - mTouchY) > mTouchSlop) {
574 mTouchMode = TOUCH_MODE_DRAGGING;
575 getParent().requestDisallowInterceptTouchEvent(true);
576 mTouchX = x;
577 mTouchY = y;
578 return true;
579 }
580 break;
581 }
582
583 case TOUCH_MODE_DRAGGING: {
584 final float x = ev.getX();
585 final float dx = x - mTouchX;
586 float newPos = Math.max(0,
587 Math.min(mThumbPosition + dx, getThumbScrollRange()));
588 if (newPos != mThumbPosition) {
589 mThumbPosition = newPos;
590 mTouchX = x;
591 invalidate();
592 }
593 return true;
594 }
595 }
596 break;
597 }
598
599 case MotionEvent.ACTION_UP:
600 case MotionEvent.ACTION_CANCEL: {
601 if (mTouchMode == TOUCH_MODE_DRAGGING) {
602 stopDrag(ev);
603 return true;
604 }
605 mTouchMode = TOUCH_MODE_IDLE;
606 mVelocityTracker.clear();
607 break;
608 }
609 }
610
611 return super.onTouchEvent(ev);
612 }
613
614 private void cancelSuperTouch(MotionEvent ev) {
615 MotionEvent cancel = MotionEvent.obtain(ev);
616 cancel.setAction(MotionEvent.ACTION_CANCEL);
617 super.onTouchEvent(cancel);
618 cancel.recycle();
619 }
620
621 /**
622 * Called from onTouchEvent to end a drag operation.
623 *
624 * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
625 */
626 private void stopDrag(MotionEvent ev) {
627 mTouchMode = TOUCH_MODE_IDLE;
Gilles Debunnec2ab0d62011-06-13 12:52:48 -0700628 // Up and not canceled, also checks the switch has not been disabled during the drag
629 boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
Adam Powell12190b32010-11-28 19:07:53 -0800630
631 cancelSuperTouch(ev);
632
633 if (commitChange) {
634 boolean newState;
635 mVelocityTracker.computeCurrentVelocity(1000);
636 float xvel = mVelocityTracker.getXVelocity();
637 if (Math.abs(xvel) > mMinFlingVelocity) {
Adam Powell01d11ed2011-08-09 16:32:12 -0700638 newState = xvel > 0;
Adam Powell12190b32010-11-28 19:07:53 -0800639 } else {
640 newState = getTargetCheckedState();
641 }
642 animateThumbToCheckedState(newState);
643 } else {
644 animateThumbToCheckedState(isChecked());
645 }
646 }
647
648 private void animateThumbToCheckedState(boolean newCheckedState) {
Adam Powell12190b32010-11-28 19:07:53 -0800649 // TODO animate!
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800650 //float targetPos = newCheckedState ? 0 : getThumbScrollRange();
651 //mThumbPosition = targetPos;
Adam Powell12190b32010-11-28 19:07:53 -0800652 setChecked(newCheckedState);
653 }
654
655 private boolean getTargetCheckedState() {
Adam Powellec1d6032011-08-07 18:03:39 -0700656 return mThumbPosition >= getThumbScrollRange() / 2;
Adam Powell12190b32010-11-28 19:07:53 -0800657 }
658
659 @Override
660 public void setChecked(boolean checked) {
661 super.setChecked(checked);
Adam Powellec1d6032011-08-07 18:03:39 -0700662 mThumbPosition = checked ? getThumbScrollRange() : 0;
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800663 invalidate();
Adam Powell12190b32010-11-28 19:07:53 -0800664 }
665
666 @Override
667 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
668 super.onLayout(changed, left, top, right, bottom);
669
Adam Powellec1d6032011-08-07 18:03:39 -0700670 mThumbPosition = isChecked() ? getThumbScrollRange() : 0;
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800671
Adam Powell12190b32010-11-28 19:07:53 -0800672 int switchRight = getWidth() - getPaddingRight();
673 int switchLeft = switchRight - mSwitchWidth;
674 int switchTop = 0;
675 int switchBottom = 0;
676 switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
677 default:
678 case Gravity.TOP:
679 switchTop = getPaddingTop();
680 switchBottom = switchTop + mSwitchHeight;
681 break;
682
683 case Gravity.CENTER_VERTICAL:
684 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
685 mSwitchHeight / 2;
686 switchBottom = switchTop + mSwitchHeight;
687 break;
688
689 case Gravity.BOTTOM:
690 switchBottom = getHeight() - getPaddingBottom();
691 switchTop = switchBottom - mSwitchHeight;
692 break;
693 }
694
695 mSwitchLeft = switchLeft;
696 mSwitchTop = switchTop;
697 mSwitchBottom = switchBottom;
698 mSwitchRight = switchRight;
699 }
700
701 @Override
702 protected void onDraw(Canvas canvas) {
703 super.onDraw(canvas);
704
705 // Draw the switch
706 int switchLeft = mSwitchLeft;
707 int switchTop = mSwitchTop;
708 int switchRight = mSwitchRight;
709 int switchBottom = mSwitchBottom;
710
711 mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
712 mTrackDrawable.draw(canvas);
713
714 canvas.save();
715
716 mTrackDrawable.getPadding(mTempRect);
717 int switchInnerLeft = switchLeft + mTempRect.left;
718 int switchInnerTop = switchTop + mTempRect.top;
719 int switchInnerRight = switchRight - mTempRect.right;
720 int switchInnerBottom = switchBottom - mTempRect.bottom;
721 canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
722
723 mThumbDrawable.getPadding(mTempRect);
724 final int thumbPos = (int) (mThumbPosition + 0.5f);
725 int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
726 int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
727
728 mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
729 mThumbDrawable.draw(canvas);
730
Chet Haase150176d2011-08-26 09:54:06 -0700731 // mTextColors should not be null, but just in case
732 if (mTextColors != null) {
733 mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
734 mTextColors.getDefaultColor()));
735 }
Adam Powell12190b32010-11-28 19:07:53 -0800736 mTextPaint.drawableState = getDrawableState();
737
738 Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
739
740 canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
741 (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
742 switchText.draw(canvas);
743
744 canvas.restore();
745 }
746
747 @Override
748 public int getCompoundPaddingRight() {
749 int padding = super.getCompoundPaddingRight() + mSwitchWidth;
750 if (!TextUtils.isEmpty(getText())) {
751 padding += mSwitchPadding;
752 }
753 return padding;
754 }
755
756 private int getThumbScrollRange() {
757 if (mTrackDrawable == null) {
758 return 0;
759 }
760 mTrackDrawable.getPadding(mTempRect);
761 return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
762 }
763
764 @Override
765 protected int[] onCreateDrawableState(int extraSpace) {
766 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
767 if (isChecked()) {
768 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
769 }
770 return drawableState;
771 }
772
773 @Override
774 protected void drawableStateChanged() {
775 super.drawableStateChanged();
776
777 int[] myDrawableState = getDrawableState();
778
779 // Set the state of the Drawable
Gilles Debunnee724ee42011-08-31 11:20:27 -0700780 // Drawable may be null when checked state is set from XML, from super constructor
781 if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
782 if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
Adam Powell12190b32010-11-28 19:07:53 -0800783
784 invalidate();
785 }
786
787 @Override
788 protected boolean verifyDrawable(Drawable who) {
789 return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
790 }
791
792 @Override
793 public void jumpDrawablesToCurrentState() {
794 super.jumpDrawablesToCurrentState();
795 mThumbDrawable.jumpToCurrentState();
796 mTrackDrawable.jumpToCurrentState();
797 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800798
799 @Override
800 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
801 super.onInitializeAccessibilityEvent(event);
802 event.setClassName(Switch.class.getName());
803 }
804
805 @Override
806 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
807 super.onInitializeAccessibilityNodeInfo(info);
808 info.setClassName(Switch.class.getName());
Svetoslav Ganov78bcc152012-04-12 17:17:19 -0700809 CharSequence switchText = isChecked() ? mTextOn : mTextOff;
810 if (!TextUtils.isEmpty(switchText)) {
811 CharSequence oldText = info.getText();
812 if (TextUtils.isEmpty(oldText)) {
813 info.setText(switchText);
814 } else {
815 StringBuilder newText = new StringBuilder();
816 newText.append(oldText).append(' ').append(switchText);
817 info.setText(newText);
818 }
819 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800820 }
Adam Powell12190b32010-11-28 19:07:53 -0800821}