summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/ui/Switch.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/ui/Switch.java')
-rw-r--r--src/com/android/camera/ui/Switch.java505
1 files changed, 505 insertions, 0 deletions
diff --git a/src/com/android/camera/ui/Switch.java b/src/com/android/camera/ui/Switch.java
new file mode 100644
index 000000000..ac21758a7
--- /dev/null
+++ b/src/com/android/camera/ui/Switch.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.CompoundButton;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.Arrays;
+
+/**
+ * A Switch is a two-state toggle switch widget that can select between two
+ * options. The user may drag the "thumb" back and forth to choose the selected option,
+ * or simply tap to toggle as if it were a checkbox.
+ */
+public class Switch extends CompoundButton {
+ private static final int TOUCH_MODE_IDLE = 0;
+ private static final int TOUCH_MODE_DOWN = 1;
+ private static final int TOUCH_MODE_DRAGGING = 2;
+
+ private Drawable mThumbDrawable;
+ private Drawable mTrackDrawable;
+ private int mThumbTextPadding;
+ private int mSwitchMinWidth;
+ private int mSwitchTextMaxWidth;
+ private int mSwitchPadding;
+ private CharSequence mTextOn;
+ private CharSequence mTextOff;
+
+ private int mTouchMode;
+ private int mTouchSlop;
+ private float mTouchX;
+ private float mTouchY;
+ private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private int mMinFlingVelocity;
+
+ private float mThumbPosition;
+ private int mSwitchWidth;
+ private int mSwitchHeight;
+ private int mThumbWidth; // Does not include padding
+
+ private int mSwitchLeft;
+ private int mSwitchTop;
+ private int mSwitchRight;
+ private int mSwitchBottom;
+
+ private TextPaint mTextPaint;
+ private ColorStateList mTextColors;
+ private Layout mOnLayout;
+ private Layout mOffLayout;
+
+ @SuppressWarnings("hiding")
+ private final Rect mTempRect = new Rect();
+
+ private static final int[] CHECKED_STATE_SET = {
+ android.R.attr.state_checked
+ };
+
+ /**
+ * Construct a new Switch with default styling, overriding specific style
+ * attributes as requested.
+ *
+ * @param context The Context that will determine this widget's theming.
+ * @param attrs Specification of attributes that should deviate from default styling.
+ */
+ public Switch(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.switchStyle);
+ }
+
+ /**
+ * Construct a new Switch with a default style determined by the given theme attribute,
+ * overriding specific style attributes as requested.
+ *
+ * @param context The Context that will determine this widget's theming.
+ * @param attrs Specification of attributes that should deviate from the default styling.
+ * @param defStyle An attribute ID within the active theme containing a reference to the
+ * default style for this widget. e.g. android.R.attr.switchStyle.
+ */
+ public Switch(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+ Resources res = getResources();
+ DisplayMetrics dm = res.getDisplayMetrics();
+ mTextPaint.density = dm.density;
+ mThumbDrawable = res.getDrawable(R.drawable.switch_inner_holo_dark);
+ mTrackDrawable = res.getDrawable(R.drawable.switch_track_holo_dark);
+ mTextOn = res.getString(R.string.capital_on);
+ mTextOff = res.getString(R.string.capital_off);
+ mThumbTextPadding = res.getDimensionPixelSize(R.dimen.thumb_text_padding);
+ mSwitchMinWidth = res.getDimensionPixelSize(R.dimen.switch_min_width);
+ mSwitchTextMaxWidth = res.getDimensionPixelSize(R.dimen.switch_text_max_width);
+ mSwitchPadding = res.getDimensionPixelSize(R.dimen.switch_padding);
+ setSwitchTextAppearance(context, android.R.style.TextAppearance_Holo_Small);
+
+ ViewConfiguration config = ViewConfiguration.get(context);
+ mTouchSlop = config.getScaledTouchSlop();
+ mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
+
+ // Refresh display with current params
+ refreshDrawableState();
+ setChecked(isChecked());
+ }
+
+ /**
+ * Sets the switch text color, size, style, hint color, and highlight color
+ * from the specified TextAppearance resource.
+ */
+ public void setSwitchTextAppearance(Context context, int resid) {
+ Resources res = getResources();
+ mTextColors = getTextColors();
+ int ts = res.getDimensionPixelSize(R.dimen.thumb_text_size);
+ if (ts != mTextPaint.getTextSize()) {
+ mTextPaint.setTextSize(ts);
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ if (mOnLayout == null) {
+ mOnLayout = makeLayout(mTextOn, mSwitchTextMaxWidth);
+ }
+ if (mOffLayout == null) {
+ mOffLayout = makeLayout(mTextOff, mSwitchTextMaxWidth);
+ }
+
+ mTrackDrawable.getPadding(mTempRect);
+ final int maxTextWidth = Math.min(mSwitchTextMaxWidth,
+ Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()));
+ final int switchWidth = Math.max(mSwitchMinWidth,
+ maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
+ final int switchHeight = mTrackDrawable.getIntrinsicHeight();
+
+ mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
+
+ mSwitchWidth = switchWidth;
+ mSwitchHeight = switchHeight;
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int measuredHeight = getMeasuredHeight();
+ final int measuredWidth = getMeasuredWidth();
+ if (measuredHeight < switchHeight) {
+ setMeasuredDimension(measuredWidth, switchHeight);
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEvent(event);
+ CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
+ if (!TextUtils.isEmpty(text)) {
+ event.getText().add(text);
+ }
+ }
+
+ private Layout makeLayout(CharSequence text, int maxWidth) {
+ int actual_width = (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint));
+ StaticLayout l = new StaticLayout(text, 0, text.length(), mTextPaint,
+ actual_width,
+ Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true,
+ TextUtils.TruncateAt.END,
+ (int) Math.min(actual_width, maxWidth));
+ return l;
+ }
+
+ /**
+ * @return true if (x, y) is within the target area of the switch thumb
+ */
+ private boolean hitThumb(float x, float y) {
+ mThumbDrawable.getPadding(mTempRect);
+ final int thumbTop = mSwitchTop - mTouchSlop;
+ final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
+ final int thumbRight = thumbLeft + mThumbWidth +
+ mTempRect.left + mTempRect.right + mTouchSlop;
+ final int thumbBottom = mSwitchBottom + mTouchSlop;
+ return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mVelocityTracker.addMovement(ev);
+ final int action = ev.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ if (isEnabled() && hitThumb(x, y)) {
+ mTouchMode = TOUCH_MODE_DOWN;
+ mTouchX = x;
+ mTouchY = y;
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE: {
+ switch (mTouchMode) {
+ case TOUCH_MODE_IDLE:
+ // Didn't target the thumb, treat normally.
+ break;
+
+ case TOUCH_MODE_DOWN: {
+ final float x = ev.getX();
+ final float y = ev.getY();
+ if (Math.abs(x - mTouchX) > mTouchSlop ||
+ Math.abs(y - mTouchY) > mTouchSlop) {
+ mTouchMode = TOUCH_MODE_DRAGGING;
+ getParent().requestDisallowInterceptTouchEvent(true);
+ mTouchX = x;
+ mTouchY = y;
+ return true;
+ }
+ break;
+ }
+
+ case TOUCH_MODE_DRAGGING: {
+ final float x = ev.getX();
+ final float dx = x - mTouchX;
+ float newPos = Math.max(0,
+ Math.min(mThumbPosition + dx, getThumbScrollRange()));
+ if (newPos != mThumbPosition) {
+ mThumbPosition = newPos;
+ mTouchX = x;
+ invalidate();
+ }
+ return true;
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ if (mTouchMode == TOUCH_MODE_DRAGGING) {
+ stopDrag(ev);
+ return true;
+ }
+ mTouchMode = TOUCH_MODE_IDLE;
+ mVelocityTracker.clear();
+ break;
+ }
+ }
+
+ return super.onTouchEvent(ev);
+ }
+
+ private void cancelSuperTouch(MotionEvent ev) {
+ MotionEvent cancel = MotionEvent.obtain(ev);
+ cancel.setAction(MotionEvent.ACTION_CANCEL);
+ super.onTouchEvent(cancel);
+ cancel.recycle();
+ }
+
+ /**
+ * Called from onTouchEvent to end a drag operation.
+ *
+ * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
+ */
+ private void stopDrag(MotionEvent ev) {
+ mTouchMode = TOUCH_MODE_IDLE;
+ // Up and not canceled, also checks the switch has not been disabled during the drag
+ boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
+
+ cancelSuperTouch(ev);
+
+ if (commitChange) {
+ boolean newState;
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float xvel = mVelocityTracker.getXVelocity();
+ if (Math.abs(xvel) > mMinFlingVelocity) {
+ newState = xvel > 0;
+ } else {
+ newState = getTargetCheckedState();
+ }
+ animateThumbToCheckedState(newState);
+ } else {
+ animateThumbToCheckedState(isChecked());
+ }
+ }
+
+ private void animateThumbToCheckedState(boolean newCheckedState) {
+ setChecked(newCheckedState);
+ }
+
+ private boolean getTargetCheckedState() {
+ return mThumbPosition >= getThumbScrollRange() / 2;
+ }
+
+ private void setThumbPosition(boolean checked) {
+ mThumbPosition = checked ? getThumbScrollRange() : 0;
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ super.setChecked(checked);
+ setThumbPosition(checked);
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ setThumbPosition(isChecked());
+
+ int switchRight;
+ int switchLeft;
+
+ switchRight = getWidth() - getPaddingRight();
+ switchLeft = switchRight - mSwitchWidth;
+
+ int switchTop = 0;
+ int switchBottom = 0;
+ switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
+ default:
+ case Gravity.TOP:
+ switchTop = getPaddingTop();
+ switchBottom = switchTop + mSwitchHeight;
+ break;
+
+ case Gravity.CENTER_VERTICAL:
+ switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
+ mSwitchHeight / 2;
+ switchBottom = switchTop + mSwitchHeight;
+ break;
+
+ case Gravity.BOTTOM:
+ switchBottom = getHeight() - getPaddingBottom();
+ switchTop = switchBottom - mSwitchHeight;
+ break;
+ }
+
+ mSwitchLeft = switchLeft;
+ mSwitchTop = switchTop;
+ mSwitchBottom = switchBottom;
+ mSwitchRight = switchRight;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Draw the switch
+ int switchLeft = mSwitchLeft;
+ int switchTop = mSwitchTop;
+ int switchRight = mSwitchRight;
+ int switchBottom = mSwitchBottom;
+
+ mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
+ mTrackDrawable.draw(canvas);
+
+ canvas.save();
+
+ mTrackDrawable.getPadding(mTempRect);
+ int switchInnerLeft = switchLeft + mTempRect.left;
+ int switchInnerTop = switchTop + mTempRect.top;
+ int switchInnerRight = switchRight - mTempRect.right;
+ int switchInnerBottom = switchBottom - mTempRect.bottom;
+ canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
+
+ mThumbDrawable.getPadding(mTempRect);
+ final int thumbPos = (int) (mThumbPosition + 0.5f);
+ int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
+ int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
+
+ mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+ mThumbDrawable.draw(canvas);
+
+ // mTextColors should not be null, but just in case
+ if (mTextColors != null) {
+ mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
+ mTextColors.getDefaultColor()));
+ }
+ mTextPaint.drawableState = getDrawableState();
+
+ Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
+
+ canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getEllipsizedWidth() / 2,
+ (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
+ switchText.draw(canvas);
+
+ canvas.restore();
+ }
+
+ @Override
+ public int getCompoundPaddingRight() {
+ int padding = super.getCompoundPaddingRight() + mSwitchWidth;
+ if (!TextUtils.isEmpty(getText())) {
+ padding += mSwitchPadding;
+ }
+ return padding;
+ }
+
+ private int getThumbScrollRange() {
+ if (mTrackDrawable == null) {
+ return 0;
+ }
+ mTrackDrawable.getPadding(mTempRect);
+ return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+
+ if (isChecked()) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ }
+ return drawableState;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ int[] myDrawableState = getDrawableState();
+
+ // Set the state of the Drawable
+ // Drawable may be null when checked state is set from XML, from super constructor
+ if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
+ if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
+
+ invalidate();
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ mThumbDrawable.jumpToCurrentState();
+ mTrackDrawable.jumpToCurrentState();
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(Switch.class.getName());
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(Switch.class.getName());
+ CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+ if (!TextUtils.isEmpty(switchText)) {
+ CharSequence oldText = info.getText();
+ if (TextUtils.isEmpty(oldText)) {
+ info.setText(switchText);
+ } else {
+ StringBuilder newText = new StringBuilder();
+ newText.append(oldText).append(' ').append(switchText);
+ info.setText(newText);
+ }
+ }
+ }
+}