blob: a8980849af43baeec3469608047438f7a07f1a3b [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
Alan Viverettecc2688d2013-09-17 17:00:12 -070019import android.animation.ObjectAnimator;
Adam Powell12190b32010-11-28 19:07:53 -080020import android.content.Context;
21import android.content.res.ColorStateList;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
Alan Viverette661e6362014-05-12 10:55:37 -070025import android.graphics.Insets;
Adam Powell12190b32010-11-28 19:07:53 -080026import android.graphics.Paint;
27import android.graphics.Rect;
28import android.graphics.Typeface;
Alan Viverette661e6362014-05-12 10:55:37 -070029import android.graphics.Region.Op;
Adam Powell12190b32010-11-28 19:07:53 -080030import android.graphics.drawable.Drawable;
31import android.text.Layout;
32import android.text.StaticLayout;
33import android.text.TextPaint;
34import android.text.TextUtils;
Daniel Sandler4c3308d2012-04-19 11:04:39 -040035import android.text.method.AllCapsTransformationMethod;
36import android.text.method.TransformationMethod2;
Adam Powell12190b32010-11-28 19:07:53 -080037import android.util.AttributeSet;
Alan Viverettecc2688d2013-09-17 17:00:12 -070038import android.util.FloatProperty;
39import android.util.MathUtils;
Adam Powell12190b32010-11-28 19:07:53 -080040import android.view.Gravity;
41import android.view.MotionEvent;
42import android.view.VelocityTracker;
43import android.view.ViewConfiguration;
Svetoslav Ganov63bce032011-07-23 19:52:17 -070044import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080045import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell12190b32010-11-28 19:07:53 -080046
Adam Powellbe0a4532010-11-29 17:47:48 -080047import com.android.internal.R;
48
Adam Powell12190b32010-11-28 19:07:53 -080049/**
50 * A Switch is a two-state toggle switch widget that can select between two
51 * options. The user may drag the "thumb" back and forth to choose the selected option,
Chet Haase150176d2011-08-26 09:54:06 -070052 * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text}
53 * property controls the text displayed in the label for the switch, whereas the
54 * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text
55 * controls the text on the thumb. Similarly, the
56 * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related
57 * setTypeface() methods control the typeface and style of label text, whereas the
58 * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
59 * the related seSwitchTypeface() methods control that of the thumb.
Adam Powell12190b32010-11-28 19:07:53 -080060 *
Scott Main4c359b72012-07-24 15:51:27 -070061 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
62 * guide.</p>
63 *
64 * @attr ref android.R.styleable#Switch_textOn
65 * @attr ref android.R.styleable#Switch_textOff
66 * @attr ref android.R.styleable#Switch_switchMinWidth
67 * @attr ref android.R.styleable#Switch_switchPadding
68 * @attr ref android.R.styleable#Switch_switchTextAppearance
69 * @attr ref android.R.styleable#Switch_thumb
70 * @attr ref android.R.styleable#Switch_thumbTextPadding
71 * @attr ref android.R.styleable#Switch_track
Adam Powell12190b32010-11-28 19:07:53 -080072 */
73public class Switch extends CompoundButton {
Alan Viverettecc2688d2013-09-17 17:00:12 -070074 private static final int THUMB_ANIMATION_DURATION = 250;
75
Adam Powell12190b32010-11-28 19:07:53 -080076 private static final int TOUCH_MODE_IDLE = 0;
77 private static final int TOUCH_MODE_DOWN = 1;
78 private static final int TOUCH_MODE_DRAGGING = 2;
79
80 // Enum for the "typeface" XML parameter.
81 private static final int SANS = 1;
82 private static final int SERIF = 2;
83 private static final int MONOSPACE = 3;
84
85 private Drawable mThumbDrawable;
86 private Drawable mTrackDrawable;
87 private int mThumbTextPadding;
88 private int mSwitchMinWidth;
89 private int mSwitchPadding;
Alan Viverette661e6362014-05-12 10:55:37 -070090 private boolean mSplitTrack;
Adam Powell12190b32010-11-28 19:07:53 -080091 private CharSequence mTextOn;
92 private CharSequence mTextOff;
Alan Viverette2a37cf8d2014-06-19 17:09:10 -070093 private boolean mShowText;
Adam Powell12190b32010-11-28 19:07:53 -080094
95 private int mTouchMode;
96 private int mTouchSlop;
97 private float mTouchX;
98 private float mTouchY;
99 private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
100 private int mMinFlingVelocity;
101
102 private float mThumbPosition;
Alan Viverette8bb39902014-07-29 17:22:30 -0700103
Alan Viverette0c0dde72014-07-30 13:29:39 -0700104 /**
105 * Width required to draw the switch track and thumb. Includes padding and
106 * optical bounds for both the track and thumb.
107 */
108 private int mSwitchWidth;
109
110 /**
111 * Height required to draw the switch track and thumb. Includes padding and
112 * optical bounds for both the track and thumb.
113 */
114 private int mSwitchHeight;
115
116 /**
117 * Width of the thumb's content region. Does not include padding or
118 * optical bounds.
119 */
120 private int mThumbWidth;
121
122 /** Left bound for drawing the switch track and thumb. */
Adam Powell12190b32010-11-28 19:07:53 -0800123 private int mSwitchLeft;
Alan Viverette0c0dde72014-07-30 13:29:39 -0700124
125 /** Top bound for drawing the switch track and thumb. */
Adam Powell12190b32010-11-28 19:07:53 -0800126 private int mSwitchTop;
Alan Viverette0c0dde72014-07-30 13:29:39 -0700127
128 /** Right bound for drawing the switch track and thumb. */
Adam Powell12190b32010-11-28 19:07:53 -0800129 private int mSwitchRight;
Alan Viverette0c0dde72014-07-30 13:29:39 -0700130
131 /** Bottom bound for drawing the switch track and thumb. */
Adam Powell12190b32010-11-28 19:07:53 -0800132 private int mSwitchBottom;
133
134 private TextPaint mTextPaint;
135 private ColorStateList mTextColors;
136 private Layout mOnLayout;
137 private Layout mOffLayout;
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400138 private TransformationMethod2 mSwitchTransformationMethod;
Alan Viverettecc2688d2013-09-17 17:00:12 -0700139 private ObjectAnimator mPositionAnimator;
Adam Powell12190b32010-11-28 19:07:53 -0800140
Adam Powellbe0a4532010-11-29 17:47:48 -0800141 @SuppressWarnings("hiding")
Adam Powell12190b32010-11-28 19:07:53 -0800142 private final Rect mTempRect = new Rect();
143
144 private static final int[] CHECKED_STATE_SET = {
145 R.attr.state_checked
146 };
147
148 /**
149 * Construct a new Switch with default styling.
150 *
151 * @param context The Context that will determine this widget's theming.
152 */
153 public Switch(Context context) {
154 this(context, null);
155 }
156
157 /**
158 * Construct a new Switch with default styling, overriding specific style
159 * attributes as requested.
160 *
161 * @param context The Context that will determine this widget's theming.
162 * @param attrs Specification of attributes that should deviate from default styling.
163 */
164 public Switch(Context context, AttributeSet attrs) {
165 this(context, attrs, com.android.internal.R.attr.switchStyle);
166 }
167
168 /**
169 * Construct a new Switch with a default style determined by the given theme attribute,
170 * overriding specific style attributes as requested.
171 *
172 * @param context The Context that will determine this widget's theming.
173 * @param attrs Specification of attributes that should deviate from the default styling.
Alan Viverette617feb92013-09-09 18:09:13 -0700174 * @param defStyleAttr An attribute in the current theme that contains a
175 * reference to a style resource that supplies default values for
176 * the view. Can be 0 to not look for defaults.
Adam Powell12190b32010-11-28 19:07:53 -0800177 */
Alan Viverette617feb92013-09-09 18:09:13 -0700178 public Switch(Context context, AttributeSet attrs, int defStyleAttr) {
179 this(context, attrs, defStyleAttr, 0);
180 }
181
182
183 /**
184 * Construct a new Switch with a default style determined by the given theme
185 * attribute or style resource, overriding specific style attributes as
186 * requested.
187 *
188 * @param context The Context that will determine this widget's theming.
189 * @param attrs Specification of attributes that should deviate from the
190 * default styling.
191 * @param defStyleAttr An attribute in the current theme that contains a
192 * reference to a style resource that supplies default values for
193 * the view. Can be 0 to not look for defaults.
194 * @param defStyleRes A resource identifier of a style resource that
195 * supplies default values for the view, used only if
196 * defStyleAttr is 0 or can not be found in the theme. Can be 0
197 * to not look for defaults.
198 */
199 public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
200 super(context, attrs, defStyleAttr, defStyleRes);
Adam Powell12190b32010-11-28 19:07:53 -0800201
202 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Alan Viverette661e6362014-05-12 10:55:37 -0700203
204 final Resources res = getResources();
Adam Powell12190b32010-11-28 19:07:53 -0800205 mTextPaint.density = res.getDisplayMetrics().density;
206 mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
207
Alan Viverette617feb92013-09-09 18:09:13 -0700208 final TypedArray a = context.obtainStyledAttributes(
209 attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes);
Chet Haase150176d2011-08-26 09:54:06 -0700210 mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
Alan Viveretteb0674052014-09-26 16:12:16 -0700211 if (mThumbDrawable != null) {
212 mThumbDrawable.setCallback(this);
213 }
Chet Haase150176d2011-08-26 09:54:06 -0700214 mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
Alan Viveretteb0674052014-09-26 16:12:16 -0700215 if (mTrackDrawable != null) {
216 mTrackDrawable.setCallback(this);
217 }
Adam Powell12190b32010-11-28 19:07:53 -0800218 mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
219 mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
Alan Viverette2a37cf8d2014-06-19 17:09:10 -0700220 mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true);
Adam Powell12190b32010-11-28 19:07:53 -0800221 mThumbTextPadding = a.getDimensionPixelSize(
222 com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
223 mSwitchMinWidth = a.getDimensionPixelSize(
224 com.android.internal.R.styleable.Switch_switchMinWidth, 0);
225 mSwitchPadding = a.getDimensionPixelSize(
226 com.android.internal.R.styleable.Switch_switchPadding, 0);
Alan Viverette661e6362014-05-12 10:55:37 -0700227 mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
Adam Powell12190b32010-11-28 19:07:53 -0800228
Alan Viverette661e6362014-05-12 10:55:37 -0700229 final int appearance = a.getResourceId(
Adam Powell12190b32010-11-28 19:07:53 -0800230 com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
231 if (appearance != 0) {
Chet Haase150176d2011-08-26 09:54:06 -0700232 setSwitchTextAppearance(context, appearance);
Adam Powell12190b32010-11-28 19:07:53 -0800233 }
234 a.recycle();
235
Alan Viverette661e6362014-05-12 10:55:37 -0700236 final ViewConfiguration config = ViewConfiguration.get(context);
Adam Powell12190b32010-11-28 19:07:53 -0800237 mTouchSlop = config.getScaledTouchSlop();
238 mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
239
240 // Refresh display with current params
Gilles Debunnee724ee42011-08-31 11:20:27 -0700241 refreshDrawableState();
Adam Powell12190b32010-11-28 19:07:53 -0800242 setChecked(isChecked());
243 }
244
245 /**
246 * Sets the switch text color, size, style, hint color, and highlight color
247 * from the specified TextAppearance resource.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800248 *
249 * @attr ref android.R.styleable#Switch_switchTextAppearance
Adam Powell12190b32010-11-28 19:07:53 -0800250 */
Chet Haase150176d2011-08-26 09:54:06 -0700251 public void setSwitchTextAppearance(Context context, int resid) {
Adam Powell12190b32010-11-28 19:07:53 -0800252 TypedArray appearance =
Chet Haase150176d2011-08-26 09:54:06 -0700253 context.obtainStyledAttributes(resid,
Adam Powell12190b32010-11-28 19:07:53 -0800254 com.android.internal.R.styleable.TextAppearance);
255
256 ColorStateList colors;
257 int ts;
258
259 colors = appearance.getColorStateList(com.android.internal.R.styleable.
260 TextAppearance_textColor);
261 if (colors != null) {
262 mTextColors = colors;
Chet Haase150176d2011-08-26 09:54:06 -0700263 } else {
264 // If no color set in TextAppearance, default to the view's textColor
265 mTextColors = getTextColors();
Adam Powell12190b32010-11-28 19:07:53 -0800266 }
267
268 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
269 TextAppearance_textSize, 0);
270 if (ts != 0) {
271 if (ts != mTextPaint.getTextSize()) {
272 mTextPaint.setTextSize(ts);
273 requestLayout();
274 }
275 }
276
277 int typefaceIndex, styleIndex;
278
279 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
280 TextAppearance_typeface, -1);
281 styleIndex = appearance.getInt(com.android.internal.R.styleable.
282 TextAppearance_textStyle, -1);
283
284 setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
285
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400286 boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable.
287 TextAppearance_textAllCaps, false);
288 if (allCaps) {
289 mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
290 mSwitchTransformationMethod.setLengthChangesAllowed(true);
291 } else {
292 mSwitchTransformationMethod = null;
293 }
294
Adam Powell12190b32010-11-28 19:07:53 -0800295 appearance.recycle();
296 }
297
298 private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
299 Typeface tf = null;
300 switch (typefaceIndex) {
301 case SANS:
302 tf = Typeface.SANS_SERIF;
303 break;
304
305 case SERIF:
306 tf = Typeface.SERIF;
307 break;
308
309 case MONOSPACE:
310 tf = Typeface.MONOSPACE;
311 break;
312 }
313
314 setSwitchTypeface(tf, styleIndex);
315 }
316
317 /**
318 * Sets the typeface and style in which the text should be displayed on the
319 * switch, and turns on the fake bold and italic bits in the Paint if the
320 * Typeface that you provided does not have all the bits in the
321 * style that you specified.
322 */
323 public void setSwitchTypeface(Typeface tf, int style) {
324 if (style > 0) {
325 if (tf == null) {
326 tf = Typeface.defaultFromStyle(style);
327 } else {
328 tf = Typeface.create(tf, style);
329 }
330
331 setSwitchTypeface(tf);
332 // now compute what (if any) algorithmic styling is needed
333 int typefaceStyle = tf != null ? tf.getStyle() : 0;
334 int need = style & ~typefaceStyle;
335 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
336 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
337 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -0700338 mTextPaint.setFakeBoldText(false);
Adam Powell12190b32010-11-28 19:07:53 -0800339 mTextPaint.setTextSkewX(0);
340 setSwitchTypeface(tf);
341 }
342 }
343
344 /**
Chet Haase150176d2011-08-26 09:54:06 -0700345 * Sets the typeface in which the text should be displayed on the switch.
Adam Powell12190b32010-11-28 19:07:53 -0800346 * Note that not all Typeface families actually have bold and italic
347 * variants, so you may need to use
348 * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
349 * that you actually want.
350 *
351 * @attr ref android.R.styleable#TextView_typeface
352 * @attr ref android.R.styleable#TextView_textStyle
353 */
354 public void setSwitchTypeface(Typeface tf) {
355 if (mTextPaint.getTypeface() != tf) {
356 mTextPaint.setTypeface(tf);
357
358 requestLayout();
359 invalidate();
360 }
361 }
362
363 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800364 * Set the amount of horizontal padding between the switch and the associated text.
365 *
366 * @param pixels Amount of padding in pixels
367 *
368 * @attr ref android.R.styleable#Switch_switchPadding
369 */
370 public void setSwitchPadding(int pixels) {
371 mSwitchPadding = pixels;
372 requestLayout();
373 }
374
375 /**
376 * Get the amount of horizontal padding between the switch and the associated text.
377 *
378 * @return Amount of padding in pixels
379 *
380 * @attr ref android.R.styleable#Switch_switchPadding
381 */
382 public int getSwitchPadding() {
383 return mSwitchPadding;
384 }
385
386 /**
387 * Set the minimum width of the switch in pixels. The switch's width will be the maximum
388 * of this value and its measured width as determined by the switch drawables and text used.
389 *
390 * @param pixels Minimum width of the switch in pixels
391 *
392 * @attr ref android.R.styleable#Switch_switchMinWidth
393 */
394 public void setSwitchMinWidth(int pixels) {
395 mSwitchMinWidth = pixels;
396 requestLayout();
397 }
398
399 /**
400 * Get the minimum width of the switch in pixels. The switch's width will be the maximum
401 * of this value and its measured width as determined by the switch drawables and text used.
402 *
403 * @return Minimum width of the switch in pixels
404 *
405 * @attr ref android.R.styleable#Switch_switchMinWidth
406 */
407 public int getSwitchMinWidth() {
408 return mSwitchMinWidth;
409 }
410
411 /**
412 * Set the horizontal padding around the text drawn on the switch itself.
413 *
414 * @param pixels Horizontal padding for switch thumb text in pixels
415 *
416 * @attr ref android.R.styleable#Switch_thumbTextPadding
417 */
418 public void setThumbTextPadding(int pixels) {
419 mThumbTextPadding = pixels;
420 requestLayout();
421 }
422
423 /**
424 * Get the horizontal padding around the text drawn on the switch itself.
425 *
426 * @return Horizontal padding for switch thumb text in pixels
427 *
428 * @attr ref android.R.styleable#Switch_thumbTextPadding
429 */
430 public int getThumbTextPadding() {
431 return mThumbTextPadding;
432 }
433
434 /**
435 * Set the drawable used for the track that the switch slides within.
436 *
437 * @param track Track drawable
438 *
439 * @attr ref android.R.styleable#Switch_track
440 */
441 public void setTrackDrawable(Drawable track) {
Alan Viveretteb0674052014-09-26 16:12:16 -0700442 if (mTrackDrawable != null) {
443 mTrackDrawable.setCallback(null);
444 }
Adam Powell6c86e1b2012-03-08 15:11:46 -0800445 mTrackDrawable = track;
Alan Viveretteb0674052014-09-26 16:12:16 -0700446 if (track != null) {
447 track.setCallback(this);
448 }
Adam Powell6c86e1b2012-03-08 15:11:46 -0800449 requestLayout();
450 }
451
452 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800453 * Set the drawable used for the track that the switch slides within.
454 *
Adam Powelldca510e2012-03-08 20:06:39 -0800455 * @param resId Resource ID of a track drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800456 *
457 * @attr ref android.R.styleable#Switch_track
458 */
459 public void setTrackResource(int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800460 setTrackDrawable(getContext().getDrawable(resId));
Adam Powelld9c7be62012-03-08 19:43:43 -0800461 }
462
463 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800464 * Get the drawable used for the track that the switch slides within.
465 *
466 * @return Track drawable
467 *
468 * @attr ref android.R.styleable#Switch_track
469 */
470 public Drawable getTrackDrawable() {
471 return mTrackDrawable;
472 }
473
474 /**
475 * Set the drawable used for the switch "thumb" - the piece that the user
476 * can physically touch and drag along the track.
477 *
478 * @param thumb Thumb drawable
479 *
480 * @attr ref android.R.styleable#Switch_thumb
481 */
482 public void setThumbDrawable(Drawable thumb) {
Alan Viveretteb0674052014-09-26 16:12:16 -0700483 if (mThumbDrawable != null) {
484 mThumbDrawable.setCallback(null);
485 }
Adam Powell6c86e1b2012-03-08 15:11:46 -0800486 mThumbDrawable = thumb;
Alan Viveretteb0674052014-09-26 16:12:16 -0700487 if (thumb != null) {
488 thumb.setCallback(this);
489 }
Adam Powell6c86e1b2012-03-08 15:11:46 -0800490 requestLayout();
491 }
492
493 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800494 * Set the drawable used for the switch "thumb" - the piece that the user
495 * can physically touch and drag along the track.
496 *
Adam Powelldca510e2012-03-08 20:06:39 -0800497 * @param resId Resource ID of a thumb drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800498 *
499 * @attr ref android.R.styleable#Switch_thumb
500 */
501 public void setThumbResource(int resId) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800502 setThumbDrawable(getContext().getDrawable(resId));
Adam Powelld9c7be62012-03-08 19:43:43 -0800503 }
504
505 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800506 * Get the drawable used for the switch "thumb" - the piece that the user
507 * can physically touch and drag along the track.
508 *
509 * @return Thumb drawable
510 *
511 * @attr ref android.R.styleable#Switch_thumb
512 */
513 public Drawable getThumbDrawable() {
514 return mThumbDrawable;
515 }
516
517 /**
Alan Viverette661e6362014-05-12 10:55:37 -0700518 * Specifies whether the track should be split by the thumb. When true,
519 * the thumb's optical bounds will be clipped out of the track drawable,
520 * then the thumb will be drawn into the resulting gap.
521 *
522 * @param splitTrack Whether the track should be split by the thumb
523 *
524 * @attr ref android.R.styleable#Switch_splitTrack
525 */
526 public void setSplitTrack(boolean splitTrack) {
527 mSplitTrack = splitTrack;
528 invalidate();
529 }
530
531 /**
532 * Returns whether the track should be split by the thumb.
533 *
534 * @attr ref android.R.styleable#Switch_splitTrack
535 */
536 public boolean getSplitTrack() {
537 return mSplitTrack;
538 }
539
540 /**
Chet Haase150176d2011-08-26 09:54:06 -0700541 * Returns the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800542 *
543 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800544 */
545 public CharSequence getTextOn() {
546 return mTextOn;
547 }
548
549 /**
Chet Haase150176d2011-08-26 09:54:06 -0700550 * Sets the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800551 *
552 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800553 */
554 public void setTextOn(CharSequence textOn) {
555 mTextOn = textOn;
556 requestLayout();
557 }
558
559 /**
Chet Haase150176d2011-08-26 09:54:06 -0700560 * Returns the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800561 *
562 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800563 */
564 public CharSequence getTextOff() {
565 return mTextOff;
566 }
567
568 /**
Chet Haase150176d2011-08-26 09:54:06 -0700569 * Sets the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800570 *
571 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800572 */
573 public void setTextOff(CharSequence textOff) {
574 mTextOff = textOff;
575 requestLayout();
576 }
577
Alan Viverette2a37cf8d2014-06-19 17:09:10 -0700578 /**
579 * Sets whether the on/off text should be displayed.
580 *
581 * @param showText {@code true} to display on/off text
Alan Viverette0c0dde72014-07-30 13:29:39 -0700582 * @attr ref android.R.styleable#Switch_showText
Alan Viverette2a37cf8d2014-06-19 17:09:10 -0700583 */
584 public void setShowText(boolean showText) {
585 if (mShowText != showText) {
586 mShowText = showText;
587 requestLayout();
588 }
589 }
590
591 /**
592 * @return whether the on/off text should be displayed
Alan Viverette0c0dde72014-07-30 13:29:39 -0700593 * @attr ref android.R.styleable#Switch_showText
Alan Viverette2a37cf8d2014-06-19 17:09:10 -0700594 */
595 public boolean getShowText() {
596 return mShowText;
597 }
598
Adam Powell12190b32010-11-28 19:07:53 -0800599 @Override
600 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Alan Viverette2a37cf8d2014-06-19 17:09:10 -0700601 if (mShowText) {
602 if (mOnLayout == null) {
603 mOnLayout = makeLayout(mTextOn);
604 }
Alan Viverette5876ff42014-03-03 17:40:46 -0800605
Alan Viverette2a37cf8d2014-06-19 17:09:10 -0700606 if (mOffLayout == null) {
607 mOffLayout = makeLayout(mTextOff);
608 }
Adam Powell12190b32010-11-28 19:07:53 -0800609 }
610
Alan Viverette9b38f6c2014-07-30 02:39:07 +0000611 final Rect padding = mTempRect;
Alan Viverette0c0dde72014-07-30 13:29:39 -0700612 final int thumbWidth;
613 final int thumbHeight;
614 if (mThumbDrawable != null) {
615 // Cached thumb width does not include padding.
616 mThumbDrawable.getPadding(padding);
617 thumbWidth = mThumbDrawable.getIntrinsicWidth() - padding.left - padding.right;
618 thumbHeight = mThumbDrawable.getIntrinsicHeight();
619 } else {
620 thumbWidth = 0;
621 thumbHeight = 0;
622 }
623
624 final int maxTextWidth;
625 if (mShowText) {
626 maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
627 + mThumbTextPadding * 2;
628 } else {
629 maxTextWidth = 0;
630 }
631
632 mThumbWidth = Math.max(maxTextWidth, thumbWidth);
633
634 final int trackHeight;
Alan Viverette4d065a02014-07-11 15:28:38 -0700635 if (mTrackDrawable != null) {
636 mTrackDrawable.getPadding(padding);
637 trackHeight = mTrackDrawable.getIntrinsicHeight();
638 } else {
639 padding.setEmpty();
640 trackHeight = 0;
641 }
642
Alan Viverette0c0dde72014-07-30 13:29:39 -0700643 // Adjust left and right padding to ensure there's enough room for the
644 // thumb's padding (when present).
645 int paddingLeft = padding.left;
646 int paddingRight = padding.right;
Alan Viverette4d065a02014-07-11 15:28:38 -0700647 if (mThumbDrawable != null) {
Alan Viverette0c0dde72014-07-30 13:29:39 -0700648 final Insets inset = mThumbDrawable.getOpticalInsets();
649 paddingLeft = Math.max(paddingLeft, inset.left);
650 paddingRight = Math.max(paddingRight, inset.right);
Alan Viverette4d065a02014-07-11 15:28:38 -0700651 }
Alan Viverette5876ff42014-03-03 17:40:46 -0800652
Adam Powell12190b32010-11-28 19:07:53 -0800653 final int switchWidth = Math.max(mSwitchMinWidth,
Alan Viverette0c0dde72014-07-30 13:29:39 -0700654 2 * mThumbWidth + paddingLeft + paddingRight);
Alan Viverette4d065a02014-07-11 15:28:38 -0700655 final int switchHeight = Math.max(trackHeight, thumbHeight);
Adam Powell12190b32010-11-28 19:07:53 -0800656 mSwitchWidth = switchWidth;
657 mSwitchHeight = switchHeight;
658
659 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Alan Viverette4d065a02014-07-11 15:28:38 -0700660
Adam Powell12190b32010-11-28 19:07:53 -0800661 final int measuredHeight = getMeasuredHeight();
662 if (measuredHeight < switchHeight) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800663 setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
Adam Powell12190b32010-11-28 19:07:53 -0800664 }
665 }
666
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700667 @Override
668 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
669 super.onPopulateAccessibilityEvent(event);
Alan Viverette2a37cf8d2014-06-19 17:09:10 -0700670
671 final CharSequence text = isChecked() ? mTextOn : mTextOff;
672 if (text != null) {
673 event.getText().add(text);
Svetoslav Ganov76502592011-07-29 10:44:59 -0700674 }
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700675 }
676
Adam Powell12190b32010-11-28 19:07:53 -0800677 private Layout makeLayout(CharSequence text) {
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400678 final CharSequence transformed = (mSwitchTransformationMethod != null)
679 ? mSwitchTransformationMethod.getTransformation(text, this)
680 : text;
681
682 return new StaticLayout(transformed, mTextPaint,
683 (int) Math.ceil(Layout.getDesiredWidth(transformed, mTextPaint)),
Adam Powell12190b32010-11-28 19:07:53 -0800684 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
685 }
686
687 /**
688 * @return true if (x, y) is within the target area of the switch thumb
689 */
690 private boolean hitThumb(float x, float y) {
Alan Viverettecc2688d2013-09-17 17:00:12 -0700691 // Relies on mTempRect, MUST be called first!
692 final int thumbOffset = getThumbOffset();
693
Adam Powell12190b32010-11-28 19:07:53 -0800694 mThumbDrawable.getPadding(mTempRect);
695 final int thumbTop = mSwitchTop - mTouchSlop;
Alan Viverettecc2688d2013-09-17 17:00:12 -0700696 final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop;
Adam Powell12190b32010-11-28 19:07:53 -0800697 final int thumbRight = thumbLeft + mThumbWidth +
698 mTempRect.left + mTempRect.right + mTouchSlop;
699 final int thumbBottom = mSwitchBottom + mTouchSlop;
700 return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
701 }
702
703 @Override
704 public boolean onTouchEvent(MotionEvent ev) {
705 mVelocityTracker.addMovement(ev);
706 final int action = ev.getActionMasked();
707 switch (action) {
708 case MotionEvent.ACTION_DOWN: {
709 final float x = ev.getX();
710 final float y = ev.getY();
Gilles Debunnec2ab0d62011-06-13 12:52:48 -0700711 if (isEnabled() && hitThumb(x, y)) {
Adam Powell12190b32010-11-28 19:07:53 -0800712 mTouchMode = TOUCH_MODE_DOWN;
713 mTouchX = x;
714 mTouchY = y;
715 }
716 break;
717 }
718
719 case MotionEvent.ACTION_MOVE: {
720 switch (mTouchMode) {
721 case TOUCH_MODE_IDLE:
722 // Didn't target the thumb, treat normally.
723 break;
724
725 case TOUCH_MODE_DOWN: {
726 final float x = ev.getX();
727 final float y = ev.getY();
728 if (Math.abs(x - mTouchX) > mTouchSlop ||
729 Math.abs(y - mTouchY) > mTouchSlop) {
730 mTouchMode = TOUCH_MODE_DRAGGING;
731 getParent().requestDisallowInterceptTouchEvent(true);
732 mTouchX = x;
733 mTouchY = y;
734 return true;
735 }
736 break;
737 }
738
739 case TOUCH_MODE_DRAGGING: {
740 final float x = ev.getX();
Alan Viverettecc2688d2013-09-17 17:00:12 -0700741 final int thumbScrollRange = getThumbScrollRange();
742 final float thumbScrollOffset = x - mTouchX;
743 float dPos;
744 if (thumbScrollRange != 0) {
745 dPos = thumbScrollOffset / thumbScrollRange;
746 } else {
747 // If the thumb scroll range is empty, just use the
748 // movement direction to snap on or off.
749 dPos = thumbScrollOffset > 0 ? 1 : -1;
750 }
751 if (isLayoutRtl()) {
752 dPos = -dPos;
753 }
754 final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1);
Adam Powell12190b32010-11-28 19:07:53 -0800755 if (newPos != mThumbPosition) {
Adam Powell12190b32010-11-28 19:07:53 -0800756 mTouchX = x;
Alan Viverettecc2688d2013-09-17 17:00:12 -0700757 setThumbPosition(newPos);
Adam Powell12190b32010-11-28 19:07:53 -0800758 }
759 return true;
760 }
761 }
762 break;
763 }
764
765 case MotionEvent.ACTION_UP:
766 case MotionEvent.ACTION_CANCEL: {
767 if (mTouchMode == TOUCH_MODE_DRAGGING) {
768 stopDrag(ev);
Alan Viverettead2f8e32014-05-16 13:28:33 -0700769 // Allow super class to handle pressed state, etc.
770 super.onTouchEvent(ev);
Adam Powell12190b32010-11-28 19:07:53 -0800771 return true;
772 }
773 mTouchMode = TOUCH_MODE_IDLE;
774 mVelocityTracker.clear();
775 break;
776 }
777 }
778
779 return super.onTouchEvent(ev);
780 }
781
782 private void cancelSuperTouch(MotionEvent ev) {
783 MotionEvent cancel = MotionEvent.obtain(ev);
784 cancel.setAction(MotionEvent.ACTION_CANCEL);
785 super.onTouchEvent(cancel);
786 cancel.recycle();
787 }
788
789 /**
790 * Called from onTouchEvent to end a drag operation.
791 *
792 * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
793 */
794 private void stopDrag(MotionEvent ev) {
795 mTouchMode = TOUCH_MODE_IDLE;
Adam Powell12190b32010-11-28 19:07:53 -0800796
Alan Viverette86453ff2013-09-26 14:46:08 -0700797 // Commit the change if the event is up and not canceled and the switch
798 // has not been disabled during the drag.
799 final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
800 final boolean newState;
Adam Powell12190b32010-11-28 19:07:53 -0800801 if (commitChange) {
Adam Powell12190b32010-11-28 19:07:53 -0800802 mVelocityTracker.computeCurrentVelocity(1000);
Alan Viverette86453ff2013-09-26 14:46:08 -0700803 final float xvel = mVelocityTracker.getXVelocity();
Adam Powell12190b32010-11-28 19:07:53 -0800804 if (Math.abs(xvel) > mMinFlingVelocity) {
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700805 newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0);
Adam Powell12190b32010-11-28 19:07:53 -0800806 } else {
807 newState = getTargetCheckedState();
808 }
Adam Powell12190b32010-11-28 19:07:53 -0800809 } else {
Alan Viverette86453ff2013-09-26 14:46:08 -0700810 newState = isChecked();
Adam Powell12190b32010-11-28 19:07:53 -0800811 }
Alan Viverette86453ff2013-09-26 14:46:08 -0700812
813 setChecked(newState);
814 cancelSuperTouch(ev);
Adam Powell12190b32010-11-28 19:07:53 -0800815 }
816
817 private void animateThumbToCheckedState(boolean newCheckedState) {
Alan Viverettecc2688d2013-09-17 17:00:12 -0700818 final float targetPosition = newCheckedState ? 1 : 0;
819 mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition);
820 mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION);
821 mPositionAnimator.setAutoCancel(true);
822 mPositionAnimator.start();
823 }
824
825 private void cancelPositionAnimator() {
826 if (mPositionAnimator != null) {
827 mPositionAnimator.cancel();
828 }
Adam Powell12190b32010-11-28 19:07:53 -0800829 }
830
831 private boolean getTargetCheckedState() {
Alan Viverettecc2688d2013-09-17 17:00:12 -0700832 return mThumbPosition > 0.5f;
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700833 }
834
Alan Viverettecc2688d2013-09-17 17:00:12 -0700835 /**
836 * Sets the thumb position as a decimal value between 0 (off) and 1 (on).
837 *
838 * @param position new position between [0,1]
839 */
840 private void setThumbPosition(float position) {
841 mThumbPosition = position;
842 invalidate();
843 }
844
845 @Override
846 public void toggle() {
Alan Viverette86453ff2013-09-26 14:46:08 -0700847 setChecked(!isChecked());
Adam Powell12190b32010-11-28 19:07:53 -0800848 }
849
850 @Override
851 public void setChecked(boolean checked) {
852 super.setChecked(checked);
Alan Viverettecc2688d2013-09-17 17:00:12 -0700853
Alan Viverette467d6292014-08-12 15:13:19 -0700854 // Calling the super method may result in setChecked() getting called
855 // recursively with a different value, so load the REAL value...
856 checked = isChecked();
857
Alan Viverette86453ff2013-09-26 14:46:08 -0700858 if (isAttachedToWindow() && isLaidOut()) {
859 animateThumbToCheckedState(checked);
860 } else {
861 // Immediately move the thumb to the new position.
862 cancelPositionAnimator();
863 setThumbPosition(checked ? 1 : 0);
864 }
Adam Powell12190b32010-11-28 19:07:53 -0800865 }
866
867 @Override
868 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
869 super.onLayout(changed, left, top, right, bottom);
870
Alan Viverette0c0dde72014-07-30 13:29:39 -0700871 int opticalInsetLeft = 0;
872 int opticalInsetRight = 0;
873 if (mThumbDrawable != null) {
874 final Rect trackPadding = mTempRect;
875 if (mTrackDrawable != null) {
876 mTrackDrawable.getPadding(trackPadding);
877 } else {
878 trackPadding.setEmpty();
879 }
Fabrice Di Meglio28efba32012-06-01 16:52:31 -0700880
Alan Viverette0c0dde72014-07-30 13:29:39 -0700881 final Insets insets = mThumbDrawable.getOpticalInsets();
882 opticalInsetLeft = Math.max(0, insets.left - trackPadding.left);
883 opticalInsetRight = Math.max(0, insets.right - trackPadding.right);
Alan Viverette8bb39902014-07-29 17:22:30 -0700884 }
885
Alan Viverette0c0dde72014-07-30 13:29:39 -0700886 final int switchRight;
887 final int switchLeft;
888 if (isLayoutRtl()) {
889 switchLeft = getPaddingLeft() + opticalInsetLeft;
890 switchRight = switchLeft + mSwitchWidth - opticalInsetLeft - opticalInsetRight;
891 } else {
892 switchRight = getWidth() - getPaddingRight() - opticalInsetRight;
893 switchLeft = switchRight - mSwitchWidth + opticalInsetLeft + opticalInsetRight;
894 }
895
896 final int switchTop;
897 final int switchBottom;
Adam Powell12190b32010-11-28 19:07:53 -0800898 switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
899 default:
900 case Gravity.TOP:
901 switchTop = getPaddingTop();
902 switchBottom = switchTop + mSwitchHeight;
903 break;
904
905 case Gravity.CENTER_VERTICAL:
906 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
907 mSwitchHeight / 2;
908 switchBottom = switchTop + mSwitchHeight;
909 break;
910
911 case Gravity.BOTTOM:
912 switchBottom = getHeight() - getPaddingBottom();
913 switchTop = switchBottom - mSwitchHeight;
914 break;
915 }
916
917 mSwitchLeft = switchLeft;
918 mSwitchTop = switchTop;
919 mSwitchBottom = switchBottom;
920 mSwitchRight = switchRight;
921 }
922
923 @Override
Alan Viverettead2f8e32014-05-16 13:28:33 -0700924 public void draw(Canvas c) {
Alan Viverette4d065a02014-07-11 15:28:38 -0700925 final Rect padding = mTempRect;
Alan Viverette5876ff42014-03-03 17:40:46 -0800926 final int switchLeft = mSwitchLeft;
927 final int switchTop = mSwitchTop;
928 final int switchRight = mSwitchRight;
929 final int switchBottom = mSwitchBottom;
Alan Viverette0c0dde72014-07-30 13:29:39 -0700930
931 int thumbInitialLeft = switchLeft + getThumbOffset();
932
933 final Insets thumbInsets;
934 if (mThumbDrawable != null) {
935 thumbInsets = mThumbDrawable.getOpticalInsets();
936 } else {
937 thumbInsets = Insets.NONE;
Alan Viverette8bb39902014-07-29 17:22:30 -0700938 }
Alan Viverettecc2688d2013-09-17 17:00:12 -0700939
Alan Viverette0c0dde72014-07-30 13:29:39 -0700940 // Layout the track.
941 if (mTrackDrawable != null) {
942 mTrackDrawable.getPadding(padding);
Alan Viverette9b38f6c2014-07-30 02:39:07 +0000943
Alan Viverette0c0dde72014-07-30 13:29:39 -0700944 // Adjust thumb position for track padding.
945 thumbInitialLeft += padding.left;
946
947 // If necessary, offset by the optical insets of the thumb asset.
948 int trackLeft = switchLeft;
949 int trackTop = switchTop;
950 int trackRight = switchRight;
951 int trackBottom = switchBottom;
952 if (thumbInsets != Insets.NONE) {
953 if (thumbInsets.left > padding.left) {
954 trackLeft += thumbInsets.left - padding.left;
955 }
956 if (thumbInsets.top > padding.top) {
957 trackTop += thumbInsets.top - padding.top;
958 }
959 if (thumbInsets.right > padding.right) {
960 trackRight -= thumbInsets.right - padding.right;
961 }
962 if (thumbInsets.bottom > padding.bottom) {
963 trackBottom -= thumbInsets.bottom - padding.bottom;
964 }
965 }
966 mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom);
967 }
Alan Viverette9b38f6c2014-07-30 02:39:07 +0000968
Alan Viverette661e6362014-05-12 10:55:37 -0700969 // Layout the thumb.
Alan Viverette4d065a02014-07-11 15:28:38 -0700970 if (mThumbDrawable != null) {
971 mThumbDrawable.getPadding(padding);
Alan Viverette0c0dde72014-07-30 13:29:39 -0700972
973 final int thumbLeft = thumbInitialLeft - padding.left;
974 final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right;
Alan Viverette4d065a02014-07-11 15:28:38 -0700975 mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
Alan Viverette61956602014-04-22 19:07:06 -0700976
Alan Viverette4d065a02014-07-11 15:28:38 -0700977 final Drawable background = getBackground();
978 if (background != null) {
979 background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
980 }
Alan Viverette61956602014-04-22 19:07:06 -0700981 }
982
Alan Viverettead2f8e32014-05-16 13:28:33 -0700983 // Draw the background.
984 super.draw(c);
985 }
986
987 @Override
988 protected void onDraw(Canvas canvas) {
Alan Viverette61956602014-04-22 19:07:06 -0700989 super.onDraw(canvas);
990
Alan Viverette4d065a02014-07-11 15:28:38 -0700991 final Rect padding = mTempRect;
Alan Viverettead2f8e32014-05-16 13:28:33 -0700992 final Drawable trackDrawable = mTrackDrawable;
Alan Viverette4d065a02014-07-11 15:28:38 -0700993 if (trackDrawable != null) {
994 trackDrawable.getPadding(padding);
995 } else {
996 padding.setEmpty();
997 }
Alan Viverettead2f8e32014-05-16 13:28:33 -0700998
999 final int switchTop = mSwitchTop;
1000 final int switchBottom = mSwitchBottom;
Alan Viverette4d065a02014-07-11 15:28:38 -07001001 final int switchInnerTop = switchTop + padding.top;
Alan Viverette4d065a02014-07-11 15:28:38 -07001002 final int switchInnerBottom = switchBottom - padding.bottom;
Alan Viverettead2f8e32014-05-16 13:28:33 -07001003
Alan Viverette4d065a02014-07-11 15:28:38 -07001004 final Drawable thumbDrawable = mThumbDrawable;
1005 if (trackDrawable != null) {
1006 if (mSplitTrack && thumbDrawable != null) {
1007 final Insets insets = thumbDrawable.getOpticalInsets();
1008 thumbDrawable.copyBounds(padding);
1009 padding.left += insets.left;
1010 padding.right -= insets.right;
Alan Viverette661e6362014-05-12 10:55:37 -07001011
Alan Viverette4d065a02014-07-11 15:28:38 -07001012 final int saveCount = canvas.save();
1013 canvas.clipRect(padding, Op.DIFFERENCE);
1014 trackDrawable.draw(canvas);
1015 canvas.restoreToCount(saveCount);
1016 } else {
1017 trackDrawable.draw(canvas);
1018 }
Alan Viverette661e6362014-05-12 10:55:37 -07001019 }
Alan Viverette61956602014-04-22 19:07:06 -07001020
1021 final int saveCount = canvas.save();
Alan Viverette4d065a02014-07-11 15:28:38 -07001022
1023 if (thumbDrawable != null) {
Alan Viverette4d065a02014-07-11 15:28:38 -07001024 thumbDrawable.draw(canvas);
1025 }
Adam Powell12190b32010-11-28 19:07:53 -08001026
Alan Viverette5876ff42014-03-03 17:40:46 -08001027 final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
Fabrice Di Megliobe06e322012-09-11 17:42:45 -07001028 if (switchText != null) {
Alan Viverette661e6362014-05-12 10:55:37 -07001029 final int drawableState[] = getDrawableState();
1030 if (mTextColors != null) {
1031 mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
1032 }
1033 mTextPaint.drawableState = drawableState;
1034
Alan Viverette4d065a02014-07-11 15:28:38 -07001035 final int cX;
1036 if (thumbDrawable != null) {
1037 final Rect bounds = thumbDrawable.getBounds();
1038 cX = bounds.left + bounds.right;
1039 } else {
Alan Viverettedec17292014-07-12 00:26:36 -07001040 cX = getWidth();
Alan Viverette4d065a02014-07-11 15:28:38 -07001041 }
1042
1043 final int left = cX / 2 - switchText.getWidth() / 2;
Alan Viverette5876ff42014-03-03 17:40:46 -08001044 final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
1045 canvas.translate(left, top);
Fabrice Di Megliobe06e322012-09-11 17:42:45 -07001046 switchText.draw(canvas);
1047 }
Adam Powell12190b32010-11-28 19:07:53 -08001048
Alan Viverette5876ff42014-03-03 17:40:46 -08001049 canvas.restoreToCount(saveCount);
Adam Powell12190b32010-11-28 19:07:53 -08001050 }
1051
1052 @Override
Fabrice Di Meglio28efba32012-06-01 16:52:31 -07001053 public int getCompoundPaddingLeft() {
1054 if (!isLayoutRtl()) {
1055 return super.getCompoundPaddingLeft();
1056 }
1057 int padding = super.getCompoundPaddingLeft() + mSwitchWidth;
1058 if (!TextUtils.isEmpty(getText())) {
1059 padding += mSwitchPadding;
1060 }
1061 return padding;
1062 }
1063
1064 @Override
Adam Powell12190b32010-11-28 19:07:53 -08001065 public int getCompoundPaddingRight() {
Fabrice Di Meglio28efba32012-06-01 16:52:31 -07001066 if (isLayoutRtl()) {
1067 return super.getCompoundPaddingRight();
1068 }
Adam Powell12190b32010-11-28 19:07:53 -08001069 int padding = super.getCompoundPaddingRight() + mSwitchWidth;
1070 if (!TextUtils.isEmpty(getText())) {
1071 padding += mSwitchPadding;
1072 }
1073 return padding;
1074 }
1075
Alan Viverettecc2688d2013-09-17 17:00:12 -07001076 /**
1077 * Translates thumb position to offset according to current RTL setting and
Alan Viverette0c0dde72014-07-30 13:29:39 -07001078 * thumb scroll range. Accounts for both track and thumb padding.
Alan Viverettecc2688d2013-09-17 17:00:12 -07001079 *
1080 * @return thumb offset
1081 */
1082 private int getThumbOffset() {
1083 final float thumbPosition;
1084 if (isLayoutRtl()) {
1085 thumbPosition = 1 - mThumbPosition;
1086 } else {
1087 thumbPosition = mThumbPosition;
1088 }
1089 return (int) (thumbPosition * getThumbScrollRange() + 0.5f);
1090 }
1091
Adam Powell12190b32010-11-28 19:07:53 -08001092 private int getThumbScrollRange() {
Alan Viverette4d065a02014-07-11 15:28:38 -07001093 if (mTrackDrawable != null) {
Alan Viverette0c0dde72014-07-30 13:29:39 -07001094 final Rect padding = mTempRect;
1095 mTrackDrawable.getPadding(padding);
1096
1097 final Insets insets;
1098 if (mThumbDrawable != null) {
1099 insets = mThumbDrawable.getOpticalInsets();
1100 } else {
1101 insets = Insets.NONE;
1102 }
1103
1104 return mSwitchWidth - mThumbWidth - padding.left - padding.right
1105 - insets.left - insets.right;
Alan Viverette4d065a02014-07-11 15:28:38 -07001106 } else {
Adam Powell12190b32010-11-28 19:07:53 -08001107 return 0;
1108 }
Adam Powell12190b32010-11-28 19:07:53 -08001109 }
1110
1111 @Override
1112 protected int[] onCreateDrawableState(int extraSpace) {
1113 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
1114 if (isChecked()) {
1115 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
1116 }
1117 return drawableState;
1118 }
1119
1120 @Override
1121 protected void drawableStateChanged() {
1122 super.drawableStateChanged();
1123
Alan Viverette661e6362014-05-12 10:55:37 -07001124 final int[] myDrawableState = getDrawableState();
Adam Powell12190b32010-11-28 19:07:53 -08001125
Alan Viverette2356c5e2014-05-22 22:43:59 -07001126 if (mThumbDrawable != null) {
1127 mThumbDrawable.setState(myDrawableState);
Alan Viverette661e6362014-05-12 10:55:37 -07001128 }
1129
1130 if (mTrackDrawable != null) {
1131 mTrackDrawable.setState(myDrawableState);
1132 }
Adam Powell12190b32010-11-28 19:07:53 -08001133
1134 invalidate();
1135 }
1136
Alan Viverettecebc6ba2014-06-13 15:52:13 -07001137 @Override
Alan Viverette8de14942014-06-18 18:05:15 -07001138 public void drawableHotspotChanged(float x, float y) {
1139 super.drawableHotspotChanged(x, y);
Alan Viverettecebc6ba2014-06-13 15:52:13 -07001140
1141 if (mThumbDrawable != null) {
1142 mThumbDrawable.setHotspot(x, y);
1143 }
1144
1145 if (mTrackDrawable != null) {
1146 mTrackDrawable.setHotspot(x, y);
1147 }
1148 }
1149
Adam Powell12190b32010-11-28 19:07:53 -08001150 @Override
1151 protected boolean verifyDrawable(Drawable who) {
1152 return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
1153 }
1154
1155 @Override
1156 public void jumpDrawablesToCurrentState() {
1157 super.jumpDrawablesToCurrentState();
Alan Viverette4d065a02014-07-11 15:28:38 -07001158
1159 if (mThumbDrawable != null) {
1160 mThumbDrawable.jumpToCurrentState();
1161 }
1162
1163 if (mTrackDrawable != null) {
1164 mTrackDrawable.jumpToCurrentState();
1165 }
1166
1167 if (mPositionAnimator != null && mPositionAnimator.isRunning()) {
1168 mPositionAnimator.end();
1169 mPositionAnimator = null;
1170 }
Adam Powell12190b32010-11-28 19:07:53 -08001171 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001172
1173 @Override
1174 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1175 super.onInitializeAccessibilityEvent(event);
1176 event.setClassName(Switch.class.getName());
1177 }
1178
1179 @Override
1180 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1181 super.onInitializeAccessibilityNodeInfo(info);
1182 info.setClassName(Switch.class.getName());
Svetoslav Ganov78bcc152012-04-12 17:17:19 -07001183 CharSequence switchText = isChecked() ? mTextOn : mTextOff;
1184 if (!TextUtils.isEmpty(switchText)) {
1185 CharSequence oldText = info.getText();
1186 if (TextUtils.isEmpty(oldText)) {
1187 info.setText(switchText);
1188 } else {
1189 StringBuilder newText = new StringBuilder();
1190 newText.append(oldText).append(' ').append(switchText);
1191 info.setText(newText);
1192 }
1193 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -08001194 }
Alan Viverettecc2688d2013-09-17 17:00:12 -07001195
1196 private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") {
1197 @Override
1198 public Float get(Switch object) {
1199 return object.mThumbPosition;
1200 }
1201
1202 @Override
1203 public void setValue(Switch object, float value) {
1204 object.setThumbPosition(value);
1205 }
1206 };
Adam Powell12190b32010-11-28 19:07:53 -08001207}