blob: 689bd02fbe16fc227141ca9336c72ec40cdb8ecd [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;
Daniel Sandler4c3308d2012-04-19 11:04:39 -040032import android.text.method.AllCapsTransformationMethod;
33import android.text.method.TransformationMethod2;
Adam Powell12190b32010-11-28 19:07:53 -080034import android.util.AttributeSet;
35import android.view.Gravity;
36import android.view.MotionEvent;
37import android.view.VelocityTracker;
38import android.view.ViewConfiguration;
Svetoslav Ganov63bce032011-07-23 19:52:17 -070039import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080040import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell12190b32010-11-28 19:07:53 -080041
Adam Powellbe0a4532010-11-29 17:47:48 -080042import com.android.internal.R;
43
Adam Powell12190b32010-11-28 19:07:53 -080044/**
45 * A Switch is a two-state toggle switch widget that can select between two
46 * options. The user may drag the "thumb" back and forth to choose the selected option,
Chet Haase150176d2011-08-26 09:54:06 -070047 * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text}
48 * property controls the text displayed in the label for the switch, whereas the
49 * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text
50 * controls the text on the thumb. Similarly, the
51 * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related
52 * setTypeface() methods control the typeface and style of label text, whereas the
53 * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
54 * the related seSwitchTypeface() methods control that of the thumb.
Adam Powell12190b32010-11-28 19:07:53 -080055 *
Scott Main4c359b72012-07-24 15:51:27 -070056 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
57 * guide.</p>
58 *
59 * @attr ref android.R.styleable#Switch_textOn
60 * @attr ref android.R.styleable#Switch_textOff
61 * @attr ref android.R.styleable#Switch_switchMinWidth
62 * @attr ref android.R.styleable#Switch_switchPadding
63 * @attr ref android.R.styleable#Switch_switchTextAppearance
64 * @attr ref android.R.styleable#Switch_thumb
65 * @attr ref android.R.styleable#Switch_thumbTextPadding
66 * @attr ref android.R.styleable#Switch_track
Adam Powell12190b32010-11-28 19:07:53 -080067 */
68public class Switch extends CompoundButton {
69 private static final int TOUCH_MODE_IDLE = 0;
70 private static final int TOUCH_MODE_DOWN = 1;
71 private static final int TOUCH_MODE_DRAGGING = 2;
72
73 // Enum for the "typeface" XML parameter.
74 private static final int SANS = 1;
75 private static final int SERIF = 2;
76 private static final int MONOSPACE = 3;
77
78 private Drawable mThumbDrawable;
79 private Drawable mTrackDrawable;
80 private int mThumbTextPadding;
81 private int mSwitchMinWidth;
82 private int mSwitchPadding;
83 private CharSequence mTextOn;
84 private CharSequence mTextOff;
85
86 private int mTouchMode;
87 private int mTouchSlop;
88 private float mTouchX;
89 private float mTouchY;
90 private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
91 private int mMinFlingVelocity;
92
93 private float mThumbPosition;
94 private int mSwitchWidth;
95 private int mSwitchHeight;
96 private int mThumbWidth; // Does not include padding
97
98 private int mSwitchLeft;
99 private int mSwitchTop;
100 private int mSwitchRight;
101 private int mSwitchBottom;
102
103 private TextPaint mTextPaint;
104 private ColorStateList mTextColors;
105 private Layout mOnLayout;
106 private Layout mOffLayout;
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400107 private TransformationMethod2 mSwitchTransformationMethod;
Adam Powell12190b32010-11-28 19:07:53 -0800108
Adam Powellbe0a4532010-11-29 17:47:48 -0800109 @SuppressWarnings("hiding")
Adam Powell12190b32010-11-28 19:07:53 -0800110 private final Rect mTempRect = new Rect();
111
112 private static final int[] CHECKED_STATE_SET = {
113 R.attr.state_checked
114 };
115
116 /**
117 * Construct a new Switch with default styling.
118 *
119 * @param context The Context that will determine this widget's theming.
120 */
121 public Switch(Context context) {
122 this(context, null);
123 }
124
125 /**
126 * Construct a new Switch with default styling, overriding specific style
127 * attributes as requested.
128 *
129 * @param context The Context that will determine this widget's theming.
130 * @param attrs Specification of attributes that should deviate from default styling.
131 */
132 public Switch(Context context, AttributeSet attrs) {
133 this(context, attrs, com.android.internal.R.attr.switchStyle);
134 }
135
136 /**
137 * Construct a new Switch with a default style determined by the given theme attribute,
138 * overriding specific style attributes as requested.
139 *
140 * @param context The Context that will determine this widget's theming.
141 * @param attrs Specification of attributes that should deviate from the default styling.
142 * @param defStyle An attribute ID within the active theme containing a reference to the
143 * default style for this widget. e.g. android.R.attr.switchStyle.
144 */
145 public Switch(Context context, AttributeSet attrs, int defStyle) {
146 super(context, attrs, defStyle);
147
148 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
149 Resources res = getResources();
150 mTextPaint.density = res.getDisplayMetrics().density;
151 mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
152
153 TypedArray a = context.obtainStyledAttributes(attrs,
154 com.android.internal.R.styleable.Switch, defStyle, 0);
155
Chet Haase150176d2011-08-26 09:54:06 -0700156 mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
157 mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
Adam Powell12190b32010-11-28 19:07:53 -0800158 mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
159 mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
160 mThumbTextPadding = a.getDimensionPixelSize(
161 com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
162 mSwitchMinWidth = a.getDimensionPixelSize(
163 com.android.internal.R.styleable.Switch_switchMinWidth, 0);
164 mSwitchPadding = a.getDimensionPixelSize(
165 com.android.internal.R.styleable.Switch_switchPadding, 0);
166
167 int appearance = a.getResourceId(
168 com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
169 if (appearance != 0) {
Chet Haase150176d2011-08-26 09:54:06 -0700170 setSwitchTextAppearance(context, appearance);
Adam Powell12190b32010-11-28 19:07:53 -0800171 }
172 a.recycle();
173
174 ViewConfiguration config = ViewConfiguration.get(context);
175 mTouchSlop = config.getScaledTouchSlop();
176 mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
177
178 // Refresh display with current params
Gilles Debunnee724ee42011-08-31 11:20:27 -0700179 refreshDrawableState();
Adam Powell12190b32010-11-28 19:07:53 -0800180 setChecked(isChecked());
181 }
182
183 /**
184 * Sets the switch text color, size, style, hint color, and highlight color
185 * from the specified TextAppearance resource.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800186 *
187 * @attr ref android.R.styleable#Switch_switchTextAppearance
Adam Powell12190b32010-11-28 19:07:53 -0800188 */
Chet Haase150176d2011-08-26 09:54:06 -0700189 public void setSwitchTextAppearance(Context context, int resid) {
Adam Powell12190b32010-11-28 19:07:53 -0800190 TypedArray appearance =
Chet Haase150176d2011-08-26 09:54:06 -0700191 context.obtainStyledAttributes(resid,
Adam Powell12190b32010-11-28 19:07:53 -0800192 com.android.internal.R.styleable.TextAppearance);
193
194 ColorStateList colors;
195 int ts;
196
197 colors = appearance.getColorStateList(com.android.internal.R.styleable.
198 TextAppearance_textColor);
199 if (colors != null) {
200 mTextColors = colors;
Chet Haase150176d2011-08-26 09:54:06 -0700201 } else {
202 // If no color set in TextAppearance, default to the view's textColor
203 mTextColors = getTextColors();
Adam Powell12190b32010-11-28 19:07:53 -0800204 }
205
206 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
207 TextAppearance_textSize, 0);
208 if (ts != 0) {
209 if (ts != mTextPaint.getTextSize()) {
210 mTextPaint.setTextSize(ts);
211 requestLayout();
212 }
213 }
214
215 int typefaceIndex, styleIndex;
216
217 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
218 TextAppearance_typeface, -1);
219 styleIndex = appearance.getInt(com.android.internal.R.styleable.
220 TextAppearance_textStyle, -1);
221
222 setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
223
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400224 boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable.
225 TextAppearance_textAllCaps, false);
226 if (allCaps) {
227 mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
228 mSwitchTransformationMethod.setLengthChangesAllowed(true);
229 } else {
230 mSwitchTransformationMethod = null;
231 }
232
Adam Powell12190b32010-11-28 19:07:53 -0800233 appearance.recycle();
234 }
235
236 private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
237 Typeface tf = null;
238 switch (typefaceIndex) {
239 case SANS:
240 tf = Typeface.SANS_SERIF;
241 break;
242
243 case SERIF:
244 tf = Typeface.SERIF;
245 break;
246
247 case MONOSPACE:
248 tf = Typeface.MONOSPACE;
249 break;
250 }
251
252 setSwitchTypeface(tf, styleIndex);
253 }
254
255 /**
256 * Sets the typeface and style in which the text should be displayed on the
257 * switch, and turns on the fake bold and italic bits in the Paint if the
258 * Typeface that you provided does not have all the bits in the
259 * style that you specified.
260 */
261 public void setSwitchTypeface(Typeface tf, int style) {
262 if (style > 0) {
263 if (tf == null) {
264 tf = Typeface.defaultFromStyle(style);
265 } else {
266 tf = Typeface.create(tf, style);
267 }
268
269 setSwitchTypeface(tf);
270 // now compute what (if any) algorithmic styling is needed
271 int typefaceStyle = tf != null ? tf.getStyle() : 0;
272 int need = style & ~typefaceStyle;
273 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
274 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
275 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -0700276 mTextPaint.setFakeBoldText(false);
Adam Powell12190b32010-11-28 19:07:53 -0800277 mTextPaint.setTextSkewX(0);
278 setSwitchTypeface(tf);
279 }
280 }
281
282 /**
Chet Haase150176d2011-08-26 09:54:06 -0700283 * Sets the typeface in which the text should be displayed on the switch.
Adam Powell12190b32010-11-28 19:07:53 -0800284 * Note that not all Typeface families actually have bold and italic
285 * variants, so you may need to use
286 * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
287 * that you actually want.
288 *
289 * @attr ref android.R.styleable#TextView_typeface
290 * @attr ref android.R.styleable#TextView_textStyle
291 */
292 public void setSwitchTypeface(Typeface tf) {
293 if (mTextPaint.getTypeface() != tf) {
294 mTextPaint.setTypeface(tf);
295
296 requestLayout();
297 invalidate();
298 }
299 }
300
301 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800302 * Set the amount of horizontal padding between the switch and the associated text.
303 *
304 * @param pixels Amount of padding in pixels
305 *
306 * @attr ref android.R.styleable#Switch_switchPadding
307 */
308 public void setSwitchPadding(int pixels) {
309 mSwitchPadding = pixels;
310 requestLayout();
311 }
312
313 /**
314 * Get the amount of horizontal padding between the switch and the associated text.
315 *
316 * @return Amount of padding in pixels
317 *
318 * @attr ref android.R.styleable#Switch_switchPadding
319 */
320 public int getSwitchPadding() {
321 return mSwitchPadding;
322 }
323
324 /**
325 * Set the minimum width of the switch in pixels. The switch's width will be the maximum
326 * of this value and its measured width as determined by the switch drawables and text used.
327 *
328 * @param pixels Minimum width of the switch in pixels
329 *
330 * @attr ref android.R.styleable#Switch_switchMinWidth
331 */
332 public void setSwitchMinWidth(int pixels) {
333 mSwitchMinWidth = pixels;
334 requestLayout();
335 }
336
337 /**
338 * Get the minimum width of the switch in pixels. The switch's width will be the maximum
339 * of this value and its measured width as determined by the switch drawables and text used.
340 *
341 * @return Minimum width of the switch in pixels
342 *
343 * @attr ref android.R.styleable#Switch_switchMinWidth
344 */
345 public int getSwitchMinWidth() {
346 return mSwitchMinWidth;
347 }
348
349 /**
350 * Set the horizontal padding around the text drawn on the switch itself.
351 *
352 * @param pixels Horizontal padding for switch thumb text in pixels
353 *
354 * @attr ref android.R.styleable#Switch_thumbTextPadding
355 */
356 public void setThumbTextPadding(int pixels) {
357 mThumbTextPadding = pixels;
358 requestLayout();
359 }
360
361 /**
362 * Get the horizontal padding around the text drawn on the switch itself.
363 *
364 * @return Horizontal padding for switch thumb text in pixels
365 *
366 * @attr ref android.R.styleable#Switch_thumbTextPadding
367 */
368 public int getThumbTextPadding() {
369 return mThumbTextPadding;
370 }
371
372 /**
373 * Set the drawable used for the track that the switch slides within.
374 *
375 * @param track Track drawable
376 *
377 * @attr ref android.R.styleable#Switch_track
378 */
379 public void setTrackDrawable(Drawable track) {
380 mTrackDrawable = track;
381 requestLayout();
382 }
383
384 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800385 * Set the drawable used for the track that the switch slides within.
386 *
Adam Powelldca510e2012-03-08 20:06:39 -0800387 * @param resId Resource ID of a track drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800388 *
389 * @attr ref android.R.styleable#Switch_track
390 */
391 public void setTrackResource(int resId) {
392 setTrackDrawable(getContext().getResources().getDrawable(resId));
393 }
394
395 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800396 * Get the drawable used for the track that the switch slides within.
397 *
398 * @return Track drawable
399 *
400 * @attr ref android.R.styleable#Switch_track
401 */
402 public Drawable getTrackDrawable() {
403 return mTrackDrawable;
404 }
405
406 /**
407 * Set the drawable used for the switch "thumb" - the piece that the user
408 * can physically touch and drag along the track.
409 *
410 * @param thumb Thumb drawable
411 *
412 * @attr ref android.R.styleable#Switch_thumb
413 */
414 public void setThumbDrawable(Drawable thumb) {
415 mThumbDrawable = thumb;
416 requestLayout();
417 }
418
419 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800420 * Set the drawable used for the switch "thumb" - the piece that the user
421 * can physically touch and drag along the track.
422 *
Adam Powelldca510e2012-03-08 20:06:39 -0800423 * @param resId Resource ID of a thumb drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800424 *
425 * @attr ref android.R.styleable#Switch_thumb
426 */
427 public void setThumbResource(int resId) {
428 setThumbDrawable(getContext().getResources().getDrawable(resId));
429 }
430
431 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800432 * Get the drawable used for the switch "thumb" - the piece that the user
433 * can physically touch and drag along the track.
434 *
435 * @return Thumb drawable
436 *
437 * @attr ref android.R.styleable#Switch_thumb
438 */
439 public Drawable getThumbDrawable() {
440 return mThumbDrawable;
441 }
442
443 /**
Chet Haase150176d2011-08-26 09:54:06 -0700444 * Returns the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800445 *
446 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800447 */
448 public CharSequence getTextOn() {
449 return mTextOn;
450 }
451
452 /**
Chet Haase150176d2011-08-26 09:54:06 -0700453 * Sets the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800454 *
455 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800456 */
457 public void setTextOn(CharSequence textOn) {
458 mTextOn = textOn;
459 requestLayout();
460 }
461
462 /**
Chet Haase150176d2011-08-26 09:54:06 -0700463 * Returns the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800464 *
465 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800466 */
467 public CharSequence getTextOff() {
468 return mTextOff;
469 }
470
471 /**
Chet Haase150176d2011-08-26 09:54:06 -0700472 * Sets the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800473 *
474 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800475 */
476 public void setTextOff(CharSequence textOff) {
477 mTextOff = textOff;
478 requestLayout();
479 }
480
481 @Override
482 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adam Powell12190b32010-11-28 19:07:53 -0800483 if (mOnLayout == null) {
484 mOnLayout = makeLayout(mTextOn);
485 }
486 if (mOffLayout == null) {
487 mOffLayout = makeLayout(mTextOff);
488 }
489
490 mTrackDrawable.getPadding(mTempRect);
491 final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
492 final int switchWidth = Math.max(mSwitchMinWidth,
493 maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
494 final int switchHeight = mTrackDrawable.getIntrinsicHeight();
495
496 mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
497
Adam Powell12190b32010-11-28 19:07:53 -0800498 mSwitchWidth = switchWidth;
499 mSwitchHeight = switchHeight;
500
501 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Adam Powell12190b32010-11-28 19:07:53 -0800502 final int measuredHeight = getMeasuredHeight();
503 if (measuredHeight < switchHeight) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800504 setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
Adam Powell12190b32010-11-28 19:07:53 -0800505 }
506 }
507
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700508 @Override
509 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
510 super.onPopulateAccessibilityEvent(event);
Svetoslav Ganov55249c82012-03-09 10:09:03 -0800511 CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
512 if (!TextUtils.isEmpty(text)) {
Svetoslav Ganov76502592011-07-29 10:44:59 -0700513 event.getText().add(text);
514 }
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700515 }
516
Adam Powell12190b32010-11-28 19:07:53 -0800517 private Layout makeLayout(CharSequence text) {
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400518 final CharSequence transformed = (mSwitchTransformationMethod != null)
519 ? mSwitchTransformationMethod.getTransformation(text, this)
520 : text;
521
522 return new StaticLayout(transformed, mTextPaint,
523 (int) Math.ceil(Layout.getDesiredWidth(transformed, mTextPaint)),
Adam Powell12190b32010-11-28 19:07:53 -0800524 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
525 }
526
527 /**
528 * @return true if (x, y) is within the target area of the switch thumb
529 */
530 private boolean hitThumb(float x, float y) {
531 mThumbDrawable.getPadding(mTempRect);
532 final int thumbTop = mSwitchTop - mTouchSlop;
533 final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
534 final int thumbRight = thumbLeft + mThumbWidth +
535 mTempRect.left + mTempRect.right + mTouchSlop;
536 final int thumbBottom = mSwitchBottom + mTouchSlop;
537 return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
538 }
539
540 @Override
541 public boolean onTouchEvent(MotionEvent ev) {
542 mVelocityTracker.addMovement(ev);
543 final int action = ev.getActionMasked();
544 switch (action) {
545 case MotionEvent.ACTION_DOWN: {
546 final float x = ev.getX();
547 final float y = ev.getY();
Gilles Debunnec2ab0d62011-06-13 12:52:48 -0700548 if (isEnabled() && hitThumb(x, y)) {
Adam Powell12190b32010-11-28 19:07:53 -0800549 mTouchMode = TOUCH_MODE_DOWN;
550 mTouchX = x;
551 mTouchY = y;
552 }
553 break;
554 }
555
556 case MotionEvent.ACTION_MOVE: {
557 switch (mTouchMode) {
558 case TOUCH_MODE_IDLE:
559 // Didn't target the thumb, treat normally.
560 break;
561
562 case TOUCH_MODE_DOWN: {
563 final float x = ev.getX();
564 final float y = ev.getY();
565 if (Math.abs(x - mTouchX) > mTouchSlop ||
566 Math.abs(y - mTouchY) > mTouchSlop) {
567 mTouchMode = TOUCH_MODE_DRAGGING;
568 getParent().requestDisallowInterceptTouchEvent(true);
569 mTouchX = x;
570 mTouchY = y;
571 return true;
572 }
573 break;
574 }
575
576 case TOUCH_MODE_DRAGGING: {
577 final float x = ev.getX();
578 final float dx = x - mTouchX;
579 float newPos = Math.max(0,
580 Math.min(mThumbPosition + dx, getThumbScrollRange()));
581 if (newPos != mThumbPosition) {
582 mThumbPosition = newPos;
583 mTouchX = x;
584 invalidate();
585 }
586 return true;
587 }
588 }
589 break;
590 }
591
592 case MotionEvent.ACTION_UP:
593 case MotionEvent.ACTION_CANCEL: {
594 if (mTouchMode == TOUCH_MODE_DRAGGING) {
595 stopDrag(ev);
596 return true;
597 }
598 mTouchMode = TOUCH_MODE_IDLE;
599 mVelocityTracker.clear();
600 break;
601 }
602 }
603
604 return super.onTouchEvent(ev);
605 }
606
607 private void cancelSuperTouch(MotionEvent ev) {
608 MotionEvent cancel = MotionEvent.obtain(ev);
609 cancel.setAction(MotionEvent.ACTION_CANCEL);
610 super.onTouchEvent(cancel);
611 cancel.recycle();
612 }
613
614 /**
615 * Called from onTouchEvent to end a drag operation.
616 *
617 * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
618 */
619 private void stopDrag(MotionEvent ev) {
620 mTouchMode = TOUCH_MODE_IDLE;
Gilles Debunnec2ab0d62011-06-13 12:52:48 -0700621 // Up and not canceled, also checks the switch has not been disabled during the drag
622 boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
Adam Powell12190b32010-11-28 19:07:53 -0800623
624 cancelSuperTouch(ev);
625
626 if (commitChange) {
627 boolean newState;
628 mVelocityTracker.computeCurrentVelocity(1000);
629 float xvel = mVelocityTracker.getXVelocity();
630 if (Math.abs(xvel) > mMinFlingVelocity) {
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700631 newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0);
Adam Powell12190b32010-11-28 19:07:53 -0800632 } else {
633 newState = getTargetCheckedState();
634 }
635 animateThumbToCheckedState(newState);
636 } else {
637 animateThumbToCheckedState(isChecked());
638 }
639 }
640
641 private void animateThumbToCheckedState(boolean newCheckedState) {
Adam Powell12190b32010-11-28 19:07:53 -0800642 // TODO animate!
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800643 //float targetPos = newCheckedState ? 0 : getThumbScrollRange();
644 //mThumbPosition = targetPos;
Adam Powell12190b32010-11-28 19:07:53 -0800645 setChecked(newCheckedState);
646 }
647
648 private boolean getTargetCheckedState() {
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700649 if (isLayoutRtl()) {
650 return mThumbPosition <= getThumbScrollRange() / 2;
651 } else {
652 return mThumbPosition >= getThumbScrollRange() / 2;
653 }
654 }
655
656 private void setThumbPosition(boolean checked) {
657 if (isLayoutRtl()) {
658 mThumbPosition = checked ? 0 : getThumbScrollRange();
659 } else {
660 mThumbPosition = checked ? getThumbScrollRange() : 0;
661 }
Adam Powell12190b32010-11-28 19:07:53 -0800662 }
663
664 @Override
665 public void setChecked(boolean checked) {
666 super.setChecked(checked);
Jean-Baptiste Queruacbbd112012-08-20 14:57:23 -0700667 setThumbPosition(isChecked());
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800668 invalidate();
Adam Powell12190b32010-11-28 19:07:53 -0800669 }
670
671 @Override
672 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
673 super.onLayout(changed, left, top, right, bottom);
674
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700675 setThumbPosition(isChecked());
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800676
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700677 int switchRight;
678 int switchLeft;
679
680 if (isLayoutRtl()) {
681 switchLeft = getPaddingLeft();
682 switchRight = switchLeft + mSwitchWidth;
683 } else {
684 switchRight = getWidth() - getPaddingRight();
685 switchLeft = switchRight - mSwitchWidth;
686 }
687
Adam Powell12190b32010-11-28 19:07:53 -0800688 int switchTop = 0;
689 int switchBottom = 0;
690 switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
691 default:
692 case Gravity.TOP:
693 switchTop = getPaddingTop();
694 switchBottom = switchTop + mSwitchHeight;
695 break;
696
697 case Gravity.CENTER_VERTICAL:
698 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
699 mSwitchHeight / 2;
700 switchBottom = switchTop + mSwitchHeight;
701 break;
702
703 case Gravity.BOTTOM:
704 switchBottom = getHeight() - getPaddingBottom();
705 switchTop = switchBottom - mSwitchHeight;
706 break;
707 }
708
709 mSwitchLeft = switchLeft;
710 mSwitchTop = switchTop;
711 mSwitchBottom = switchBottom;
712 mSwitchRight = switchRight;
713 }
714
715 @Override
716 protected void onDraw(Canvas canvas) {
717 super.onDraw(canvas);
718
719 // Draw the switch
720 int switchLeft = mSwitchLeft;
721 int switchTop = mSwitchTop;
722 int switchRight = mSwitchRight;
723 int switchBottom = mSwitchBottom;
724
725 mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
726 mTrackDrawable.draw(canvas);
727
728 canvas.save();
729
730 mTrackDrawable.getPadding(mTempRect);
731 int switchInnerLeft = switchLeft + mTempRect.left;
732 int switchInnerTop = switchTop + mTempRect.top;
733 int switchInnerRight = switchRight - mTempRect.right;
734 int switchInnerBottom = switchBottom - mTempRect.bottom;
735 canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
736
737 mThumbDrawable.getPadding(mTempRect);
738 final int thumbPos = (int) (mThumbPosition + 0.5f);
739 int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
740 int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
741
742 mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
743 mThumbDrawable.draw(canvas);
744
Chet Haase150176d2011-08-26 09:54:06 -0700745 // mTextColors should not be null, but just in case
746 if (mTextColors != null) {
747 mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
748 mTextColors.getDefaultColor()));
749 }
Adam Powell12190b32010-11-28 19:07:53 -0800750 mTextPaint.drawableState = getDrawableState();
751
752 Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
Fabrice Di Megliobe06e322012-09-11 17:42:45 -0700753 if (switchText != null) {
754 canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
755 (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
756 switchText.draw(canvas);
757 }
Adam Powell12190b32010-11-28 19:07:53 -0800758
759 canvas.restore();
760 }
761
762 @Override
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700763 public int getCompoundPaddingLeft() {
764 if (!isLayoutRtl()) {
765 return super.getCompoundPaddingLeft();
766 }
767 int padding = super.getCompoundPaddingLeft() + mSwitchWidth;
768 if (!TextUtils.isEmpty(getText())) {
769 padding += mSwitchPadding;
770 }
771 return padding;
772 }
773
774 @Override
Adam Powell12190b32010-11-28 19:07:53 -0800775 public int getCompoundPaddingRight() {
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700776 if (isLayoutRtl()) {
777 return super.getCompoundPaddingRight();
778 }
Adam Powell12190b32010-11-28 19:07:53 -0800779 int padding = super.getCompoundPaddingRight() + mSwitchWidth;
780 if (!TextUtils.isEmpty(getText())) {
781 padding += mSwitchPadding;
782 }
783 return padding;
784 }
785
786 private int getThumbScrollRange() {
787 if (mTrackDrawable == null) {
788 return 0;
789 }
790 mTrackDrawable.getPadding(mTempRect);
791 return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
792 }
793
794 @Override
795 protected int[] onCreateDrawableState(int extraSpace) {
796 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
797 if (isChecked()) {
798 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
799 }
800 return drawableState;
801 }
802
803 @Override
804 protected void drawableStateChanged() {
805 super.drawableStateChanged();
806
807 int[] myDrawableState = getDrawableState();
808
809 // Set the state of the Drawable
Gilles Debunnee724ee42011-08-31 11:20:27 -0700810 // Drawable may be null when checked state is set from XML, from super constructor
811 if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
812 if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
Adam Powell12190b32010-11-28 19:07:53 -0800813
814 invalidate();
815 }
816
817 @Override
818 protected boolean verifyDrawable(Drawable who) {
819 return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
820 }
821
822 @Override
823 public void jumpDrawablesToCurrentState() {
824 super.jumpDrawablesToCurrentState();
825 mThumbDrawable.jumpToCurrentState();
826 mTrackDrawable.jumpToCurrentState();
827 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800828
829 @Override
830 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
831 super.onInitializeAccessibilityEvent(event);
832 event.setClassName(Switch.class.getName());
833 }
834
835 @Override
836 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
837 super.onInitializeAccessibilityNodeInfo(info);
838 info.setClassName(Switch.class.getName());
Svetoslav Ganov78bcc152012-04-12 17:17:19 -0700839 CharSequence switchText = isChecked() ? mTextOn : mTextOff;
840 if (!TextUtils.isEmpty(switchText)) {
841 CharSequence oldText = info.getText();
842 if (TextUtils.isEmpty(oldText)) {
843 info.setText(switchText);
844 } else {
845 StringBuilder newText = new StringBuilder();
846 newText.append(oldText).append(' ').append(switchText);
847 info.setText(newText);
848 }
849 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800850 }
Adam Powell12190b32010-11-28 19:07:53 -0800851}