blob: 471f259cc95417a48f284486487d0570f897e6eb [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 *
Adam Powell12190b32010-11-28 19:07:53 -080056 */
57public class Switch extends CompoundButton {
58 private static final int TOUCH_MODE_IDLE = 0;
59 private static final int TOUCH_MODE_DOWN = 1;
60 private static final int TOUCH_MODE_DRAGGING = 2;
61
62 // Enum for the "typeface" XML parameter.
63 private static final int SANS = 1;
64 private static final int SERIF = 2;
65 private static final int MONOSPACE = 3;
66
67 private Drawable mThumbDrawable;
68 private Drawable mTrackDrawable;
69 private int mThumbTextPadding;
70 private int mSwitchMinWidth;
71 private int mSwitchPadding;
72 private CharSequence mTextOn;
73 private CharSequence mTextOff;
74
75 private int mTouchMode;
76 private int mTouchSlop;
77 private float mTouchX;
78 private float mTouchY;
79 private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
80 private int mMinFlingVelocity;
81
82 private float mThumbPosition;
83 private int mSwitchWidth;
84 private int mSwitchHeight;
85 private int mThumbWidth; // Does not include padding
86
87 private int mSwitchLeft;
88 private int mSwitchTop;
89 private int mSwitchRight;
90 private int mSwitchBottom;
91
92 private TextPaint mTextPaint;
93 private ColorStateList mTextColors;
94 private Layout mOnLayout;
95 private Layout mOffLayout;
Daniel Sandler4c3308d2012-04-19 11:04:39 -040096 private TransformationMethod2 mSwitchTransformationMethod;
Adam Powell12190b32010-11-28 19:07:53 -080097
Adam Powellbe0a4532010-11-29 17:47:48 -080098 @SuppressWarnings("hiding")
Adam Powell12190b32010-11-28 19:07:53 -080099 private final Rect mTempRect = new Rect();
100
101 private static final int[] CHECKED_STATE_SET = {
102 R.attr.state_checked
103 };
104
105 /**
106 * Construct a new Switch with default styling.
107 *
108 * @param context The Context that will determine this widget's theming.
109 */
110 public Switch(Context context) {
111 this(context, null);
112 }
113
114 /**
115 * Construct a new Switch with default styling, overriding specific style
116 * attributes as requested.
117 *
118 * @param context The Context that will determine this widget's theming.
119 * @param attrs Specification of attributes that should deviate from default styling.
120 */
121 public Switch(Context context, AttributeSet attrs) {
122 this(context, attrs, com.android.internal.R.attr.switchStyle);
123 }
124
125 /**
126 * Construct a new Switch with a default style determined by the given theme attribute,
127 * overriding specific style 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 the default styling.
131 * @param defStyle An attribute ID within the active theme containing a reference to the
132 * default style for this widget. e.g. android.R.attr.switchStyle.
133 */
134 public Switch(Context context, AttributeSet attrs, int defStyle) {
135 super(context, attrs, defStyle);
136
137 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
138 Resources res = getResources();
139 mTextPaint.density = res.getDisplayMetrics().density;
140 mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
141
142 TypedArray a = context.obtainStyledAttributes(attrs,
143 com.android.internal.R.styleable.Switch, defStyle, 0);
144
Chet Haase150176d2011-08-26 09:54:06 -0700145 mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
146 mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
Adam Powell12190b32010-11-28 19:07:53 -0800147 mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
148 mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
149 mThumbTextPadding = a.getDimensionPixelSize(
150 com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
151 mSwitchMinWidth = a.getDimensionPixelSize(
152 com.android.internal.R.styleable.Switch_switchMinWidth, 0);
153 mSwitchPadding = a.getDimensionPixelSize(
154 com.android.internal.R.styleable.Switch_switchPadding, 0);
155
156 int appearance = a.getResourceId(
157 com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
158 if (appearance != 0) {
Chet Haase150176d2011-08-26 09:54:06 -0700159 setSwitchTextAppearance(context, appearance);
Adam Powell12190b32010-11-28 19:07:53 -0800160 }
161 a.recycle();
162
163 ViewConfiguration config = ViewConfiguration.get(context);
164 mTouchSlop = config.getScaledTouchSlop();
165 mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
166
167 // Refresh display with current params
Gilles Debunnee724ee42011-08-31 11:20:27 -0700168 refreshDrawableState();
Adam Powell12190b32010-11-28 19:07:53 -0800169 setChecked(isChecked());
170 }
171
172 /**
173 * Sets the switch text color, size, style, hint color, and highlight color
174 * from the specified TextAppearance resource.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800175 *
176 * @attr ref android.R.styleable#Switch_switchTextAppearance
Adam Powell12190b32010-11-28 19:07:53 -0800177 */
Chet Haase150176d2011-08-26 09:54:06 -0700178 public void setSwitchTextAppearance(Context context, int resid) {
Adam Powell12190b32010-11-28 19:07:53 -0800179 TypedArray appearance =
Chet Haase150176d2011-08-26 09:54:06 -0700180 context.obtainStyledAttributes(resid,
Adam Powell12190b32010-11-28 19:07:53 -0800181 com.android.internal.R.styleable.TextAppearance);
182
183 ColorStateList colors;
184 int ts;
185
186 colors = appearance.getColorStateList(com.android.internal.R.styleable.
187 TextAppearance_textColor);
188 if (colors != null) {
189 mTextColors = colors;
Chet Haase150176d2011-08-26 09:54:06 -0700190 } else {
191 // If no color set in TextAppearance, default to the view's textColor
192 mTextColors = getTextColors();
Adam Powell12190b32010-11-28 19:07:53 -0800193 }
194
195 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
196 TextAppearance_textSize, 0);
197 if (ts != 0) {
198 if (ts != mTextPaint.getTextSize()) {
199 mTextPaint.setTextSize(ts);
200 requestLayout();
201 }
202 }
203
204 int typefaceIndex, styleIndex;
205
206 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
207 TextAppearance_typeface, -1);
208 styleIndex = appearance.getInt(com.android.internal.R.styleable.
209 TextAppearance_textStyle, -1);
210
211 setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
212
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400213 boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable.
214 TextAppearance_textAllCaps, false);
215 if (allCaps) {
216 mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
217 mSwitchTransformationMethod.setLengthChangesAllowed(true);
218 } else {
219 mSwitchTransformationMethod = null;
220 }
221
Adam Powell12190b32010-11-28 19:07:53 -0800222 appearance.recycle();
223 }
224
225 private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
226 Typeface tf = null;
227 switch (typefaceIndex) {
228 case SANS:
229 tf = Typeface.SANS_SERIF;
230 break;
231
232 case SERIF:
233 tf = Typeface.SERIF;
234 break;
235
236 case MONOSPACE:
237 tf = Typeface.MONOSPACE;
238 break;
239 }
240
241 setSwitchTypeface(tf, styleIndex);
242 }
243
244 /**
245 * Sets the typeface and style in which the text should be displayed on the
246 * switch, and turns on the fake bold and italic bits in the Paint if the
247 * Typeface that you provided does not have all the bits in the
248 * style that you specified.
249 */
250 public void setSwitchTypeface(Typeface tf, int style) {
251 if (style > 0) {
252 if (tf == null) {
253 tf = Typeface.defaultFromStyle(style);
254 } else {
255 tf = Typeface.create(tf, style);
256 }
257
258 setSwitchTypeface(tf);
259 // now compute what (if any) algorithmic styling is needed
260 int typefaceStyle = tf != null ? tf.getStyle() : 0;
261 int need = style & ~typefaceStyle;
262 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
263 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
264 } else {
Victoria Leaseaa0980a2012-06-11 14:46:04 -0700265 mTextPaint.setFakeBoldText(false);
Adam Powell12190b32010-11-28 19:07:53 -0800266 mTextPaint.setTextSkewX(0);
267 setSwitchTypeface(tf);
268 }
269 }
270
271 /**
Chet Haase150176d2011-08-26 09:54:06 -0700272 * Sets the typeface in which the text should be displayed on the switch.
Adam Powell12190b32010-11-28 19:07:53 -0800273 * Note that not all Typeface families actually have bold and italic
274 * variants, so you may need to use
275 * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
276 * that you actually want.
277 *
278 * @attr ref android.R.styleable#TextView_typeface
279 * @attr ref android.R.styleable#TextView_textStyle
280 */
281 public void setSwitchTypeface(Typeface tf) {
282 if (mTextPaint.getTypeface() != tf) {
283 mTextPaint.setTypeface(tf);
284
285 requestLayout();
286 invalidate();
287 }
288 }
289
290 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800291 * Set the amount of horizontal padding between the switch and the associated text.
292 *
293 * @param pixels Amount of padding in pixels
294 *
295 * @attr ref android.R.styleable#Switch_switchPadding
296 */
297 public void setSwitchPadding(int pixels) {
298 mSwitchPadding = pixels;
299 requestLayout();
300 }
301
302 /**
303 * Get the amount of horizontal padding between the switch and the associated text.
304 *
305 * @return Amount of padding in pixels
306 *
307 * @attr ref android.R.styleable#Switch_switchPadding
308 */
309 public int getSwitchPadding() {
310 return mSwitchPadding;
311 }
312
313 /**
314 * Set the minimum width of the switch in pixels. The switch's width will be the maximum
315 * of this value and its measured width as determined by the switch drawables and text used.
316 *
317 * @param pixels Minimum width of the switch in pixels
318 *
319 * @attr ref android.R.styleable#Switch_switchMinWidth
320 */
321 public void setSwitchMinWidth(int pixels) {
322 mSwitchMinWidth = pixels;
323 requestLayout();
324 }
325
326 /**
327 * Get the minimum width of the switch in pixels. The switch's width will be the maximum
328 * of this value and its measured width as determined by the switch drawables and text used.
329 *
330 * @return Minimum width of the switch in pixels
331 *
332 * @attr ref android.R.styleable#Switch_switchMinWidth
333 */
334 public int getSwitchMinWidth() {
335 return mSwitchMinWidth;
336 }
337
338 /**
339 * Set the horizontal padding around the text drawn on the switch itself.
340 *
341 * @param pixels Horizontal padding for switch thumb text in pixels
342 *
343 * @attr ref android.R.styleable#Switch_thumbTextPadding
344 */
345 public void setThumbTextPadding(int pixels) {
346 mThumbTextPadding = pixels;
347 requestLayout();
348 }
349
350 /**
351 * Get the horizontal padding around the text drawn on the switch itself.
352 *
353 * @return Horizontal padding for switch thumb text in pixels
354 *
355 * @attr ref android.R.styleable#Switch_thumbTextPadding
356 */
357 public int getThumbTextPadding() {
358 return mThumbTextPadding;
359 }
360
361 /**
362 * Set the drawable used for the track that the switch slides within.
363 *
364 * @param track Track drawable
365 *
366 * @attr ref android.R.styleable#Switch_track
367 */
368 public void setTrackDrawable(Drawable track) {
369 mTrackDrawable = track;
370 requestLayout();
371 }
372
373 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800374 * Set the drawable used for the track that the switch slides within.
375 *
Adam Powelldca510e2012-03-08 20:06:39 -0800376 * @param resId Resource ID of a track drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800377 *
378 * @attr ref android.R.styleable#Switch_track
379 */
380 public void setTrackResource(int resId) {
381 setTrackDrawable(getContext().getResources().getDrawable(resId));
382 }
383
384 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800385 * Get the drawable used for the track that the switch slides within.
386 *
387 * @return Track drawable
388 *
389 * @attr ref android.R.styleable#Switch_track
390 */
391 public Drawable getTrackDrawable() {
392 return mTrackDrawable;
393 }
394
395 /**
396 * Set the drawable used for the switch "thumb" - the piece that the user
397 * can physically touch and drag along the track.
398 *
399 * @param thumb Thumb drawable
400 *
401 * @attr ref android.R.styleable#Switch_thumb
402 */
403 public void setThumbDrawable(Drawable thumb) {
404 mThumbDrawable = thumb;
405 requestLayout();
406 }
407
408 /**
Adam Powelld9c7be62012-03-08 19:43:43 -0800409 * Set the drawable used for the switch "thumb" - the piece that the user
410 * can physically touch and drag along the track.
411 *
Adam Powelldca510e2012-03-08 20:06:39 -0800412 * @param resId Resource ID of a thumb drawable
Adam Powelld9c7be62012-03-08 19:43:43 -0800413 *
414 * @attr ref android.R.styleable#Switch_thumb
415 */
416 public void setThumbResource(int resId) {
417 setThumbDrawable(getContext().getResources().getDrawable(resId));
418 }
419
420 /**
Adam Powell6c86e1b2012-03-08 15:11:46 -0800421 * Get the drawable used for the switch "thumb" - the piece that the user
422 * can physically touch and drag along the track.
423 *
424 * @return Thumb drawable
425 *
426 * @attr ref android.R.styleable#Switch_thumb
427 */
428 public Drawable getThumbDrawable() {
429 return mThumbDrawable;
430 }
431
432 /**
Chet Haase150176d2011-08-26 09:54:06 -0700433 * Returns the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800434 *
435 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800436 */
437 public CharSequence getTextOn() {
438 return mTextOn;
439 }
440
441 /**
Chet Haase150176d2011-08-26 09:54:06 -0700442 * Sets the text displayed when the button is in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800443 *
444 * @attr ref android.R.styleable#Switch_textOn
Adam Powell12190b32010-11-28 19:07:53 -0800445 */
446 public void setTextOn(CharSequence textOn) {
447 mTextOn = textOn;
448 requestLayout();
449 }
450
451 /**
Chet Haase150176d2011-08-26 09:54:06 -0700452 * Returns the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800453 *
454 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800455 */
456 public CharSequence getTextOff() {
457 return mTextOff;
458 }
459
460 /**
Chet Haase150176d2011-08-26 09:54:06 -0700461 * Sets the text displayed when the button is not in the checked state.
Adam Powell6c86e1b2012-03-08 15:11:46 -0800462 *
463 * @attr ref android.R.styleable#Switch_textOff
Adam Powell12190b32010-11-28 19:07:53 -0800464 */
465 public void setTextOff(CharSequence textOff) {
466 mTextOff = textOff;
467 requestLayout();
468 }
469
470 @Override
471 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
472 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
473 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
474 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
475 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
476
477
478 if (mOnLayout == null) {
479 mOnLayout = makeLayout(mTextOn);
480 }
481 if (mOffLayout == null) {
482 mOffLayout = makeLayout(mTextOff);
483 }
484
485 mTrackDrawable.getPadding(mTempRect);
486 final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
487 final int switchWidth = Math.max(mSwitchMinWidth,
488 maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
489 final int switchHeight = mTrackDrawable.getIntrinsicHeight();
490
491 mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
492
493 switch (widthMode) {
494 case MeasureSpec.AT_MOST:
495 widthSize = Math.min(widthSize, switchWidth);
496 break;
497
498 case MeasureSpec.UNSPECIFIED:
499 widthSize = switchWidth;
500 break;
501
502 case MeasureSpec.EXACTLY:
503 // Just use what we were given
504 break;
505 }
506
507 switch (heightMode) {
508 case MeasureSpec.AT_MOST:
509 heightSize = Math.min(heightSize, switchHeight);
510 break;
511
512 case MeasureSpec.UNSPECIFIED:
513 heightSize = switchHeight;
514 break;
515
516 case MeasureSpec.EXACTLY:
517 // Just use what we were given
518 break;
519 }
520
521 mSwitchWidth = switchWidth;
522 mSwitchHeight = switchHeight;
523
524 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Adam Powell12190b32010-11-28 19:07:53 -0800525 final int measuredHeight = getMeasuredHeight();
526 if (measuredHeight < switchHeight) {
Dianne Hackborn189ee182010-12-02 21:48:53 -0800527 setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
Adam Powell12190b32010-11-28 19:07:53 -0800528 }
529 }
530
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700531 @Override
532 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
533 super.onPopulateAccessibilityEvent(event);
Svetoslav Ganov55249c82012-03-09 10:09:03 -0800534 CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
535 if (!TextUtils.isEmpty(text)) {
Svetoslav Ganov76502592011-07-29 10:44:59 -0700536 event.getText().add(text);
537 }
Svetoslav Ganov63bce032011-07-23 19:52:17 -0700538 }
539
Adam Powell12190b32010-11-28 19:07:53 -0800540 private Layout makeLayout(CharSequence text) {
Daniel Sandler4c3308d2012-04-19 11:04:39 -0400541 final CharSequence transformed = (mSwitchTransformationMethod != null)
542 ? mSwitchTransformationMethod.getTransformation(text, this)
543 : text;
544
545 return new StaticLayout(transformed, mTextPaint,
546 (int) Math.ceil(Layout.getDesiredWidth(transformed, mTextPaint)),
Adam Powell12190b32010-11-28 19:07:53 -0800547 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
548 }
549
550 /**
551 * @return true if (x, y) is within the target area of the switch thumb
552 */
553 private boolean hitThumb(float x, float y) {
554 mThumbDrawable.getPadding(mTempRect);
555 final int thumbTop = mSwitchTop - mTouchSlop;
556 final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
557 final int thumbRight = thumbLeft + mThumbWidth +
558 mTempRect.left + mTempRect.right + mTouchSlop;
559 final int thumbBottom = mSwitchBottom + mTouchSlop;
560 return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
561 }
562
563 @Override
564 public boolean onTouchEvent(MotionEvent ev) {
565 mVelocityTracker.addMovement(ev);
566 final int action = ev.getActionMasked();
567 switch (action) {
568 case MotionEvent.ACTION_DOWN: {
569 final float x = ev.getX();
570 final float y = ev.getY();
Gilles Debunnec2ab0d62011-06-13 12:52:48 -0700571 if (isEnabled() && hitThumb(x, y)) {
Adam Powell12190b32010-11-28 19:07:53 -0800572 mTouchMode = TOUCH_MODE_DOWN;
573 mTouchX = x;
574 mTouchY = y;
575 }
576 break;
577 }
578
579 case MotionEvent.ACTION_MOVE: {
580 switch (mTouchMode) {
581 case TOUCH_MODE_IDLE:
582 // Didn't target the thumb, treat normally.
583 break;
584
585 case TOUCH_MODE_DOWN: {
586 final float x = ev.getX();
587 final float y = ev.getY();
588 if (Math.abs(x - mTouchX) > mTouchSlop ||
589 Math.abs(y - mTouchY) > mTouchSlop) {
590 mTouchMode = TOUCH_MODE_DRAGGING;
591 getParent().requestDisallowInterceptTouchEvent(true);
592 mTouchX = x;
593 mTouchY = y;
594 return true;
595 }
596 break;
597 }
598
599 case TOUCH_MODE_DRAGGING: {
600 final float x = ev.getX();
601 final float dx = x - mTouchX;
602 float newPos = Math.max(0,
603 Math.min(mThumbPosition + dx, getThumbScrollRange()));
604 if (newPos != mThumbPosition) {
605 mThumbPosition = newPos;
606 mTouchX = x;
607 invalidate();
608 }
609 return true;
610 }
611 }
612 break;
613 }
614
615 case MotionEvent.ACTION_UP:
616 case MotionEvent.ACTION_CANCEL: {
617 if (mTouchMode == TOUCH_MODE_DRAGGING) {
618 stopDrag(ev);
619 return true;
620 }
621 mTouchMode = TOUCH_MODE_IDLE;
622 mVelocityTracker.clear();
623 break;
624 }
625 }
626
627 return super.onTouchEvent(ev);
628 }
629
630 private void cancelSuperTouch(MotionEvent ev) {
631 MotionEvent cancel = MotionEvent.obtain(ev);
632 cancel.setAction(MotionEvent.ACTION_CANCEL);
633 super.onTouchEvent(cancel);
634 cancel.recycle();
635 }
636
637 /**
638 * Called from onTouchEvent to end a drag operation.
639 *
640 * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
641 */
642 private void stopDrag(MotionEvent ev) {
643 mTouchMode = TOUCH_MODE_IDLE;
Gilles Debunnec2ab0d62011-06-13 12:52:48 -0700644 // Up and not canceled, also checks the switch has not been disabled during the drag
645 boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
Adam Powell12190b32010-11-28 19:07:53 -0800646
647 cancelSuperTouch(ev);
648
649 if (commitChange) {
650 boolean newState;
651 mVelocityTracker.computeCurrentVelocity(1000);
652 float xvel = mVelocityTracker.getXVelocity();
653 if (Math.abs(xvel) > mMinFlingVelocity) {
Adam Powell01d11ed2011-08-09 16:32:12 -0700654 newState = xvel > 0;
Adam Powell12190b32010-11-28 19:07:53 -0800655 } else {
656 newState = getTargetCheckedState();
657 }
658 animateThumbToCheckedState(newState);
659 } else {
660 animateThumbToCheckedState(isChecked());
661 }
662 }
663
664 private void animateThumbToCheckedState(boolean newCheckedState) {
Adam Powell12190b32010-11-28 19:07:53 -0800665 // TODO animate!
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800666 //float targetPos = newCheckedState ? 0 : getThumbScrollRange();
667 //mThumbPosition = targetPos;
Adam Powell12190b32010-11-28 19:07:53 -0800668 setChecked(newCheckedState);
669 }
670
671 private boolean getTargetCheckedState() {
Adam Powellec1d6032011-08-07 18:03:39 -0700672 return mThumbPosition >= getThumbScrollRange() / 2;
Adam Powell12190b32010-11-28 19:07:53 -0800673 }
674
675 @Override
676 public void setChecked(boolean checked) {
677 super.setChecked(checked);
Adam Powellec1d6032011-08-07 18:03:39 -0700678 mThumbPosition = checked ? getThumbScrollRange() : 0;
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800679 invalidate();
Adam Powell12190b32010-11-28 19:07:53 -0800680 }
681
682 @Override
683 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
684 super.onLayout(changed, left, top, right, bottom);
685
Adam Powellec1d6032011-08-07 18:03:39 -0700686 mThumbPosition = isChecked() ? getThumbScrollRange() : 0;
Joe Onoratoc3eabb92011-01-07 15:58:44 -0800687
Adam Powell12190b32010-11-28 19:07:53 -0800688 int switchRight = getWidth() - getPaddingRight();
689 int switchLeft = switchRight - mSwitchWidth;
690 int switchTop = 0;
691 int switchBottom = 0;
692 switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
693 default:
694 case Gravity.TOP:
695 switchTop = getPaddingTop();
696 switchBottom = switchTop + mSwitchHeight;
697 break;
698
699 case Gravity.CENTER_VERTICAL:
700 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
701 mSwitchHeight / 2;
702 switchBottom = switchTop + mSwitchHeight;
703 break;
704
705 case Gravity.BOTTOM:
706 switchBottom = getHeight() - getPaddingBottom();
707 switchTop = switchBottom - mSwitchHeight;
708 break;
709 }
710
711 mSwitchLeft = switchLeft;
712 mSwitchTop = switchTop;
713 mSwitchBottom = switchBottom;
714 mSwitchRight = switchRight;
715 }
716
717 @Override
718 protected void onDraw(Canvas canvas) {
719 super.onDraw(canvas);
720
721 // Draw the switch
722 int switchLeft = mSwitchLeft;
723 int switchTop = mSwitchTop;
724 int switchRight = mSwitchRight;
725 int switchBottom = mSwitchBottom;
726
727 mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
728 mTrackDrawable.draw(canvas);
729
730 canvas.save();
731
732 mTrackDrawable.getPadding(mTempRect);
733 int switchInnerLeft = switchLeft + mTempRect.left;
734 int switchInnerTop = switchTop + mTempRect.top;
735 int switchInnerRight = switchRight - mTempRect.right;
736 int switchInnerBottom = switchBottom - mTempRect.bottom;
737 canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
738
739 mThumbDrawable.getPadding(mTempRect);
740 final int thumbPos = (int) (mThumbPosition + 0.5f);
741 int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
742 int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
743
744 mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
745 mThumbDrawable.draw(canvas);
746
Chet Haase150176d2011-08-26 09:54:06 -0700747 // mTextColors should not be null, but just in case
748 if (mTextColors != null) {
749 mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
750 mTextColors.getDefaultColor()));
751 }
Adam Powell12190b32010-11-28 19:07:53 -0800752 mTextPaint.drawableState = getDrawableState();
753
754 Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
755
756 canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
757 (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
758 switchText.draw(canvas);
759
760 canvas.restore();
761 }
762
763 @Override
764 public int getCompoundPaddingRight() {
765 int padding = super.getCompoundPaddingRight() + mSwitchWidth;
766 if (!TextUtils.isEmpty(getText())) {
767 padding += mSwitchPadding;
768 }
769 return padding;
770 }
771
772 private int getThumbScrollRange() {
773 if (mTrackDrawable == null) {
774 return 0;
775 }
776 mTrackDrawable.getPadding(mTempRect);
777 return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
778 }
779
780 @Override
781 protected int[] onCreateDrawableState(int extraSpace) {
782 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
783 if (isChecked()) {
784 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
785 }
786 return drawableState;
787 }
788
789 @Override
790 protected void drawableStateChanged() {
791 super.drawableStateChanged();
792
793 int[] myDrawableState = getDrawableState();
794
795 // Set the state of the Drawable
Gilles Debunnee724ee42011-08-31 11:20:27 -0700796 // Drawable may be null when checked state is set from XML, from super constructor
797 if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
798 if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
Adam Powell12190b32010-11-28 19:07:53 -0800799
800 invalidate();
801 }
802
803 @Override
804 protected boolean verifyDrawable(Drawable who) {
805 return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
806 }
807
808 @Override
809 public void jumpDrawablesToCurrentState() {
810 super.jumpDrawablesToCurrentState();
811 mThumbDrawable.jumpToCurrentState();
812 mTrackDrawable.jumpToCurrentState();
813 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800814
815 @Override
816 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
817 super.onInitializeAccessibilityEvent(event);
818 event.setClassName(Switch.class.getName());
819 }
820
821 @Override
822 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
823 super.onInitializeAccessibilityNodeInfo(info);
824 info.setClassName(Switch.class.getName());
Svetoslav Ganov78bcc152012-04-12 17:17:19 -0700825 CharSequence switchText = isChecked() ? mTextOn : mTextOff;
826 if (!TextUtils.isEmpty(switchText)) {
827 CharSequence oldText = info.getText();
828 if (TextUtils.isEmpty(oldText)) {
829 info.setText(switchText);
830 } else {
831 StringBuilder newText = new StringBuilder();
832 newText.append(oldText).append(' ').append(switchText);
833 info.setText(newText);
834 }
835 }
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800836 }
Adam Powell12190b32010-11-28 19:07:53 -0800837}