OSDN Git Service

Remove com.android.camera.R
[android-x86/packages-apps-Gallery2.git] / src / com / android / camera / ui / Switch.java
1 /*
2  * Copyright (C) 2012 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
17 package com.android.camera.ui;
18
19 import android.annotation.TargetApi;
20 import android.content.Context;
21 import android.content.res.ColorStateList;
22 import android.content.res.Resources;
23 import android.content.res.TypedArray;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.Rect;
27 import android.graphics.Typeface;
28 import android.graphics.drawable.Drawable;
29 import android.text.Layout;
30 import android.text.StaticLayout;
31 import android.text.TextPaint;
32 import android.text.TextUtils;
33 import android.util.AttributeSet;
34 import android.util.DisplayMetrics;
35 import android.util.Log;
36 import android.util.TypedValue;
37 import android.view.Gravity;
38 import android.view.MotionEvent;
39 import android.view.VelocityTracker;
40 import android.view.ViewConfiguration;
41 import android.view.accessibility.AccessibilityEvent;
42 import android.view.accessibility.AccessibilityNodeInfo;
43 import android.widget.CompoundButton;
44
45 import com.android.gallery3d.R;
46 import com.android.gallery3d.common.ApiHelper;
47
48 import java.util.Arrays;
49
50 /**
51  * A Switch is a two-state toggle switch widget that can select between two
52  * options. The user may drag the "thumb" back and forth to choose the selected option,
53  * or simply tap to toggle as if it were a checkbox.
54  */
55 public class Switch extends CompoundButton {
56     private static final int TOUCH_MODE_IDLE = 0;
57     private static final int TOUCH_MODE_DOWN = 1;
58     private static final int TOUCH_MODE_DRAGGING = 2;
59
60     private Drawable mThumbDrawable;
61     private Drawable mTrackDrawable;
62     private int mThumbTextPadding;
63     private int mSwitchMinWidth;
64     private int mSwitchTextMaxWidth;
65     private int mSwitchPadding;
66     private CharSequence mTextOn;
67     private CharSequence mTextOff;
68
69     private int mTouchMode;
70     private int mTouchSlop;
71     private float mTouchX;
72     private float mTouchY;
73     private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
74     private int mMinFlingVelocity;
75
76     private float mThumbPosition;
77     private int mSwitchWidth;
78     private int mSwitchHeight;
79     private int mThumbWidth; // Does not include padding
80
81     private int mSwitchLeft;
82     private int mSwitchTop;
83     private int mSwitchRight;
84     private int mSwitchBottom;
85
86     private TextPaint mTextPaint;
87     private ColorStateList mTextColors;
88     private Layout mOnLayout;
89     private Layout mOffLayout;
90
91     @SuppressWarnings("hiding")
92     private final Rect mTempRect = new Rect();
93
94     private static final int[] CHECKED_STATE_SET = {
95         android.R.attr.state_checked
96     };
97
98     /**
99      * Construct a new Switch with default styling, overriding specific style
100      * attributes as requested.
101      *
102      * @param context The Context that will determine this widget's theming.
103      * @param attrs Specification of attributes that should deviate from default styling.
104      */
105     public Switch(Context context, AttributeSet attrs) {
106         this(context, attrs, R.attr.switchStyle);
107     }
108
109     /**
110      * Construct a new Switch with a default style determined by the given theme attribute,
111      * overriding specific style attributes as requested.
112      *
113      * @param context The Context that will determine this widget's theming.
114      * @param attrs Specification of attributes that should deviate from the default styling.
115      * @param defStyle An attribute ID within the active theme containing a reference to the
116      *                 default style for this widget. e.g. android.R.attr.switchStyle.
117      */
118     public Switch(Context context, AttributeSet attrs, int defStyle) {
119         super(context, attrs, defStyle);
120
121         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
122         Resources res = getResources();
123         DisplayMetrics dm = res.getDisplayMetrics();
124         mTextPaint.density = dm.density;
125         mThumbDrawable = res.getDrawable(R.drawable.switch_inner_holo_dark);
126         mTrackDrawable = res.getDrawable(R.drawable.switch_track_holo_dark);
127         mTextOn = res.getString(R.string.capital_on);
128         mTextOff = res.getString(R.string.capital_off);
129         mThumbTextPadding = res.getDimensionPixelSize(R.dimen.thumb_text_padding);
130         mSwitchMinWidth = res.getDimensionPixelSize(R.dimen.switch_min_width);
131         mSwitchTextMaxWidth = res.getDimensionPixelSize(R.dimen.switch_text_max_width);
132         mSwitchPadding = res.getDimensionPixelSize(R.dimen.switch_padding);
133         setSwitchTextAppearance(context, android.R.style.TextAppearance_Holo_Small);
134
135         ViewConfiguration config = ViewConfiguration.get(context);
136         mTouchSlop = config.getScaledTouchSlop();
137         mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
138
139         // Refresh display with current params
140         refreshDrawableState();
141         setChecked(isChecked());
142     }
143
144     /**
145      * Sets the switch text color, size, style, hint color, and highlight color
146      * from the specified TextAppearance resource.
147      */
148     public void setSwitchTextAppearance(Context context, int resid) {
149         Resources res = getResources();
150         mTextColors = getTextColors();
151         int ts = res.getDimensionPixelSize(R.dimen.thumb_text_size);
152         if (ts != mTextPaint.getTextSize()) {
153             mTextPaint.setTextSize(ts);
154             requestLayout();
155         }
156     }
157
158     @Override
159     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
160         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
161         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
162         if (mOnLayout == null) {
163             mOnLayout = makeLayout(mTextOn, mSwitchTextMaxWidth);
164         }
165         if (mOffLayout == null) {
166             mOffLayout = makeLayout(mTextOff, mSwitchTextMaxWidth);
167         }
168
169         mTrackDrawable.getPadding(mTempRect);
170         final int maxTextWidth = Math.min(mSwitchTextMaxWidth,
171                 Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()));
172         final int switchWidth = Math.max(mSwitchMinWidth,
173                 maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
174         final int switchHeight = mTrackDrawable.getIntrinsicHeight();
175
176         mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
177
178         mSwitchWidth = switchWidth;
179         mSwitchHeight = switchHeight;
180
181         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
182         final int measuredHeight = getMeasuredHeight();
183         final int measuredWidth = getMeasuredWidth();
184         if (measuredHeight < switchHeight) {
185             setMeasuredDimension(measuredWidth, switchHeight);
186         }
187     }
188
189     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
190     @Override
191     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
192         super.onPopulateAccessibilityEvent(event);
193         CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
194         if (!TextUtils.isEmpty(text)) {
195             event.getText().add(text);
196         }
197     }
198
199     private Layout makeLayout(CharSequence text, int maxWidth) {
200         int actual_width = (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint));
201         StaticLayout l = new StaticLayout(text, 0, text.length(), mTextPaint,
202                 actual_width,
203                 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true,
204                 TextUtils.TruncateAt.END,
205                 (int) Math.min(actual_width, maxWidth));
206         return l;
207     }
208
209     /**
210      * @return true if (x, y) is within the target area of the switch thumb
211      */
212     private boolean hitThumb(float x, float y) {
213         mThumbDrawable.getPadding(mTempRect);
214         final int thumbTop = mSwitchTop - mTouchSlop;
215         final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
216         final int thumbRight = thumbLeft + mThumbWidth +
217                 mTempRect.left + mTempRect.right + mTouchSlop;
218         final int thumbBottom = mSwitchBottom + mTouchSlop;
219         return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
220     }
221
222     @Override
223     public boolean onTouchEvent(MotionEvent ev) {
224         mVelocityTracker.addMovement(ev);
225         final int action = ev.getActionMasked();
226         switch (action) {
227             case MotionEvent.ACTION_DOWN: {
228                 final float x = ev.getX();
229                 final float y = ev.getY();
230                 if (isEnabled() && hitThumb(x, y)) {
231                     mTouchMode = TOUCH_MODE_DOWN;
232                     mTouchX = x;
233                     mTouchY = y;
234                 }
235                 break;
236             }
237
238             case MotionEvent.ACTION_MOVE: {
239                 switch (mTouchMode) {
240                     case TOUCH_MODE_IDLE:
241                         // Didn't target the thumb, treat normally.
242                         break;
243
244                     case TOUCH_MODE_DOWN: {
245                         final float x = ev.getX();
246                         final float y = ev.getY();
247                         if (Math.abs(x - mTouchX) > mTouchSlop ||
248                                 Math.abs(y - mTouchY) > mTouchSlop) {
249                             mTouchMode = TOUCH_MODE_DRAGGING;
250                             getParent().requestDisallowInterceptTouchEvent(true);
251                             mTouchX = x;
252                             mTouchY = y;
253                             return true;
254                         }
255                         break;
256                     }
257
258                     case TOUCH_MODE_DRAGGING: {
259                         final float x = ev.getX();
260                         final float dx = x - mTouchX;
261                         float newPos = Math.max(0,
262                                 Math.min(mThumbPosition + dx, getThumbScrollRange()));
263                         if (newPos != mThumbPosition) {
264                             mThumbPosition = newPos;
265                             mTouchX = x;
266                             invalidate();
267                         }
268                         return true;
269                     }
270                 }
271                 break;
272             }
273
274             case MotionEvent.ACTION_UP:
275             case MotionEvent.ACTION_CANCEL: {
276                 if (mTouchMode == TOUCH_MODE_DRAGGING) {
277                     stopDrag(ev);
278                     return true;
279                 }
280                 mTouchMode = TOUCH_MODE_IDLE;
281                 mVelocityTracker.clear();
282                 break;
283             }
284         }
285
286         return super.onTouchEvent(ev);
287     }
288
289     private void cancelSuperTouch(MotionEvent ev) {
290         MotionEvent cancel = MotionEvent.obtain(ev);
291         cancel.setAction(MotionEvent.ACTION_CANCEL);
292         super.onTouchEvent(cancel);
293         cancel.recycle();
294     }
295
296     /**
297      * Called from onTouchEvent to end a drag operation.
298      *
299      * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
300      */
301     private void stopDrag(MotionEvent ev) {
302         mTouchMode = TOUCH_MODE_IDLE;
303         // Up and not canceled, also checks the switch has not been disabled during the drag
304         boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
305
306         cancelSuperTouch(ev);
307
308         if (commitChange) {
309             boolean newState;
310             mVelocityTracker.computeCurrentVelocity(1000);
311             float xvel = mVelocityTracker.getXVelocity();
312             if (Math.abs(xvel) > mMinFlingVelocity) {
313                 newState = xvel > 0;
314             } else {
315                 newState = getTargetCheckedState();
316             }
317             animateThumbToCheckedState(newState);
318         } else {
319             animateThumbToCheckedState(isChecked());
320         }
321     }
322
323     private void animateThumbToCheckedState(boolean newCheckedState) {
324         setChecked(newCheckedState);
325     }
326
327     private boolean getTargetCheckedState() {
328         return mThumbPosition >= getThumbScrollRange() / 2;
329     }
330
331     private void setThumbPosition(boolean checked) {
332         mThumbPosition = checked ? getThumbScrollRange() : 0;
333     }
334
335     @Override
336     public void setChecked(boolean checked) {
337         super.setChecked(checked);
338         setThumbPosition(checked);
339         invalidate();
340     }
341
342     @Override
343     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
344         super.onLayout(changed, left, top, right, bottom);
345
346         setThumbPosition(isChecked());
347
348         int switchRight;
349         int switchLeft;
350
351         switchRight = getWidth() - getPaddingRight();
352         switchLeft = switchRight - mSwitchWidth;
353
354         int switchTop = 0;
355         int switchBottom = 0;
356         switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
357             default:
358             case Gravity.TOP:
359                 switchTop = getPaddingTop();
360                 switchBottom = switchTop + mSwitchHeight;
361                 break;
362
363             case Gravity.CENTER_VERTICAL:
364                 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
365                         mSwitchHeight / 2;
366                 switchBottom = switchTop + mSwitchHeight;
367                 break;
368
369             case Gravity.BOTTOM:
370                 switchBottom = getHeight() - getPaddingBottom();
371                 switchTop = switchBottom - mSwitchHeight;
372                 break;
373         }
374
375         mSwitchLeft = switchLeft;
376         mSwitchTop = switchTop;
377         mSwitchBottom = switchBottom;
378         mSwitchRight = switchRight;
379     }
380
381     @Override
382     protected void onDraw(Canvas canvas) {
383         super.onDraw(canvas);
384
385         // Draw the switch
386         int switchLeft = mSwitchLeft;
387         int switchTop = mSwitchTop;
388         int switchRight = mSwitchRight;
389         int switchBottom = mSwitchBottom;
390
391         mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
392         mTrackDrawable.draw(canvas);
393
394         canvas.save();
395
396         mTrackDrawable.getPadding(mTempRect);
397         int switchInnerLeft = switchLeft + mTempRect.left;
398         int switchInnerTop = switchTop + mTempRect.top;
399         int switchInnerRight = switchRight - mTempRect.right;
400         int switchInnerBottom = switchBottom - mTempRect.bottom;
401         canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
402
403         mThumbDrawable.getPadding(mTempRect);
404         final int thumbPos = (int) (mThumbPosition + 0.5f);
405         int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
406         int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
407
408         mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
409         mThumbDrawable.draw(canvas);
410
411         // mTextColors should not be null, but just in case
412         if (mTextColors != null) {
413             mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
414                     mTextColors.getDefaultColor()));
415         }
416         mTextPaint.drawableState = getDrawableState();
417
418         Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
419
420         canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getEllipsizedWidth() / 2,
421                 (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
422         switchText.draw(canvas);
423
424         canvas.restore();
425     }
426
427     @Override
428     public int getCompoundPaddingRight() {
429         int padding = super.getCompoundPaddingRight() + mSwitchWidth;
430         if (!TextUtils.isEmpty(getText())) {
431             padding += mSwitchPadding;
432         }
433         return padding;
434     }
435
436     private int getThumbScrollRange() {
437         if (mTrackDrawable == null) {
438             return 0;
439         }
440         mTrackDrawable.getPadding(mTempRect);
441         return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
442     }
443
444     @Override
445     protected int[] onCreateDrawableState(int extraSpace) {
446         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
447
448         if (isChecked()) {
449             mergeDrawableStates(drawableState, CHECKED_STATE_SET);
450         }
451         return drawableState;
452     }
453
454     @Override
455     protected void drawableStateChanged() {
456         super.drawableStateChanged();
457
458         int[] myDrawableState = getDrawableState();
459
460         // Set the state of the Drawable
461         // Drawable may be null when checked state is set from XML, from super constructor
462         if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
463         if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
464
465         invalidate();
466     }
467
468     @Override
469     protected boolean verifyDrawable(Drawable who) {
470         return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
471     }
472
473     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
474     @Override
475     public void jumpDrawablesToCurrentState() {
476         super.jumpDrawablesToCurrentState();
477         mThumbDrawable.jumpToCurrentState();
478         mTrackDrawable.jumpToCurrentState();
479     }
480
481     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
482     @Override
483     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
484         super.onInitializeAccessibilityEvent(event);
485         event.setClassName(Switch.class.getName());
486     }
487
488     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
489     @Override
490     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
491         super.onInitializeAccessibilityNodeInfo(info);
492         info.setClassName(Switch.class.getName());
493         CharSequence switchText = isChecked() ? mTextOn : mTextOff;
494         if (!TextUtils.isEmpty(switchText)) {
495             CharSequence oldText = info.getText();
496             if (TextUtils.isEmpty(oldText)) {
497                 info.setText(switchText);
498             } else {
499                 StringBuilder newText = new StringBuilder();
500                 newText.append(oldText).append(' ').append(switchText);
501                 info.setText(newText);
502             }
503         }
504     }
505 }