summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-02 22:54:33 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-02 22:54:33 -0800
commit3dec7d563a2f3e1eb967ce2054a00b6620e3558c (patch)
treeaa3b0365c47cb3c1607c0dc76c8d32b4046fc287 /core/java/android/widget
parent15ab3eae2ec3d73b3e8aa60b33ae41445bf83f4b (diff)
downloadframeworks_base-3dec7d563a2f3e1eb967ce2054a00b6620e3558c.zip
frameworks_base-3dec7d563a2f3e1eb967ce2054a00b6620e3558c.tar.gz
frameworks_base-3dec7d563a2f3e1eb967ce2054a00b6620e3558c.tar.bz2
auto import from //depot/cupcake/@137055
Diffstat (limited to 'core/java/android/widget')
-rw-r--r--core/java/android/widget/AbsListView.java29
-rw-r--r--core/java/android/widget/AbsSeekBar.java45
-rw-r--r--core/java/android/widget/AnalogClock.java17
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java103
-rw-r--r--core/java/android/widget/BaseAdapter.java4
-rw-r--r--core/java/android/widget/Chronometer.java19
-rw-r--r--core/java/android/widget/CursorAdapter.java20
-rw-r--r--core/java/android/widget/Filter.java14
-rw-r--r--core/java/android/widget/GridView.java29
-rw-r--r--core/java/android/widget/HorizontalScrollView.java2
-rw-r--r--core/java/android/widget/ImageView.java2
-rw-r--r--core/java/android/widget/ListView.java85
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java4
-rw-r--r--core/java/android/widget/PopupWindow.java63
-rw-r--r--core/java/android/widget/ProgressBar.java1
-rw-r--r--core/java/android/widget/ResourceCursorAdapter.java22
-rw-r--r--core/java/android/widget/TextView.java355
-rw-r--r--core/java/android/widget/ZoomButtonsController.java478
-rw-r--r--core/java/android/widget/ZoomRing.java418
-rw-r--r--core/java/android/widget/ZoomRingController.java462
20 files changed, 1728 insertions, 444 deletions
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index f362e22..9da78d0 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -17,7 +17,6 @@
package android.widget;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -28,6 +27,7 @@ import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -893,25 +893,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mSyncMode = SYNC_FIRST_POSITION;
}
- // Don't restore the type filter window when there is no keyboard
- if (acceptFilter()) {
- String filterText = ss.filter;
- setFilterText(filterText);
- }
+ setFilterText(ss.filter);
requestLayout();
}
private boolean acceptFilter() {
final Context context = mContext;
- final Configuration configuration = context.getResources().getConfiguration();
- final boolean keyboardShowing = configuration.keyboardHidden !=
- Configuration.KEYBOARDHIDDEN_YES;
- final boolean hasKeyboard = configuration.keyboard != Configuration.KEYBOARD_NOKEYS;
final InputMethodManager inputManager = (InputMethodManager)
context.getSystemService(Context.INPUT_METHOD_SERVICE);
- return (hasKeyboard && keyboardShowing) ||
- (!hasKeyboard && !inputManager.isFullscreenMode());
+ return !inputManager.isFullscreenMode();
}
/**
@@ -922,7 +913,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
public void setFilterText(String filterText) {
// TODO: Should we check for acceptFilter()?
- if (mTextFilterEnabled && filterText != null && filterText.length() > 0) {
+ if (mTextFilterEnabled && !TextUtils.isEmpty(filterText)) {
createTextFilter(false);
// This is going to call our listener onTextChanged, but we might not
// be ready to bring up a window yet
@@ -942,6 +933,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /**
+ * Returns the list's text filter, if available.
+ * @return the list's text filter or null if filtering isn't enabled
+ * @hide pending API Council approval
+ */
+ public CharSequence getTextFilter() {
+ if (mTextFilterEnabled && mTextFilter != null) {
+ return mTextFilter.getText();
+ }
+ return null;
+ }
+
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index b046a6b..1d553f1 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -40,9 +40,15 @@ public abstract class AbsSeekBar extends ProgressBar {
* Whether this is user seekable.
*/
boolean mIsUserSeekable = true;
+
+ /**
+ * On key presses (right or left), the amount to increment/decrement the
+ * progress.
+ */
+ private int mKeyProgressIncrement = 1;
private static final int NO_ALPHA = 0xFF;
- float mDisabledAlpha;
+ private float mDisabledAlpha;
public AbsSeekBar(Context context) {
super(context);
@@ -101,6 +107,39 @@ public abstract class AbsSeekBar extends ProgressBar {
invalidate();
}
+ /**
+ * Sets the amount of progress changed via the arrow keys.
+ *
+ * @param increment The amount to increment or decrement when the user
+ * presses the arrow keys.
+ */
+ public void setKeyProgressIncrement(int increment) {
+ mKeyProgressIncrement = increment < 0 ? -increment : increment;
+ }
+
+ /**
+ * Returns the amount of progress changed via the arrow keys.
+ * <p>
+ * By default, this will be a value that is derived from the max progress.
+ *
+ * @return The amount to increment or decrement when the user presses the
+ * arrow keys. This will be positive.
+ */
+ public int getKeyProgressIncrement() {
+ return mKeyProgressIncrement;
+ }
+
+ @Override
+ public synchronized void setMax(int max) {
+ super.setMax(max);
+
+ if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
+ // It will take the user too long to change this via keys, change it
+ // to something more reasonable
+ setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
+ }
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mThumb || super.verifyDrawable(who);
@@ -321,12 +360,12 @@ public abstract class AbsSeekBar extends ProgressBar {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (progress <= 0) break;
- setProgress(progress - 1, true);
+ setProgress(progress - mKeyProgressIncrement, true);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (progress >= getMax()) break;
- setProgress(progress + 1, true);
+ setProgress(progress + mKeyProgressIncrement, true);
return true;
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index cf9c588..f847bc3 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -48,7 +48,6 @@ public class AnalogClock extends View {
private int mDialHeight;
private boolean mAttached;
- private long mLastTime;
private final Handler mHandler = new Handler();
private float mMinutes;
@@ -96,7 +95,6 @@ public class AnalogClock extends View {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- onTimeChanged();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();
@@ -107,6 +105,15 @@ public class AnalogClock extends View {
getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
+
+ // NOTE: It's safe to do these after registering the receiver since the receiver always runs
+ // in the main thread, therefore the receiver can't run before this method returns.
+
+ // The time zone may have changed while the receiver wasn't registered, so update the Time
+ mCalendar = new Time();
+
+ // Make sure we update to the current time
+ onTimeChanged();
}
@Override
@@ -212,9 +219,7 @@ public class AnalogClock extends View {
}
private void onTimeChanged() {
- long time = System.currentTimeMillis();
- mCalendar.set(time);
- mLastTime = time;
+ mCalendar.setToNow();
int hour = mCalendar.hour;
int minute = mCalendar.minute;
@@ -231,8 +236,6 @@ public class AnalogClock extends View {
if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
String tz = intent.getStringExtra("time-zone");
mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
- } else {
- mCalendar = new Time();
}
onTimeChanged();
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 7a51676..0c1c72a 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -78,6 +78,8 @@ import com.android.internal.R;
* @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
* @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
static final boolean DEBUG = false;
@@ -96,6 +98,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private DropDownListView mDropDownList;
private int mDropDownVerticalOffset;
private int mDropDownHorizontalOffset;
+ private int mDropDownAnchorId;
+ private View mDropDownAnchorView; // view is retrieved lazily from id once needed
+ private int mDropDownWidth;
private Drawable mDropDownListHighlight;
@@ -147,6 +152,18 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
mDropDownHorizontalOffset = (int)
a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
+
+ // Get the anchor's id now, but the view won't be ready, so wait to actually get the
+ // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
+ // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
+ // this TextView, as a default anchoring point.
+ mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
+ View.NO_ID);
+
+ // For dropdown width, the developer can specify a specific width, or FILL_PARENT
+ // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
+ mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
R.layout.simple_dropdown_hint);
@@ -187,6 +204,49 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void setCompletionHint(CharSequence hint) {
mHintText = hint;
}
+
+ /**
+ * <p>Returns the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+ *
+ * @return the width for the drop down list
+ */
+ public int getDropDownWidth() {
+ return mDropDownWidth;
+ }
+
+ /**
+ * <p>Sets the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
+ *
+ * @param width the width to use
+ */
+ public void setDropDownWidth(int width) {
+ mDropDownWidth = width;
+ }
+
+ /**
+ * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
+ *
+ * @return the view's id, or {@link View#NO_ID} if none specified
+ */
+ public int getDropDownAnchor() {
+ return mDropDownAnchorId;
+ }
+
+ /**
+ * <p>Sets the view to which the auto-complete drop down list should anchor. The view
+ * corresponding to this id will not be loaded until the next time it is needed to avoid
+ * loading a view which is not yet instantiated.</p>
+ *
+ * @param id the id to anchor the drop down list view to
+ */
+ public void setDropDownAnchor(int id) {
+ mDropDownAnchorId = id;
+ mDropDownAnchorView = null;
+ }
/**
* <p>Returns the number of characters the user must type before the drop
@@ -741,6 +801,18 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return result;
}
+
+ /**
+ * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
+ * the id is NO_ID or we can't find a view for the given id, we return this TextView as
+ * the default anchoring point.</p>
+ */
+ private View getDropDownAnchorView() {
+ if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
+ mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
+ }
+ return mDropDownAnchorView == null ? this : mDropDownAnchorView;
+ }
/**
* <p>Displays the drop down on screen.</p>
@@ -748,16 +820,37 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void showDropDown() {
int height = buildDropDown();
if (mPopup.isShowing()) {
- mPopup.update(this, mDropDownHorizontalOffset, mDropDownVerticalOffset,
- getWidth(), height);
+ int widthSpec;
+ if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ // The call to PopupWindow's update method below can accept -1 for any
+ // value you do not want to update.
+ widthSpec = -1;
+ } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ widthSpec = getDropDownAnchorView().getWidth();
+ } else {
+ widthSpec = mDropDownWidth;
+ }
+ mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
+ mDropDownVerticalOffset, widthSpec, height);
} else {
- mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
- mPopup.setWidth(getWidth());
+ int widthSpec;
+ if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ } else {
+ mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
+ if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ mPopup.setWidth(getDropDownAnchorView().getWidth());
+ } else {
+ mPopup.setWidth(mDropDownWidth);
+ }
+ }
mPopup.setHeight(height);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
mPopup.setOutsideTouchable(true);
mPopup.setTouchInterceptor(new PopupTouchIntercepter());
- mPopup.showAsDropDown(this, mDropDownHorizontalOffset, mDropDownVerticalOffset);
+ mPopup.showAsDropDown(getDropDownAnchorView(),
+ mDropDownHorizontalOffset, mDropDownVerticalOffset);
mDropDownList.setSelection(ListView.INVALID_POSITION);
mDropDownList.hideSelector();
mDropDownList.requestFocus();
diff --git a/core/java/android/widget/BaseAdapter.java b/core/java/android/widget/BaseAdapter.java
index 1921d73..532fd76 100644
--- a/core/java/android/widget/BaseAdapter.java
+++ b/core/java/android/widget/BaseAdapter.java
@@ -42,6 +42,10 @@ public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
mDataSetObservable.unregisterObserver(observer);
}
+ /**
+ * Notifies the attached View that the underlying data has been changed
+ * and it should refresh itself.
+ */
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 369221e..91add58 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -69,7 +69,10 @@ public class Chronometer extends TextView {
private Object[] mFormatterArgs = new Object[1];
private StringBuilder mFormatBuilder;
private OnChronometerTickListener mOnChronometerTickListener;
-
+ private StringBuilder mRecycle = new StringBuilder(8);
+
+ private static final int TICK_WHAT = 2;
+
/**
* Initialize this Chronometer object.
* Sets the base to the current time.
@@ -115,6 +118,7 @@ public class Chronometer extends TextView {
@android.view.RemotableViewMethod
public void setBase(long base) {
mBase = base;
+ dispatchChronometerTick();
updateText(SystemClock.elapsedRealtime());
}
@@ -216,10 +220,10 @@ public class Chronometer extends TextView {
updateRunning();
}
- private void updateText(long now) {
+ private synchronized void updateText(long now) {
long seconds = now - mBase;
seconds /= 1000;
- String text = DateUtils.formatElapsedTime(seconds);
+ String text = DateUtils.formatElapsedTime(mRecycle, seconds);
if (mFormat != null) {
Locale loc = Locale.getDefault();
@@ -247,7 +251,10 @@ public class Chronometer extends TextView {
if (running != mRunning) {
if (running) {
updateText(SystemClock.elapsedRealtime());
- mHandler.sendMessageDelayed(Message.obtain(), 1000);
+ dispatchChronometerTick();
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, TICK_WHAT), 1000);
+ } else {
+ mHandler.removeMessages(TICK_WHAT);
}
mRunning = running;
}
@@ -255,10 +262,10 @@ public class Chronometer extends TextView {
private Handler mHandler = new Handler() {
public void handleMessage(Message m) {
- if (mStarted) {
+ if (mRunning) {
updateText(SystemClock.elapsedRealtime());
dispatchChronometerTick();
- sendMessageDelayed(Message.obtain(), 1000);
+ sendMessageDelayed(Message.obtain(this, TICK_WHAT), 1000);
}
}
};
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 3d758e7..898e501 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -348,6 +348,21 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
mFilterQueryProvider = filterQueryProvider;
}
+ /**
+ * Called when the {@link ContentObserver} on the cursor receives a change notification.
+ * The default implementation provides the auto-requery logic, but may be overridden by
+ * sub classes.
+ *
+ * @see ContentObserver#onChange(boolean)
+ * @hide pending API Council approval
+ */
+ protected void onContentChanged() {
+ if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
+ if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
+ mDataValid = mCursor.requery();
+ }
+ }
+
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
@@ -360,10 +375,7 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
@Override
public void onChange(boolean selfChange) {
- if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
- mDataValid = mCursor.requery();
- }
+ onContentChanged();
}
}
diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java
index a2316cf..1d0fd5e 100644
--- a/core/java/android/widget/Filter.java
+++ b/core/java/android/widget/Filter.java
@@ -45,8 +45,6 @@ public abstract class Filter {
private Handler mThreadHandler;
private Handler mResultHandler;
- private String mConstraint;
- private boolean mConstraintIsValid = false;
/**
* <p>Creates a new asynchronous filter.</p>
@@ -84,13 +82,6 @@ public abstract class Filter {
*/
public final void filter(CharSequence constraint, FilterListener listener) {
synchronized (this) {
- String constraintAsString = constraint != null ? constraint.toString() : null;
- if (mConstraintIsValid && (
- (constraintAsString == null && mConstraint == null) ||
- (constraintAsString != null && constraintAsString.equals(mConstraint)))) {
- // nothing to do
- return;
- }
if (mThreadHandler == null) {
HandlerThread thread = new HandlerThread(THREAD_NAME);
@@ -103,16 +94,13 @@ public abstract class Filter {
RequestArguments args = new RequestArguments();
// make sure we use an immutable copy of the constraint, so that
// it doesn't change while the filter operation is in progress
- args.constraint = constraintAsString;
+ args.constraint = constraint != null ? constraint.toString() : null;
args.listener = listener;
message.obj = args;
mThreadHandler.removeMessages(FILTER_TOKEN);
mThreadHandler.removeMessages(FINISH_TOKEN);
mThreadHandler.sendMessage(message);
-
- mConstraint = constraintAsString;
- mConstraintIsValid = true;
}
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 38bfc7c..6bbf062 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -924,32 +924,23 @@ public class GridView extends AbsListView {
final int count = mItemCount;
if (count > 0) {
final View child = obtainView(0);
- final int childViewType = mAdapter.getItemViewType(0);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+ if (p == null) {
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
-
- final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- mListPadding.left + mListPadding.right, lp.width);
-
- int lpHeight = lp.height;
-
- int childHeightSpec;
- if (lpHeight > 0) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
+ p.viewType = mAdapter.getItemViewType(0);
+ int childHeightSpec = getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
+ int childWidthSpec = getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
child.measure(childWidthSpec, childHeightSpec);
+
childHeight = child.getMeasuredHeight();
- if (mRecycler.shouldRecycleViewType(childViewType)) {
+ if (mRecycler.shouldRecycleViewType(p.viewType)) {
mRecycler.addScrapView(child);
}
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 96fe595..652e30c 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -875,7 +875,7 @@ public class HorizontalScrollView extends FrameLayout {
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
- final int childHeightMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 94d1bd1..a4523b9 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -840,7 +840,7 @@ public class ImageView extends View {
@Override
public int getBaseline() {
- return mBaselineAligned ? getHeight() : -1;
+ return mBaselineAligned ? getMeasuredHeight() : -1;
}
/**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 4e5989c..6df72d4 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1011,34 +1011,13 @@ public class ListView extends AbsListView {
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
final View child = obtainView(0);
- final int childViewType = mAdapter.getItemViewType(0);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
-
- final int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
- mListPadding.left + mListPadding.right, lp.width);
-
- int lpHeight = lp.height;
-
- int childHeightSpec;
- if (lpHeight > 0) {
- childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- }
-
- child.measure(childWidthSpec, childHeightSpec);
+ measureScrapChild(child, 0, widthMeasureSpec);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
- if (mRecycler.shouldRecycleViewType(childViewType)) {
+ if (recycleOnMeasure()) {
mRecycler.addScrapView(child);
}
}
@@ -1055,13 +1034,40 @@ public class ListView extends AbsListView {
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
- heightSize = measureHeightOfChildren(
- MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
- 0, NO_POSITION, heightSize, -1);
+ heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
setMeasuredDimension(widthSize, heightSize);
- mWidthMeasureSpec = widthMeasureSpec;
+ mWidthMeasureSpec = widthMeasureSpec;
+ }
+
+ private void measureScrapChild(View child, int position, int widthMeasureSpec) {
+ LayoutParams p = (LayoutParams) child.getLayoutParams();
+ if (p == null) {
+ p = new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ }
+ p.viewType = mAdapter.getItemViewType(position);
+
+ int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
+ mListPadding.left + mListPadding.right, p.width);
+ int lpHeight = p.height;
+ int childHeightSpec;
+ if (lpHeight > 0) {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
+ } else {
+ childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ /**
+ * @return True to recycle the views used to measure this ListView in
+ * UNSPECIFIED/AT_MOST modes, false otherwise.
+ * @hide
+ */
+ protected boolean recycleOnMeasure() {
+ return true;
}
/**
@@ -1090,8 +1096,8 @@ public class ListView extends AbsListView {
* startPosition is 0).
* @return The height of this ListView with the given children.
*/
- final int measureHeightOfChildren(final int widthMeasureSpec, final int startPosition,
- int endPosition, final int maxHeight, int disallowPartialChildPosition) {
+ final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
+ final int maxHeight, int disallowPartialChildPosition) {
final ListAdapter adapter = mAdapter;
if (adapter == null) {
@@ -1110,29 +1116,20 @@ public class ListView extends AbsListView {
// mItemCount - 1 since endPosition parameter is inclusive
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
+ final boolean recyle = recycleOnMeasure();
+
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i);
- final int childViewType = adapter.getItemViewType(i);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, 0);
- child.setLayoutParams(lp);
- }
- lp.viewType = childViewType;
+ measureScrapChild(child, i, widthMeasureSpec);
if (i > 0) {
// Count the divider for all but one child
returnedHeight += dividerHeight;
}
- child.measure(widthMeasureSpec, lp.height >= 0
- ? MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY)
- : MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
-
// Recycle the view before we possibly return from the method
- if (recycleBin.shouldRecycleViewType(childViewType)) {
+ if (recyle) {
recycleBin.addScrapView(child);
}
@@ -1656,7 +1653,7 @@ public class ListView extends AbsListView {
// Respect layout params that are already in the view. Otherwise make some up...
// noinspection unchecked
- AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
+ AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
if (p == null) {
p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
@@ -1675,7 +1672,7 @@ public class ListView extends AbsListView {
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
if (child instanceof Checkable) {
- ((Checkable)child).setChecked(mCheckStates.get(position));
+ ((Checkable) child).setChecked(mCheckStates.get(position));
}
}
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index 59a9310..05abc26 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -126,7 +126,7 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
Editable text = getText();
int end = getSelectionEnd();
- if (end < 0) {
+ if (end < 0 || mTokenizer == null) {
return false;
}
@@ -147,7 +147,7 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
public void performValidation() {
Validator v = getValidator();
- if (v == null) {
+ if (v == null || mTokenizer == null) {
return;
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 4a5cea1..53db77e 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -30,6 +30,7 @@ import android.view.View.OnTouchListener;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
import android.os.IBinder;
import android.content.Context;
import android.content.res.TypedArray;
@@ -102,6 +103,8 @@ public class PopupWindow {
private Rect mTempRect = new Rect();
private Drawable mBackground;
+ private Drawable mAboveAnchorBackgroundDrawable;
+ private Drawable mBelowAnchorBackgroundDrawable;
private boolean mAboveAnchor;
@@ -164,6 +167,43 @@ public class PopupWindow {
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
+ // If this is a StateListDrawable, try to find and store the drawable to be
+ // used when the drop-down is placed above its anchor view, and the one to be
+ // used when the drop-down is placed below its anchor view. We extract
+ // the drawables ourselves to work around a problem with using refreshDrawableState
+ // that it will take into account the padding of all drawables specified in a
+ // StateListDrawable, thus adding superfluous padding to drop-down views.
+ //
+ // We assume a StateListDrawable will have a drawable for ABOVE_ANCHOR_STATE_SET and
+ // at least one other drawable, intended for the 'below-anchor state'.
+ if (mBackground instanceof StateListDrawable) {
+ StateListDrawable background = (StateListDrawable) mBackground;
+
+ // Find the above-anchor view - this one's easy, it should be labeled as such.
+ int aboveAnchorStateIndex = background.getStateDrawableIndex(ABOVE_ANCHOR_STATE_SET);
+
+ // Now, for the below-anchor view, look for any other drawable specified in the
+ // StateListDrawable which is not for the above-anchor state and use that.
+ int count = background.getStateCount();
+ int belowAnchorStateIndex = -1;
+ for (int i = 0; i < count; i++) {
+ if (i != aboveAnchorStateIndex) {
+ belowAnchorStateIndex = i;
+ break;
+ }
+ }
+
+ // Store the drawables we found, if we found them. Otherwise, set them both
+ // to null so that we'll just use refreshDrawableState.
+ if (aboveAnchorStateIndex != -1 && belowAnchorStateIndex != -1) {
+ mAboveAnchorBackgroundDrawable = background.getStateDrawable(aboveAnchorStateIndex);
+ mBelowAnchorBackgroundDrawable = background.getStateDrawable(belowAnchorStateIndex);
+ } else {
+ mBelowAnchorBackgroundDrawable = null;
+ mAboveAnchorBackgroundDrawable = null;
+ }
+ }
+
a.recycle();
}
@@ -661,7 +701,18 @@ public class PopupWindow {
mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
if (mBackground != null) {
- mPopupView.refreshDrawableState();
+ // If the background drawable provided was a StateListDrawable with above-anchor
+ // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
+ // do the job.
+ if (mAboveAnchorBackgroundDrawable != null) {
+ if (mAboveAnchor) {
+ mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
+ } else {
+ mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
+ }
+ } else {
+ mPopupView.refreshDrawableState();
+ }
}
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
@@ -697,12 +748,18 @@ public class PopupWindow {
*/
private void preparePopup(WindowManager.LayoutParams p) {
if (mBackground != null) {
+ final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+ int height = ViewGroup.LayoutParams.FILL_PARENT;
+ if (layoutParams != null &&
+ layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+
// when a background is available, we embed the content view
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT
+ ViewGroup.LayoutParams.FILL_PARENT, height
);
popupViewContainer.setBackgroundDrawable(mBackground);
popupViewContainer.addView(mContentView, listParams);
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index dd2570a..f646ab5 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -526,6 +526,7 @@ public class ProgressBar extends View {
* @see #getProgress()
* @see #incrementProgressBy(int)
*/
+ @android.view.RemotableViewMethod
public synchronized void setProgress(int progress) {
setProgress(progress, false);
}
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index 9052ae3..a5dbd98 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -46,10 +46,30 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
public ResourceCursorAdapter(Context context, int layout, Cursor c) {
super(context, c);
mLayout = mDropDownLayout = layout;
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
+ * Constructor.
+ *
+ * @param context The context where the ListView associated with this
+ * SimpleListItemFactory is running
+ * @param layout resource identifier of a layout file that defines the views
+ * for this list item. Unless you override them later, this will
+ * define both the item views and the drop down views.
+ * @param c The cursor from which to get the data.
+ * @param autoRequery If true the adapter will call requery() on the
+ * cursor whenever it changes so the most recent
+ * data is always displayed.
+ * @hide Pending API Council approval
+ */
+ public ResourceCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
+ super(context, c, autoRequery);
+ mLayout = mDropDownLayout = layout;
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /**
* Inflates view(s) from the specified XML file.
*
* @see android.widget.CursorAdapter#newView(android.content.Context,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index c852be5..88b2a01 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -84,6 +84,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
+import android.view.ViewRoot;
import android.view.ViewTreeObserver;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
@@ -215,7 +216,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
int mDrawablePadding;
- };
+ }
private Drawables mDrawables;
private CharSequence mError;
@@ -239,8 +240,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mMarqueeRepeatLimit = 3;
class InputContentType {
- String privateContentType;
+ int imeOptions = EditorInfo.IME_UNDEFINED;
+ String privateImeOptions;
+ CharSequence imeActionLabel;
+ int imeActionId;
Bundle extras;
+ OnEditorActionListener onEditorActionListener;
+ boolean enterDown;
}
InputContentType mInputContentType;
@@ -268,6 +274,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
p.measureText("H");
}
+ /**
+ * Interface definition for a callback to be invoked when an action is
+ * performed on the editor.
+ */
+ public interface OnEditorActionListener {
+ /**
+ * Called when an action is being performed.
+ *
+ * @param v The view that was clicked.
+ * @param actionId Identifier of the action. This will be either the
+ * identifier you supplied, or {@link EditorInfo#IME_UNDEFINED
+ * EditorInfo.IME_UNDEFINED} if being called due to the enter key
+ * being pressed.
+ * @param event If triggered by an enter key, this is the event;
+ * otherwise, this is null.
+ * @return Return true if you have consumed the action, else false.
+ */
+ boolean onEditorAction(TextView v, int actionId, KeyEvent event);
+ }
+
public TextView(Context context) {
this(context, null);
}
@@ -376,7 +402,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int shadowcolor = 0;
float dx = 0, dy = 0, r = 0;
boolean password = false;
- int contentType = EditorInfo.TYPE_NULL;
+ int inputType = EditorInfo.TYPE_NULL;
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
@@ -610,11 +636,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_inputType:
- contentType = a.getInt(attr, mInputType);
+ inputType = a.getInt(attr, mInputType);
break;
- case com.android.internal.R.styleable.TextView_editorPrivateContentType:
- setPrivateContentType(a.getString(attr));
+ case com.android.internal.R.styleable.TextView_imeOptions:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeOptions = a.getInt(attr,
+ mInputContentType.imeOptions);
+ break;
+
+ case com.android.internal.R.styleable.TextView_imeActionLabel:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionLabel = a.getText(attr);
+ break;
+
+ case com.android.internal.R.styleable.TextView_imeActionId:
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionId = a.getInt(attr,
+ mInputContentType.imeActionId);
+ break;
+
+ case com.android.internal.R.styleable.TextView_privateImeOptions:
+ setPrivateImeOptions(a.getString(attr));
break;
case com.android.internal.R.styleable.TextView_editorExtras:
@@ -632,7 +681,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BufferType bufferType = BufferType.EDITABLE;
- if ((contentType&(EditorInfo.TYPE_MASK_CLASS
+ if ((inputType&(EditorInfo.TYPE_MASK_CLASS
|EditorInfo.TYPE_MASK_VARIATION))
== (EditorInfo.TYPE_CLASS_TEXT
|EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
@@ -656,57 +705,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
throw new RuntimeException(ex);
}
try {
- mInputType = contentType != EditorInfo.TYPE_NULL
- ? contentType
+ mInputType = inputType != EditorInfo.TYPE_NULL
+ ? inputType
: mInput.getInputType();
} catch (IncompatibleClassChangeError e) {
mInputType = EditorInfo.TYPE_CLASS_TEXT;
}
} else if (digits != null) {
mInput = DigitsKeyListener.getInstance(digits.toString());
- mInputType = contentType;
- } else if (contentType != EditorInfo.TYPE_NULL) {
- setInputType(contentType, true);
- singleLine = (contentType&(EditorInfo.TYPE_MASK_CLASS
+ mInputType = inputType;
+ } else if (inputType != EditorInfo.TYPE_NULL) {
+ setInputType(inputType, true);
+ singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) !=
(EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
} else if (phone) {
mInput = DialerKeyListener.getInstance();
- contentType = EditorInfo.TYPE_CLASS_PHONE;
+ inputType = EditorInfo.TYPE_CLASS_PHONE;
} else if (numeric != 0) {
mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
(numeric & DECIMAL) != 0);
- contentType = EditorInfo.TYPE_CLASS_NUMBER;
+ inputType = EditorInfo.TYPE_CLASS_NUMBER;
if ((numeric & SIGNED) != 0) {
- contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
+ inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
}
if ((numeric & DECIMAL) != 0) {
- contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
+ inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
}
- mInputType = contentType;
+ mInputType = inputType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
- contentType = EditorInfo.TYPE_CLASS_TEXT;
+ inputType = EditorInfo.TYPE_CLASS_TEXT;
if (!singleLine) {
- contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
}
switch (autocap) {
case 1:
cap = TextKeyListener.Capitalize.SENTENCES;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
break;
case 2:
cap = TextKeyListener.Capitalize.WORDS;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
break;
case 3:
cap = TextKeyListener.Capitalize.CHARACTERS;
- contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+ inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
break;
default:
@@ -715,7 +764,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mInput = TextKeyListener.getInstance(autotext, cap);
- mInputType = contentType;
+ mInputType = inputType;
} else if (editable) {
mInput = TextKeyListener.getInstance();
mInputType = EditorInfo.TYPE_CLASS_TEXT;
@@ -1075,6 +1124,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_singleLine
*/
public final void setTransformationMethod(TransformationMethod method) {
+ if (method == mTransformation) {
+ // Avoid the setText() below if the transformation is
+ // the same.
+ return;
+ }
if (mTransformation != null) {
if (mText instanceof Spannable) {
((Spannable) mText).removeSpan(mTransformation);
@@ -2778,7 +2832,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Directly change the content type integer of the text view, without
* modifying any other state.
- * @see #setContentType
+ * @see #setInputType(int)
* @see android.text.InputType
* @attr ref android.R.styleable#TextView_inputType
*/
@@ -2842,28 +2896,159 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Change the editor type integer associated with the text view, which
+ * will be reported to an IME with {@link EditorInfo#imeOptions} when it
+ * has focus.
+ * @see #getImeOptions
+ * @see android.view.inputmethod.EditorInfo
+ * @attr ref android.R.styleable#TextView_imeOptions
+ */
+ public void setImeOptions(int imeOptions) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeOptions = imeOptions;
+ }
+
+ /**
+ * Get the type of the IME editor.
+ *
+ * @see #setImeOptions(int)
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public int getImeOptions() {
+ return mInputContentType != null
+ ? mInputContentType.imeOptions : EditorInfo.IME_UNDEFINED;
+ }
+
+ /**
+ * Change the custom IME action associated with the text view, which
+ * will be reported to an IME with {@link EditorInfo#actionLabel}
+ * and {@link EditorInfo#actionId} when it has focus.
+ * @see #getImeActionLabel
+ * @see #getImeActionId
+ * @see android.view.inputmethod.EditorInfo
+ * @attr ref android.R.styleable#TextView_imeActionLabel
+ * @attr ref android.R.styleable#TextView_imeActionId
+ */
+ public void setImeActionLabel(CharSequence label, int actionId) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.imeActionLabel = label;
+ mInputContentType.imeActionId = actionId;
+ }
+
+ /**
+ * Get the IME action label previous set with {@link #setImeActionLabel}.
+ *
+ * @see #setImeActionLabel
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public CharSequence getImeActionLabel() {
+ return mInputContentType != null
+ ? mInputContentType.imeActionLabel : null;
+ }
+
+ /**
+ * Get the IME action ID previous set with {@link #setImeActionLabel}.
+ *
+ * @see #setImeActionLabel
+ * @see android.view.inputmethod.EditorInfo
+ */
+ public int getImeActionId() {
+ return mInputContentType != null
+ ? mInputContentType.imeActionId : 0;
+ }
+
+ /**
+ * Set a special OnClickListener to be called when an action is performed
+ * on the text view. This will be called when the enter key is pressed,
+ * or when an action supplied to the IME is selected by the user.
+ */
+ public void setOnEditorActionListener(OnEditorActionListener l) {
+ if (mInputContentType == null) {
+ mInputContentType = new InputContentType();
+ }
+ mInputContentType.onEditorActionListener = l;
+ }
+
+ /**
+ * Called when an attached input method calls
+ * {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction()}
+ * for this text view. The default implementation will call your click
+ * listener supplied to {@link #setOnEditorActionListener},
+ * or generate an enter key down/up pair to invoke the action if not.
+ *
+ * @param actionCode The code of the action being performed.
+ *
+ * @see #setOnEditorActionListener
+ */
+ public void onEditorAction(int actionCode) {
+ final InputContentType ict = mInputContentType;
+ if (ict != null) {
+ if (ict.onEditorActionListener != null) {
+ if (ict.onEditorActionListener.onEditorAction(this,
+ actionCode, null)) {
+ return;
+ }
+ }
+ }
+
+ if (actionCode == EditorInfo.IME_ACTION_NEXT &&
+ (ict != null || !shouldAdvanceFocusOnEnter())) {
+ // This is the default handling for the NEXT action, to advance
+ // focus. Note that for backwards compatibility we don't do this
+ // default handling if explicit ime options have not been given,
+ // and we do not advance by default on an enter key -- in that
+ // case, we want to turn this into the normal enter key codes that
+ // an app may be expecting.
+ View v = focusSearch(FOCUS_DOWN);
+ if (v != null) {
+ if (!v.requestFocus(FOCUS_DOWN)) {
+ throw new IllegalStateException("focus search returned a view " +
+ "that wasn't able to take focus!");
+ }
+ }
+ return;
+ }
+
+ Handler h = getHandler();
+ long eventTime = SystemClock.uptimeMillis();
+ h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ }
+
+ /**
* Set the private content type of the text, which is the
- * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType}
+ * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
* field that will be filled in when creating an input connection.
*
- * @see #getPrivateContentType()
- * @see EditorInfo#privateContentType
- * @attr ref android.R.styleable#TextView_editorPrivateContentType
+ * @see #getPrivateImeOptions()
+ * @see EditorInfo#privateImeOptions
+ * @attr ref android.R.styleable#TextView_privateImeOptions
*/
- public void setPrivateContentType(String type) {
+ public void setPrivateImeOptions(String type) {
if (mInputContentType == null) mInputContentType = new InputContentType();
- mInputContentType.privateContentType = type;
+ mInputContentType.privateImeOptions = type;
}
/**
* Get the private type of the content.
*
- * @see #setPrivateContentType(String)
- * @see EditorInfo#privateContentType
+ * @see #setPrivateImeOptions(String)
+ * @see EditorInfo#privateImeOptions
*/
- public String getPrivateContentType() {
+ public String getPrivateImeOptions() {
return mInputContentType != null
- ? mInputContentType.privateContentType : null;
+ ? mInputContentType.privateImeOptions : null;
}
/**
@@ -3807,7 +3992,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* but also in mail addresses and subjects which will display on multiple
* lines but where it doesn't make sense to insert newlines.
*/
- private boolean advanceFocusOnEnter() {
+ protected boolean shouldAdvanceFocusOnEnter() {
if (mInput == null) {
return false;
}
@@ -3828,15 +4013,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
+ private boolean isInterestingEnter(KeyEvent event) {
+ if ((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 &&
+ mInputContentType != null &&
+ (mInputContentType.imeOptions &
+ EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+ // If this enter key came from a soft keyboard, and the
+ // text editor has been configured to not do a default
+ // action for software enter keys, then we aren't interested.
+ return false;
+ }
+ return true;
+ }
+
private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
if (!isEnabled()) {
return 0;
}
switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
- if (advanceFocusOnEnter()) {
+ if (!isInterestingEnter(event)) {
+ // Ignore enter key we aren't interested in.
+ return -1;
+ }
+ if (mInputContentType != null
+ && mInputContentType.onEditorActionListener != null) {
+ mInputContentType.enterDown = true;
+ }
+ // fall through...
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (shouldAdvanceFocusOnEnter()) {
return 0;
}
}
@@ -3939,7 +4146,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
case KeyEvent.KEYCODE_ENTER:
- if (advanceFocusOnEnter()) {
+ if (mInputContentType != null
+ && mInputContentType.onEditorActionListener != null
+ && mInputContentType.enterDown) {
+ mInputContentType.enterDown = false;
+ if (mInputContentType.onEditorActionListener.onEditorAction(
+ this, EditorInfo.IME_UNDEFINED, event)) {
+ return true;
+ }
+ }
+
+ if (shouldAdvanceFocusOnEnter()) {
/*
* If there is a click listener, just call through to
* super, which will invoke it.
@@ -3994,11 +4211,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mInputMethodState = new InputMethodState();
}
outAttrs.inputType = mInputType;
- outAttrs.hintText = mHint;
if (mInputContentType != null) {
- outAttrs.privateContentType = mInputContentType.privateContentType;
+ outAttrs.imeOptions = mInputContentType.imeOptions;
+ outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
+ outAttrs.actionLabel = mInputContentType.imeActionLabel;
+ outAttrs.actionId = mInputContentType.imeActionId;
outAttrs.extras = mInputContentType.extras;
+ } else {
+ outAttrs.imeOptions = EditorInfo.IME_UNDEFINED;
+ }
+ if (outAttrs.imeOptions == EditorInfo.IME_UNDEFINED) {
+ if (focusSearch(FOCUS_DOWN) != null) {
+ // An action has not been set, but the enter key will move to
+ // the next focus, so set the action to that.
+ outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
+ if (!shouldAdvanceFocusOnEnter()) {
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+ }
+ }
}
+ outAttrs.hintText = mHint;
if (mText instanceof Editable) {
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = Selection.getSelectionStart(mText);
@@ -5787,6 +6019,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Don't leave us in the middle of a batch edit.
onEndBatchEdit();
+ if (mInputContentType != null) {
+ mInputContentType.enterDown = false;
+ }
}
startStopMarquee(hasWindowFocus);
@@ -5880,8 +6115,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mScroller = s;
}
- private static class Blink extends Handler
- implements Runnable {
+ private static class Blink extends Handler implements Runnable {
private WeakReference<TextView> mView;
private boolean mCancelled;
@@ -6139,23 +6373,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
int start = end;
- char c;
int len = mText.length();
- while (start > 0 && (((c = mTransformed.charAt(start - 1)) == '\'') ||
- (Character.isLetterOrDigit(c)))) {
- start--;
+ for (; start > 0; start--) {
+ char c = mTransformed.charAt(start - 1);
+ int type = Character.getType(c);
+
+ if (c != '\'' &&
+ type != Character.UPPERCASE_LETTER &&
+ type != Character.LOWERCASE_LETTER &&
+ type != Character.TITLECASE_LETTER &&
+ type != Character.MODIFIER_LETTER &&
+ type != Character.DECIMAL_DIGIT_NUMBER) {
+ break;
+ }
}
- while (end < len && (((c = mTransformed.charAt(end)) == '\'') ||
- (Character.isLetterOrDigit(c)))) {
- end++;
+ for (; end < len; end++) {
+ char c = mTransformed.charAt(end);
+ int type = Character.getType(c);
+
+ if (c != '\'' &&
+ type != Character.UPPERCASE_LETTER &&
+ type != Character.LOWERCASE_LETTER &&
+ type != Character.TITLECASE_LETTER &&
+ type != Character.MODIFIER_LETTER &&
+ type != Character.DECIMAL_DIGIT_NUMBER) {
+ break;
+ }
}
if (start == end) {
return null;
}
+ if (end - start > 48) {
+ return null;
+ }
+
return TextUtils.substring(mTransformed, start, end);
}
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
new file mode 100644
index 0000000..ec45e23
--- /dev/null
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2008 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 android.widget;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.View.OnClickListener;
+import android.view.WindowManager.LayoutParams;
+
+// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration)
+
+/**
+ * TODO: Docs
+ *
+ * If you are using this with a custom View, please call
+ * {@link #setVisible(boolean) setVisible(false)} from the
+ * {@link View#onDetachedFromWindow}.
+ *
+ * @hide
+ */
+public class ZoomButtonsController implements View.OnTouchListener {
+
+ private static final String TAG = "ZoomButtonsController";
+
+ private static final int ZOOM_CONTROLS_TIMEOUT =
+ (int) ViewConfiguration.getZoomControlsTimeout();
+
+ // TODO: scaled to density
+ private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
+
+ private Context mContext;
+ private WindowManager mWindowManager;
+
+ /**
+ * The view that is being zoomed by this zoom ring.
+ */
+ private View mOwnerView;
+
+ /**
+ * The bounds of the owner view in global coordinates. This is recalculated
+ * each time the zoom ring is shown.
+ */
+ private Rect mOwnerViewBounds = new Rect();
+
+ /**
+ * The container that is added as a window.
+ */
+ private FrameLayout mContainer;
+ private LayoutParams mContainerLayoutParams;
+ private int[] mContainerLocation = new int[2];
+
+ private ZoomControls mControls;
+
+ /**
+ * The view (or null) that should receive touch events. This will get set if
+ * the touch down hits the container. It will be reset on the touch up.
+ */
+ private View mTouchTargetView;
+ /**
+ * The {@link #mTouchTargetView}'s location in window, set on touch down.
+ */
+ private int[] mTouchTargetLocationInWindow = new int[2];
+ /**
+ * If the zoom ring is dismissed but the user is still in a touch
+ * interaction, we set this to true. This will ignore all touch events until
+ * up/cancel, and then set the owner's touch listener to null.
+ */
+ private boolean mReleaseTouchListenerOnUp;
+
+ private boolean mIsVisible;
+
+ private Rect mTempRect = new Rect();
+
+ private OnZoomListener mCallback;
+
+ /**
+ * When showing the zoom, we add the view as a new window. However, there is
+ * logic that needs to know the size of the zoom which is determined after
+ * it's laid out. Therefore, we must post this logic onto the UI thread so
+ * it will be exceuted AFTER the layout. This is the logic.
+ */
+ private Runnable mPostedVisibleInitializer;
+
+ private IntentFilter mConfigurationChangedFilter =
+ new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+
+ private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!mIsVisible) return;
+
+ mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
+ mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
+ }
+ };
+
+ /** When configuration changes, this is called after the UI thread is idle. */
+ private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
+ /** Used to delay the zoom ring dismissal. */
+ private static final int MSG_DISMISS_ZOOM_RING = 3;
+ /**
+ * If setVisible(true) is called and the owner view's window token is null,
+ * we delay the setVisible(true) call until it is not null.
+ */
+ private static final int MSG_POST_SET_VISIBLE = 4;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_POST_CONFIGURATION_CHANGED:
+ onPostConfigurationChanged();
+ break;
+
+ case MSG_DISMISS_ZOOM_RING:
+ setVisible(false);
+ break;
+
+ case MSG_POST_SET_VISIBLE:
+ if (mOwnerView.getWindowToken() == null) {
+ // Doh, it is still null, throw an exception
+ throw new IllegalArgumentException(
+ "Cannot make the zoom ring visible if the owner view is " +
+ "not attached to a window.");
+ }
+ setVisible(true);
+ break;
+ }
+
+ }
+ };
+
+ public ZoomButtonsController(Context context, View ownerView) {
+ mContext = context;
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mOwnerView = ownerView;
+
+ mContainer = createContainer();
+ }
+
+ private FrameLayout createContainer() {
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ lp.gravity = Gravity.BOTTOM | Gravity.CENTER;
+ lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
+ LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ lp.width = LayoutParams.FILL_PARENT;
+ lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
+ lp.format = PixelFormat.TRANSPARENT;
+ // TODO: make a new animation for this
+ lp.windowAnimations = com.android.internal.R.style.Animation_InputMethodFancy;
+ mContainerLayoutParams = lp;
+
+ FrameLayout container = new FrameLayout(mContext);
+ container.setLayoutParams(lp);
+ container.setMeasureAllChildren(true);
+
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(com.android.internal.R.layout.zoom_magnify, container);
+
+ mControls = (ZoomControls) container.findViewById(com.android.internal.R.id.zoomControls);
+ mControls.setOnZoomInClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onZoom(true);
+ }
+ });
+ mControls.setOnZoomOutClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onZoom(false);
+ }
+ });
+
+ View overview = container.findViewById(com.android.internal.R.id.zoomMagnify);
+ overview.setVisibility(View.GONE);
+ overview.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ if (mCallback != null) mCallback.onOverview();
+ }
+ });
+
+ return container;
+ }
+
+ public void setCallback(OnZoomListener callback) {
+ mCallback = callback;
+ }
+
+ public void setFocusable(boolean focusable) {
+ if (focusable) {
+ mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+
+ if (mIsVisible) {
+ mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ }
+ }
+
+ public void setOverviewVisible(boolean visible) {
+ mContainer.findViewById(com.android.internal.R.id.zoomMagnify)
+ .setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ public boolean isVisible() {
+ return mIsVisible;
+ }
+
+ public void setVisible(boolean visible) {
+
+ if (!useThisZoom(mContext)) return;
+
+ if (visible) {
+ if (mOwnerView.getWindowToken() == null) {
+ /*
+ * We need a window token to show ourselves, maybe the owner's
+ * window hasn't been created yet but it will have been by the
+ * time the looper is idle, so post the setVisible(true) call.
+ */
+ if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
+ mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
+ }
+ return;
+ }
+
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ }
+
+ if (mIsVisible == visible) {
+ return;
+ }
+ mIsVisible = visible;
+
+ if (visible) {
+ if (mContainerLayoutParams.token == null) {
+ mContainerLayoutParams.token = mOwnerView.getWindowToken();
+ }
+
+ mWindowManager.addView(mContainer, mContainerLayoutParams);
+
+ if (mPostedVisibleInitializer == null) {
+ mPostedVisibleInitializer = new Runnable() {
+ public void run() {
+ refreshPositioningVariables();
+
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(true);
+ }
+ }
+ };
+ }
+
+ mHandler.post(mPostedVisibleInitializer);
+
+ // Handle configuration changes when visible
+ mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
+
+ // Steal touches events from the owner
+ mOwnerView.setOnTouchListener(this);
+ mReleaseTouchListenerOnUp = false;
+
+ } else {
+ // Don't want to steal any more touches
+ if (mTouchTargetView != null) {
+ // We are still stealing the touch events for this touch
+ // sequence, so release the touch listener later
+ mReleaseTouchListenerOnUp = true;
+ } else {
+ mOwnerView.setOnTouchListener(null);
+ }
+
+ // No longer care about configuration changes
+ mContext.unregisterReceiver(mConfigurationChangedReceiver);
+
+ mWindowManager.removeView(mContainer);
+ mHandler.removeCallbacks(mPostedVisibleInitializer);
+
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(false);
+ }
+ }
+
+ }
+
+ /**
+ * TODO: docs
+ *
+ * Notes:
+ * - Please ensure you set your View to INVISIBLE not GONE when hiding it.
+ *
+ * @return TODO
+ */
+ public FrameLayout getContainer() {
+ return mContainer;
+ }
+
+ public int getZoomRingId() {
+ return mControls.getId();
+ }
+
+ private void dismissControlsDelayed(int delay) {
+ mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+ mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay);
+ }
+
+ /**
+ * Should be called by the client for each event belonging to the second tap
+ * (the down, move, up, and cancel events).
+ *
+ * @param event The event belonging to the second tap.
+ * @return Whether the event was consumed.
+ */
+ public boolean handleDoubleTapEvent(MotionEvent event) {
+ if (!useThisZoom(mContext)) return false;
+
+ int action = event.getAction();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ setVisible(true);
+ centerPoint(x, y);
+ }
+
+ return true;
+ }
+
+ private void refreshPositioningVariables() {
+ // Calculate the owner view's bounds
+ mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
+ mContainer.getLocationOnScreen(mContainerLocation);
+ }
+
+ /**
+ * Centers the point (in owner view's coordinates).
+ */
+ private void centerPoint(int x, int y) {
+ if (mCallback != null) {
+ mCallback.onCenter(x, y);
+ }
+ }
+
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getAction();
+
+ if (mReleaseTouchListenerOnUp) {
+ // The ring was dismissed but we need to throw away all events until the up
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mOwnerView.setOnTouchListener(null);
+ setTouchTargetView(null);
+ mReleaseTouchListenerOnUp = false;
+ }
+
+ // Eat this event
+ return true;
+ }
+
+ // TODO: optimize this (it ends up removing message and queuing another)
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+
+ View targetView = mTouchTargetView;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY());
+ setTouchTargetView(targetView);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ setTouchTargetView(null);
+ break;
+ }
+
+ if (targetView != null) {
+ // The upperleft corner of the target view in raw coordinates
+ int targetViewRawX = mContainerLocation[0] + mTouchTargetLocationInWindow[0];
+ int targetViewRawY = mContainerLocation[1] + mTouchTargetLocationInWindow[1];
+
+ MotionEvent containerEvent = MotionEvent.obtain(event);
+ // Convert the motion event into the target view's coordinates (from
+ // owner view's coordinates)
+ containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX,
+ mOwnerViewBounds.top - targetViewRawY);
+ boolean retValue = targetView.dispatchTouchEvent(containerEvent);
+ containerEvent.recycle();
+ return retValue;
+
+ } else {
+ return false;
+ }
+ }
+
+ private void setTouchTargetView(View view) {
+ mTouchTargetView = view;
+ if (view != null) {
+ view.getLocationInWindow(mTouchTargetLocationInWindow);
+ }
+ }
+
+ /**
+ * Returns the View that should receive a touch at the given coordinates.
+ *
+ * @param rawX The raw X.
+ * @param rawY The raw Y.
+ * @return The view that should receive the touches, or null if there is not one.
+ */
+ private View getViewForTouch(int rawX, int rawY) {
+ // Reverse order so the child drawn on top gets first dibs.
+ int containerCoordsX = rawX - mContainerLocation[0];
+ int containerCoordsY = rawY - mContainerLocation[1];
+ Rect frame = mTempRect;
+ for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mContainer.getChildAt(i);
+ if (child.getVisibility() != View.VISIBLE) {
+ continue;
+ }
+
+ child.getHitRect(frame);
+ // Expand the touch region
+ frame.top -= ZOOM_CONTROLS_TOUCH_PADDING;
+ if (frame.contains(containerCoordsX, containerCoordsY)) {
+ return child;
+ }
+ }
+
+ return null;
+ }
+
+ private void onPostConfigurationChanged() {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ refreshPositioningVariables();
+ }
+
+ public static boolean useThisZoom(Context context) {
+ return ZoomRingController.getZoomType(context) == 2;
+ }
+
+ public interface OnZoomListener {
+ void onCenter(int x, int y);
+ void onVisibilityChanged(boolean visible);
+ void onZoom(boolean zoomIn);
+ void onOverview();
+ }
+}
diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java
index a29e1a0..a5a867b 100644
--- a/core/java/android/widget/ZoomRing.java
+++ b/core/java/android/widget/ZoomRing.java
@@ -77,7 +77,7 @@ public class ZoomRing extends View {
private int mPreviousWidgetDragX;
private int mPreviousWidgetDragY;
- private boolean mDrawThumb = true;
+ private boolean mThumbVisible = true;
private Drawable mThumbDrawable;
/** Shown beneath the thumb if we can still zoom in. */
@@ -91,6 +91,13 @@ public class ZoomRing extends View {
private static final int THUMB_ARROWS_FADE_DURATION = 300;
private long mThumbArrowsFadeStartTime;
private int mThumbArrowsAlpha = 255;
+
+ private static final int THUMB_PLUS_MINUS_DISTANCE = 69;
+ private static final int THUMB_PLUS_MINUS_OFFSET_ANGLE = TWO_PI_INT_MULTIPLIED / 11;
+ /** Drawn (without rotation) on top of the arrow. */
+ private Drawable mThumbPlusDrawable;
+ /** Drawn (without rotation) on top of the arrow. */
+ private Drawable mThumbMinusDrawable;
private static final int MODE_IDLE = 0;
@@ -99,7 +106,7 @@ public class ZoomRing extends View {
* are waiting for him to move the slop amount before considering him in the
* drag thumb state.
*/
- private static final int MODE_WAITING_FOR_DRAG_THUMB = 5;
+ private static final int MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP = 5;
private static final int MODE_DRAG_THUMB = 1;
/**
* User has his finger down, but we are waiting for him to pass the touch
@@ -109,11 +116,14 @@ public class ZoomRing extends View {
private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4;
private static final int MODE_MOVE_ZOOM_RING = 2;
private static final int MODE_TAP_DRAG = 3;
- /** Ignore the touch interaction. Reset to MODE_IDLE after up/cancel. */
- private static final int MODE_IGNORE_UNTIL_UP = 6;
+ /** Ignore the touch interaction until the user touches the thumb again. */
+ private static final int MODE_IGNORE_UNTIL_TOUCHES_THUMB = 6;
private int mMode;
-
- private long mPreviousUpTime;
+
+ /** Records the last mode the user was in. */
+ private int mPreviousMode;
+
+ private long mPreviousCenterUpTime;
private int mPreviousDownX;
private int mPreviousDownY;
@@ -122,7 +132,9 @@ public class ZoomRing extends View {
private OnZoomRingCallback mCallback;
private int mPreviousCallbackAngle;
private int mCallbackThreshold = Integer.MAX_VALUE;
-
+ /** If the user drags to within __% of a tick, snap to that tick. */
+ private int mFuzzyCallbackThreshold = Integer.MAX_VALUE;
+
private boolean mResetThumbAutomatically = true;
private int mThumbDragStartAngle;
@@ -133,6 +145,8 @@ public class ZoomRing extends View {
private Scroller mThumbScroller;
+ private boolean mVibration = true;
+
private static final int MSG_THUMB_SCROLLER_TICK = 1;
private static final int MSG_THUMB_ARROWS_FADE_TICK = 2;
private Handler mHandler = new Handler() {
@@ -163,6 +177,8 @@ public class ZoomRing extends View {
mutate();
mThumbMinusArrowDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus_arrow_rotatable).
mutate();
+ mThumbPlusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_plus);
+ mThumbMinusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus);
if (DRAW_TRAIL) {
mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate();
}
@@ -175,7 +191,7 @@ public class ZoomRing extends View {
mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2;
mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2;
- mCallbackThreshold = PI_INT_MULTIPLIED / 6;
+ setCallbackThreshold(PI_INT_MULTIPLIED / 6);
}
public ZoomRing(Context context, AttributeSet attrs) {
@@ -193,8 +209,20 @@ public class ZoomRing extends View {
// TODO: rename
public void setCallbackThreshold(int callbackThreshold) {
mCallbackThreshold = callbackThreshold;
+ mFuzzyCallbackThreshold = (int) (callbackThreshold * 0.65f);
}
+ public void setVibration(boolean vibrate) {
+ mVibration = vibrate;
+ }
+
+ public void setThumbVisible(boolean thumbVisible) {
+ if (mThumbVisible != thumbVisible) {
+ mThumbVisible = thumbVisible;
+ invalidate();
+ }
+ }
+
// TODO: from XML too
public void setRingBounds(int innerRadius, int outerRadius) {
mBoundInnerRadiusSquared = innerRadius * innerRadius;
@@ -306,15 +334,7 @@ public class ZoomRing extends View {
public void setThumbAngleAnimated(int angle, int duration) {
// The angle when going from the current angle to the new angle
int deltaAngle = getDelta(mThumbAngle, angle);
- // Counter clockwise if the new angle is more the current angle
- boolean counterClockwise = deltaAngle > 0;
-
- if (deltaAngle > PI_INT_MULTIPLIED || deltaAngle < -PI_INT_MULTIPLIED) {
- // It's quicker to go the other direction
- counterClockwise = !counterClockwise;
- }
-
- setThumbAngleAnimated(angle, duration, counterClockwise);
+ setThumbAngleAnimated(angle, duration, deltaAngle > 0);
}
public void setThumbAngleAnimated(int angle, int duration, boolean counterClockwise) {
@@ -354,14 +374,10 @@ public class ZoomRing extends View {
return mThumbScroller.getCurrX() % TWO_PI_INT_MULTIPLIED;
}
- public void resetThumbAngle(int angle) {
- mPreviousCallbackAngle = angle;
- setThumbAngleInt(angle);
- }
-
public void resetThumbAngle() {
if (mResetThumbAutomatically) {
- resetThumbAngle(0);
+ mPreviousCallbackAngle = 0;
+ setThumbAngleInt(0);
}
}
@@ -394,101 +410,119 @@ public class ZoomRing extends View {
mTrail.setBounds(0, 0, right - left, bottom - top);
}
+ // These drawables are the same size as the track
mThumbPlusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
mThumbMinusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
+// Log.d(TAG, "History size: " + event.getHistorySize());
+
return handleTouch(event.getAction(), event.getEventTime(),
(int) event.getX(), (int) event.getY(), (int) event.getRawX(),
(int) event.getRawY());
}
- private void resetState() {
- mMode = MODE_IDLE;
+ private void resetToIdle() {
+ setMode(MODE_IDLE);
mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE;
mAcculumalatedTrailAngle = 0.0;
}
public void setTapDragMode(boolean tapDragMode, int x, int y) {
- resetState();
- mMode = tapDragMode ? MODE_TAP_DRAG : MODE_IDLE;
-
+ resetToIdle();
if (tapDragMode) {
+ setMode(MODE_TAP_DRAG);
+ mCallback.onUserInteractionStarted();
onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY));
+ } else {
+ onTouchUp(SystemClock.elapsedRealtime(), true);
}
}
public boolean handleTouch(int action, long time, int x, int y, int rawX, int rawY) {
- switch (action) {
+ // local{X,Y} will be where the center of the widget is (0,0)
+ int localX = x - mCenterX;
+ int localY = y - mCenterY;
+
+ /*
+ * If we are not drawing the thumb, there is no way for the user to be
+ * touching the thumb. Also, if this is the case, assume they are not
+ * touching the ring (so the user cannot absolute set the thumb, and
+ * there will be a larger touch region for going into the move-ring
+ * mode).
+ */
+ boolean isTouchingThumb = mThumbVisible;
+ boolean isTouchingRing = mThumbVisible;
+
+ int touchAngle = getAngle(localX, localY);
+// printAngle("touchAngle", touchAngle);
+// printAngle("mThumbAngle", mThumbAngle);
+// printAngle("mPreviousCallbackAngle", mPreviousCallbackAngle);
+// Log.d(TAG, "");
+
+
+ int radiusSquared = localX * localX + localY * localY;
+ if (radiusSquared < mBoundInnerRadiusSquared ||
+ radiusSquared > mBoundOuterRadiusSquared) {
+ // Out-of-bounds
+ isTouchingThumb = false;
+ isTouchingRing = false;
+ }
+
+ if (isTouchingThumb) {
+ int deltaThumbAndTouch = getDelta(mThumbAngle, touchAngle);
+ int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ?
+ deltaThumbAndTouch : -deltaThumbAndTouch;
+ if (absoluteDeltaThumbAndTouch > THUMB_GRAB_SLOP) {
+ // Didn't grab close enough to the thumb
+ isTouchingThumb = false;
+ }
+ }
+ switch (action) {
case MotionEvent.ACTION_DOWN:
- if (time - mPreviousUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT) {
+ if (!isTouchingRing &&
+ (time - mPreviousCenterUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT)) {
+ // Make sure the double-tap is in the center of the widget (and not on the ring)
mCallback.onZoomRingDismissed(true);
- onTouchUp(time);
+ onTouchUp(time, isTouchingRing);
// Dismissing, so halt here
return true;
}
+ resetToIdle();
mCallback.onUserInteractionStarted();
mPreviousDownX = x;
mPreviousDownY = y;
- resetState();
// Fall through to code below switch (since the down is used for
// jumping to the touched tick)
break;
case MotionEvent.ACTION_MOVE:
- if (mMode == MODE_IGNORE_UNTIL_UP) return true;
-
// Fall through to code below switch
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- onTouchUp(time);
+ onTouchUp(time, isTouchingRing);
return true;
default:
return false;
}
- // local{X,Y} will be where the center of the widget is (0,0)
- int localX = x - mCenterX;
- int localY = y - mCenterY;
- boolean isTouchingThumb = true;
- boolean isInRingBounds = true;
-
- int touchAngle = getAngle(localX, localY);
- int radiusSquared = localX * localX + localY * localY;
- if (radiusSquared < mBoundInnerRadiusSquared ||
- radiusSquared > mBoundOuterRadiusSquared) {
- // Out-of-bounds
- isTouchingThumb = false;
- isInRingBounds = false;
- }
-
- int deltaThumbAndTouch = getDelta(mThumbAngle, touchAngle);
- int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ?
- deltaThumbAndTouch : -deltaThumbAndTouch;
- if (isTouchingThumb &&
- absoluteDeltaThumbAndTouch > THUMB_GRAB_SLOP) {
- // Didn't grab close enough to the thumb
- isTouchingThumb = false;
- }
-
if (mMode == MODE_IDLE) {
if (isTouchingThumb) {
// They grabbed the thumb
- mMode = MODE_DRAG_THUMB;
+ setMode(MODE_DRAG_THUMB);
onThumbDragStarted(touchAngle);
- } else if (isInRingBounds) {
+ } else if (isTouchingRing) {
// They tapped somewhere else on the ring
int tickAngle = getClosestTickAngle(touchAngle);
-
int deltaThumbAndTick = getDelta(mThumbAngle, tickAngle);
int boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
@@ -497,12 +531,12 @@ public class ZoomRing extends View {
if (deltaThumbAndTick > MAX_ABS_JUMP_DELTA_ANGLE ||
deltaThumbAndTick < -MAX_ABS_JUMP_DELTA_ANGLE) {
// Trying to jump too far, ignore this touch interaction
- mMode = MODE_IGNORE_UNTIL_UP;
+ setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
return true;
}
- // Make sure we only let them jump within bounds
if (boundAngle != Integer.MIN_VALUE) {
+ // Cap the user's jump to the bound
tickAngle = boundAngle;
}
} else {
@@ -515,47 +549,59 @@ public class ZoomRing extends View {
deltaThumbAndTick = getDelta(mThumbAngle, tickAngle, !oldDirectionIsCcw);
boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
if (boundAngle != Integer.MIN_VALUE) {
- // Not allowed to be here, it is between two bounds
- mMode = MODE_IGNORE_UNTIL_UP;
+ // Cannot get to the tapped location because it is out-of-bounds
+ setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
return true;
}
}
}
- mMode = MODE_WAITING_FOR_DRAG_THUMB;
+ setMode(MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP);
mWaitingForDragThumbDownAngle = touchAngle;
boolean ccw = deltaThumbAndTick > 0;
setThumbAngleAnimated(tickAngle, 0, ccw);
- // Our thumb scrolling animation takes us from mThumbAngle to tickAngle
+ /*
+ * Our thumb scrolling animation takes us from mThumbAngle to
+ * tickAngle, so manifest that as the user dragging the thumb
+ * there.
+ */
onThumbDragStarted(mThumbAngle);
+ // We know which direction we want to go
onThumbDragged(tickAngle, true, ccw);
} else {
- // They tapped somewhere else
- mMode = MODE_WAITING_FOR_MOVE_ZOOM_RING;
+ // They tapped somewhere else on the widget
+ setMode(MODE_WAITING_FOR_MOVE_ZOOM_RING);
mCallback.onZoomRingSetMovableHintVisible(true);
}
- } else if (mMode == MODE_WAITING_FOR_DRAG_THUMB) {
+ } else if (mMode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) {
int deltaDownAngle = getDelta(mWaitingForDragThumbDownAngle, touchAngle);
if ((deltaDownAngle < -THUMB_DRAG_SLOP || deltaDownAngle > THUMB_DRAG_SLOP) &&
isDeltaInBounds(mWaitingForDragThumbDownAngle, deltaDownAngle)) {
- mMode = MODE_DRAG_THUMB;
+ setMode(MODE_DRAG_THUMB);
+
+ // No need to call onThumbDragStarted, since that was done when they tapped-to-jump
}
} else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
if (Math.abs(x - mPreviousDownX) > mTouchSlop ||
Math.abs(y - mPreviousDownY) > mTouchSlop) {
/* Make sure the user has moved the slop amount before going into that mode. */
- mMode = MODE_MOVE_ZOOM_RING;
+ setMode(MODE_MOVE_ZOOM_RING);
mCallback.onZoomRingMovingStarted();
}
+ } else if (mMode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) {
+ if (isTouchingThumb) {
+ // The user is back on the thumb, let's go back to the previous mode
+ setMode(mPreviousMode);
+ }
}
// Purposefully not an "else if"
if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) {
- if (isInRingBounds) {
+ if (isTouchingRing) {
onThumbDragged(touchAngle, false, false);
}
} else if (mMode == MODE_MOVE_ZOOM_RING) {
@@ -565,24 +611,39 @@ public class ZoomRing extends View {
return true;
}
- private void onTouchUp(long time) {
- if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
+ private void onTouchUp(long time, boolean isTouchingRing) {
+ int mode = mMode;
+ if (mode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) {
+ // For cleaning up, pretend like the user was still in the previous mode
+ mode = mPreviousMode;
+ }
+
+ if (mode == MODE_MOVE_ZOOM_RING || mode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
mCallback.onZoomRingSetMovableHintVisible(false);
- if (mMode == MODE_MOVE_ZOOM_RING) {
+ if (mode == MODE_MOVE_ZOOM_RING) {
mCallback.onZoomRingMovingStopped();
}
- } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG ||
- mMode == MODE_WAITING_FOR_DRAG_THUMB) {
+ } else if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG ||
+ mode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) {
onThumbDragStopped();
- if (mMode == MODE_DRAG_THUMB) {
+ if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG) {
// Animate back to a tick
setThumbAngleAnimated(mPreviousCallbackAngle, 0);
}
}
-
- mPreviousUpTime = time;
mCallback.onUserInteractionStopped();
+
+ if (!isTouchingRing) {
+ mPreviousCenterUpTime = time;
+ }
+ }
+
+ private void setMode(int mode) {
+ if (mode != mMode) {
+ mPreviousMode = mMode;
+ mMode = mode;
+ }
}
private boolean isDeltaInBounds(int startAngle, int deltaAngle) {
@@ -681,9 +742,8 @@ public class ZoomRing extends View {
int totalDeltaAngle;
totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
- int fuzzyCallbackThreshold = (int) (mCallbackThreshold * 0.65f);
- if (totalDeltaAngle >= fuzzyCallbackThreshold
- || totalDeltaAngle <= -fuzzyCallbackThreshold) {
+ if (totalDeltaAngle >= mFuzzyCallbackThreshold
+ || totalDeltaAngle <= -mFuzzyCallbackThreshold) {
if (!useDirection) {
// Set ccw to match the direction found by getDelta
@@ -737,7 +797,7 @@ public class ZoomRing extends View {
// We bounded the touch angle
totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
animateThumbToNewAngle = true;
- mMode = MODE_IGNORE_UNTIL_UP;
+ setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
}
@@ -764,11 +824,13 @@ public class ZoomRing extends View {
boolean canStillZoom = mCallback.onZoomRingThumbDragged(
deltaLevels, mThumbDragStartAngle, touchAngle);
- // TODO: we're trying the haptics to see how it goes with
- // users, so we're ignoring the settings (for now)
- performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ if (mVibration) {
+ // TODO: we're trying the haptics to see how it goes with
+ // users, so we're ignoring the settings (for now)
+ performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
// Set the callback angle to the actual angle based on how many delta levels we gave
mPreviousCallbackAngle = getValidAngle(
@@ -791,6 +853,134 @@ public class ZoomRing extends View {
setThumbAngleAuto(touchAngle, useDirection, ccw);
}
}
+// private void onThumbDragged(int touchAngle, boolean useDirection, boolean ccw) {
+// int deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+//
+// if (!useDirection) {
+// // Set ccw to match the direction found by getDelta
+// ccw = deltaPrevCbAndTouch > 0;
+// useDirection = true;
+// }
+//
+// boolean animateThumbToNewAngle = false;
+// boolean animationCcw = ccw;
+//
+// if (deltaPrevCbAndTouch >= mFuzzyCallbackThreshold
+// || deltaPrevCbAndTouch <= -mFuzzyCallbackThreshold) {
+//
+// /*
+// * When the user slides the thumb through the tick that corresponds
+// * to a zoom bound, we don't want to abruptly stop there. Instead,
+// * let the user slide it to the next tick, and then animate it back
+// * to the original zoom bound tick. Because of this, we make sure
+// * the delta from the bound is more than halfway to the next tick.
+// * We make sure the bound is between the touch and the previous
+// * callback to ensure we JUST passed the bound.
+// */
+// int oldTouchAngle = touchAngle;
+// if (ccw && mThumbCcwBound != Integer.MIN_VALUE) {
+// int deltaCcwBoundAndTouch =
+// getDelta(mThumbCcwBound, touchAngle, true, ccw);
+// if (deltaCcwBoundAndTouch >= mCallbackThreshold / 2) {
+// // The touch has past far enough from the bound
+// int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+// touchAngle, true, ccw);
+// if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) {
+// // The bound is between the previous callback angle and the touch
+// // Cap to the bound
+// touchAngle = mThumbCcwBound;
+// /*
+// * We're moving the touch BACK to the bound, so animate
+// * back in the opposite direction that passed the bound.
+// */
+// animationCcw = false;
+// }
+// }
+// } else if (!ccw && mThumbCwBound != Integer.MIN_VALUE) {
+// // See block above for general comments
+// int deltaCwBoundAndTouch =
+// getDelta(mThumbCwBound, touchAngle, true, ccw);
+// if (deltaCwBoundAndTouch <= -mCallbackThreshold / 2) {
+// int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+// touchAngle, true, ccw);
+// /*
+// * Both of these will be negative since we got delta in
+// * clockwise direction, and we want the magnitude of
+// * deltaPreviousCbAndTouch to be greater than the magnitude
+// * of deltaCwBoundAndTouch
+// */
+// if (deltaPreviousCbAndTouch <= deltaCwBoundAndTouch) {
+// touchAngle = mThumbCwBound;
+// animationCcw = true;
+// }
+// }
+// }
+// if (touchAngle != oldTouchAngle) {
+// // We bounded the touch angle
+// deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, true, ccw);
+// // Animate back to the bound
+// animateThumbToNewAngle = true;
+// // Disallow movement now
+// setMode(MODE_IGNORE_UNTIL_UP);
+// }
+//
+//
+// /*
+// * Prevent it from jumping too far (this could happen if the user
+// * goes through the center)
+// */
+//
+// if (mEnforceMaxAbsJump) {
+// if (deltaPrevCbAndTouch <= -MAX_ABS_JUMP_DELTA_ANGLE) {
+// deltaPrevCbAndTouch = -MAX_ABS_JUMP_DELTA_ANGLE;
+// animateThumbToNewAngle = true;
+// } else if (deltaPrevCbAndTouch >= MAX_ABS_JUMP_DELTA_ANGLE) {
+// deltaPrevCbAndTouch = MAX_ABS_JUMP_DELTA_ANGLE;
+// animateThumbToNewAngle = true;
+// }
+// }
+//
+// /*
+// * We need to cover the edge case of a user grabbing the thumb,
+// * going into the center of the widget, and then coming out from the
+// * center to an angle that's slightly below the angle he's trying to
+// * hit. If we do int division, we'll end up with one level lower
+// * than the one he was going for.
+// */
+// int deltaLevels = Math.round((float) deltaPrevCbAndTouch / mCallbackThreshold);
+// if (deltaLevels != 0) {
+// boolean canStillZoom = mCallback.onZoomRingThumbDragged(
+// deltaLevels, mThumbDragStartAngle, touchAngle);
+//
+// if (mVibration) {
+// // TODO: we're trying the haptics to see how it goes with
+// // users, so we're ignoring the settings (for now)
+// performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
+// HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
+// HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+//
+// }
+// // Set the callback angle to the actual angle based on how many delta levels we gave
+// mPreviousCallbackAngle = getValidAngle(
+// mPreviousCallbackAngle + (deltaLevels * mCallbackThreshold));
+// }
+// }
+//
+// if (DRAW_TRAIL) {
+// int deltaAngle = getDelta(mThumbAngle, touchAngle, true, ccw);
+// mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER);
+// }
+//
+// if (animateThumbToNewAngle) {
+// setThumbAngleAnimated(touchAngle, 0, animationCcw);
+// } else {
+// /*
+// * Use regular ccw here because animationCcw will never have been
+// * changed if animateThumbToNewAngle is false
+// */
+// setThumbAngleAuto(touchAngle, true, ccw);
+// }
+// }
private int getValidAngle(int invalidAngle) {
if (invalidAngle < 0) {
@@ -818,16 +1008,16 @@ public class ZoomRing extends View {
mCallback.onZoomRingThumbDraggingStopped();
}
- private void onZoomRingMoved(int x, int y) {
+ private void onZoomRingMoved(int rawX, int rawY) {
if (mPreviousWidgetDragX != Integer.MIN_VALUE) {
- int deltaX = x - mPreviousWidgetDragX;
- int deltaY = y - mPreviousWidgetDragY;
+ int deltaX = rawX - mPreviousWidgetDragX;
+ int deltaY = rawY - mPreviousWidgetDragY;
- mCallback.onZoomRingMoved(deltaX, deltaY);
+ mCallback.onZoomRingMoved(deltaX, deltaY, rawX, rawY);
}
- mPreviousWidgetDragX = x;
- mPreviousWidgetDragY = y;
+ mPreviousWidgetDragX = rawX;
+ mPreviousWidgetDragY = rawY;
}
@Override
@@ -859,15 +1049,17 @@ public class ZoomRing extends View {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- if (mDrawThumb) {
+ if (mThumbVisible) {
if (DRAW_TRAIL) {
mTrail.draw(canvas);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
mThumbPlusArrowDrawable.draw(canvas);
+ mThumbPlusDrawable.draw(canvas);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
mThumbMinusArrowDrawable.draw(canvas);
+ mThumbMinusDrawable.draw(canvas);
}
mThumbDrawable.draw(canvas);
}
@@ -877,6 +1069,28 @@ public class ZoomRing extends View {
int level = -angle * 10000 / ZoomRing.TWO_PI_INT_MULTIPLIED;
mThumbPlusArrowDrawable.setLevel(level);
mThumbMinusArrowDrawable.setLevel(level);
+
+ // Assume it is a square
+ int halfSideLength = mThumbPlusDrawable.getIntrinsicHeight() / 2;
+ int unoffsetAngle = angle + mZeroAngle;
+
+ int plusCenterX = (int) (Math.cos(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
+ int plusCenterY = (int) (Math.sin(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
+ mThumbPlusDrawable.setBounds(plusCenterX - halfSideLength,
+ plusCenterY - halfSideLength,
+ plusCenterX + halfSideLength,
+ plusCenterY + halfSideLength);
+
+ int minusCenterX = (int) (Math.cos(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
+ int minusCenterY = (int) (Math.sin(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
+ / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
+ mThumbMinusDrawable.setBounds(minusCenterX - halfSideLength,
+ minusCenterY - halfSideLength,
+ minusCenterX + halfSideLength,
+ minusCenterY + halfSideLength);
}
public void setThumbArrowsVisible(boolean visible) {
@@ -886,6 +1100,7 @@ public class ZoomRing extends View {
if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR ||
callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) {
mThumbPlusArrowDrawable.setAlpha(255);
+ mThumbPlusDrawable.setAlpha(255);
mThumbArrowsToDraw |= THUMB_ARROW_PLUS;
} else {
mThumbArrowsToDraw &= ~THUMB_ARROW_PLUS;
@@ -893,6 +1108,7 @@ public class ZoomRing extends View {
if (callbackAngle < mThumbCcwBound - RADIAN_INT_ERROR ||
callbackAngle > mThumbCcwBound + RADIAN_INT_ERROR) {
mThumbMinusArrowDrawable.setAlpha(255);
+ mThumbMinusDrawable.setAlpha(255);
mThumbArrowsToDraw |= THUMB_ARROW_MINUS;
} else {
mThumbArrowsToDraw &= ~THUMB_ARROW_MINUS;
@@ -917,10 +1133,14 @@ public class ZoomRing extends View {
if (mThumbArrowsAlpha < 0) mThumbArrowsAlpha = 0;
if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
mThumbPlusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ mThumbPlusDrawable.setAlpha(mThumbArrowsAlpha);
+ invalidateDrawable(mThumbPlusDrawable);
invalidateDrawable(mThumbPlusArrowDrawable);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
mThumbMinusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ mThumbMinusDrawable.setAlpha(mThumbArrowsAlpha);
+ invalidateDrawable(mThumbMinusDrawable);
invalidateDrawable(mThumbMinusArrowDrawable);
}
@@ -941,7 +1161,7 @@ public class ZoomRing extends View {
void onZoomRingSetMovableHintVisible(boolean visible);
void onZoomRingMovingStarted();
- boolean onZoomRingMoved(int deltaX, int deltaY);
+ boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY);
void onZoomRingMovingStopped();
void onZoomRingThumbDraggingStarted();
diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java
index 2e97fda..19f66a0 100644
--- a/core/java/android/widget/ZoomRingController.java
+++ b/core/java/android/widget/ZoomRingController.java
@@ -33,6 +33,7 @@ import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -47,30 +48,36 @@ import android.view.animation.DecelerateInterpolator;
/**
* TODO: Docs
- *
+ *
* If you are using this with a custom View, please call
* {@link #setVisible(boolean) setVisible(false)} from the
* {@link View#onDetachedFromWindow}.
- *
+ *
* @hide
*/
public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
View.OnTouchListener, View.OnKeyListener {
-
+
private static final int ZOOM_RING_RADIUS_INSET = 24;
private static final int ZOOM_RING_RECENTERING_DURATION = 500;
private static final String TAG = "ZoomRing";
- public static final boolean USE_OLD_ZOOM = false;
+ public static final boolean USE_OLD_ZOOM = false;
+ static int getZoomType(Context context) {
+ return Settings.System.getInt(context.getContentResolver(), "zoom", 1);
+ }
public static boolean useOldZoom(Context context) {
- return Settings.System.getInt(context.getContentResolver(), "zoom", 1) == 0;
+ return getZoomType(context) == 0;
}
-
+ private static boolean useThisZoom(Context context) {
+ return getZoomType(context) == 1;
+ }
+
private static final int ZOOM_CONTROLS_TIMEOUT =
(int) ViewConfiguration.getZoomControlsTimeout();
-
+
// TODO: move these to ViewConfiguration or re-use existing ones
// TODO: scale px values based on latest from ViewConfiguration
private static final int SECOND_TAP_TIMEOUT = 500;
@@ -80,12 +87,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private static final int MAX_INITIATE_PAN_GAP = 10;
// TODO view config
private static final int INITIATE_PAN_DELAY = 300;
-
+
private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast";
-
+
private Context mContext;
private WindowManager mWindowManager;
-
+
/**
* The view that is being zoomed by this zoom ring.
*/
@@ -111,15 +118,15 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/**
* The {@link #mTouchTargetView}'s location in window, set on touch down.
*/
- private int[] mTouchTargetLocationInWindow = new int[2];
+ private int[] mTouchTargetLocationInWindow = new int[2];
/**
* If the zoom ring is dismissed but the user is still in a touch
* interaction, we set this to true. This will ignore all touch events until
* up/cancel, and then set the owner's touch listener to null.
*/
private boolean mReleaseTouchListenerOnUp;
-
-
+
+
/*
* Tap-drag is an interaction where the user first taps and then (quickly)
* does the clockwise or counter-clockwise drag. In reality, this is: (down,
@@ -132,30 +139,40 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
*/
private int mTapDragStartX;
private int mTapDragStartY;
-
+
private static final int TOUCH_MODE_IDLE = 0;
private static final int TOUCH_MODE_WAITING_FOR_SECOND_TAP = 1;
private static final int TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT = 2;
private static final int TOUCH_MODE_FORWARDING_FOR_TAP_DRAG = 3;
private int mTouchMode;
-
+
private boolean mIsZoomRingVisible;
-
+
private ZoomRing mZoomRing;
private int mZoomRingWidth;
private int mZoomRingHeight;
-
+
/** Invokes panning of owner view if the zoom ring is touching an edge. */
private Panner mPanner;
private long mTouchingEdgeStartTime;
private boolean mPanningEnabledForThisInteraction;
-
+
+ /**
+ * When the finger moves the zoom ring to an edge, this is the horizontal
+ * accumulator for how much the finger has moved off of its original touch
+ * point on the zoom ring (OOB = out-of-bounds). If < 0, the finger has
+ * moved that many px to the left of its original touch point on the ring.
+ */
+ private int mMovingZoomRingOobX;
+ /** Vertical accumulator, see {@link #mMovingZoomRingOobX} */
+ private int mMovingZoomRingOobY;
+
private ImageView mPanningArrows;
private Animation mPanningArrowsEnterAnimation;
private Animation mPanningArrowsExitAnimation;
-
+
private Rect mTempRect = new Rect();
-
+
private OnZoomListener mCallback;
private ViewConfiguration mViewConfig;
@@ -171,7 +188,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* for the container's layout params.
*/
private int mCenteredContainerY = Integer.MIN_VALUE;
-
+
/**
* Scroller used to re-center the zoom ring if the user had dragged it to a
* corner and then double-taps any point on the owner view (the owner view
@@ -181,7 +198,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* The (x,y) of the scroller is the (x,y) of the container's layout params.
*/
private Scroller mScroller;
-
+
/**
* When showing the zoom ring, we add the view as a new window. However,
* there is logic that needs to know the size of the zoom ring which is
@@ -189,7 +206,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* the UI thread so it will be exceuted AFTER the layout. This is the logic.
*/
private Runnable mPostedVisibleInitializer;
-
+
/**
* Only touch from the main thread.
*/
@@ -199,23 +216,29 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
-
+
private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mIsZoomRingVisible) return;
-
+
mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED);
mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED);
}
};
-
+
/** Keeps the scroller going (or starts it). */
private static final int MSG_SCROLLER_TICK = 1;
/** When configuration changes, this is called after the UI thread is idle. */
private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
/** Used to delay the zoom ring dismissal. */
private static final int MSG_DISMISS_ZOOM_RING = 3;
+
+ /**
+ * If setVisible(true) is called and the owner view's window token is null,
+ * we delay the setVisible(true) call until it is not null.
+ */
+ private static final int MSG_POST_SET_VISIBLE = 4;
private Handler mHandler = new Handler() {
@Override
@@ -224,26 +247,36 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
case MSG_SCROLLER_TICK:
onScrollerTick();
break;
-
+
case MSG_POST_CONFIGURATION_CHANGED:
onPostConfigurationChanged();
break;
-
+
case MSG_DISMISS_ZOOM_RING:
setVisible(false);
break;
+
+ case MSG_POST_SET_VISIBLE:
+ if (mOwnerView.getWindowToken() == null) {
+ // Doh, it is still null, throw an exception
+ throw new IllegalArgumentException(
+ "Cannot make the zoom ring visible if the owner view is " +
+ "not attached to a window.");
+ }
+ setVisible(true);
+ break;
}
-
- }
+
+ }
};
-
+
public ZoomRingController(Context context, View ownerView) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mPanner = new Panner();
mOwnerView = ownerView;
-
+
mZoomRing = new ZoomRing(context);
mZoomRing.setId(com.android.internal.R.id.zoomControls);
mZoomRing.setLayoutParams(new FrameLayout.LayoutParams(
@@ -251,7 +284,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
mZoomRing.setCallback(this);
-
+
createPanningArrows();
mContainerLayoutParams = new LayoutParams();
@@ -269,12 +302,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mContainer = new FrameLayout(context);
mContainer.setLayoutParams(mContainerLayoutParams);
mContainer.setMeasureAllChildren(true);
-
+
mContainer.addView(mZoomRing);
mContainer.addView(mPanningArrows);
-
+
mScroller = new Scroller(context, new DecelerateInterpolator());
-
+
mViewConfig = ViewConfiguration.get(context);
}
@@ -287,7 +320,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
mPanningArrows.setVisibility(View.INVISIBLE);
-
+
mPanningArrowsEnterAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.fade_in);
mPanningArrowsExitAnimation = AnimationUtils.loadAnimation(mContext,
@@ -299,7 +332,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* get a callback. Once there is a callback, the accumulator resets. For
* example, if you set this to PI/6, it will give a callback every time the
* user moves PI/6 amount on the ring.
- *
+ *
* @param callbackThreshold The angle for the callback threshold, in radians
*/
public void setZoomCallbackThreshold(float callbackThreshold) {
@@ -308,7 +341,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/**
* Sets a drawable for the zoom ring track.
- *
+ *
* @param drawable The drawable to use for the track.
* @hide Need a better way of doing this, but this one-off for browser so it
* can have its final look for the usability study
@@ -316,11 +349,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void setZoomRingTrack(int drawable) {
mZoomRing.setBackgroundResource(drawable);
}
-
+
public void setCallback(OnZoomListener callback) {
mCallback = callback;
}
-
+
public void setThumbAngle(float angle) {
mZoomRing.setThumbAngle((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER));
}
@@ -328,13 +361,21 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void setThumbAngleAnimated(float angle) {
mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0);
}
-
+
public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
mZoomRing.setResetThumbAutomatically(resetThumbAutomatically);
}
+
+ public void setVibration(boolean vibrate) {
+ mZoomRing.setVibration(vibrate);
+ }
+
+ public void setThumbVisible(boolean thumbVisible) {
+ mZoomRing.setThumbVisible(thumbVisible);
+ }
public void setThumbClockwiseBound(float angle) {
- mZoomRing.setThumbClockwiseBound(angle >= 0 ?
+ mZoomRing.setThumbClockwiseBound(angle >= 0 ?
(int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
Integer.MIN_VALUE);
}
@@ -351,14 +392,26 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void setVisible(boolean visible) {
- if (useOldZoom(mContext)) return;
+ if (!useThisZoom(mContext)) return;
if (visible) {
+ if (mOwnerView.getWindowToken() == null) {
+ /*
+ * We need a window token to show ourselves, maybe the owner's
+ * window hasn't been created yet but it will have been by the
+ * time the looper is idle, so post the setVisible(true) call.
+ */
+ if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) {
+ mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE);
+ }
+ return;
+ }
+
dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
} else {
mPanner.stop();
}
-
+
if (mIsZoomRingVisible == visible) {
return;
}
@@ -368,40 +421,40 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
if (mContainerLayoutParams.token == null) {
mContainerLayoutParams.token = mOwnerView.getWindowToken();
}
-
+
mWindowManager.addView(mContainer, mContainerLayoutParams);
-
+
if (mPostedVisibleInitializer == null) {
mPostedVisibleInitializer = new Runnable() {
public void run() {
refreshPositioningVariables();
resetZoomRing();
-
+
// TODO: remove this 'update' and just center zoom ring before the
// 'add', but need to make sure we have the width and height (which
// probably can only be retrieved after it's measured, which happens
// after it's added).
mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
-
+
if (mCallback != null) {
mCallback.onVisibilityChanged(true);
}
}
- };
+ };
}
-
+
mPanningArrows.setAnimation(null);
-
+
mHandler.post(mPostedVisibleInitializer);
-
+
// Handle configuration changes when visible
mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
-
+
// Steal key/touches events from the owner
mOwnerView.setOnKeyListener(this);
mOwnerView.setOnTouchListener(this);
mReleaseTouchListenerOnUp = false;
-
+
} else {
// Don't want to steal any more keys/touches
mOwnerView.setOnKeyListener(null);
@@ -415,45 +468,45 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
// No longer care about configuration changes
mContext.unregisterReceiver(mConfigurationChangedReceiver);
-
+
mWindowManager.removeView(mContainer);
mHandler.removeCallbacks(mPostedVisibleInitializer);
-
+
if (mCallback != null) {
mCallback.onVisibilityChanged(false);
}
}
-
+
}
-
+
/**
* TODO: docs
- *
+ *
* Notes:
* - Touch dispatching is different. Only direct children who are clickable are eligble for touch events.
* - Please ensure you set your View to INVISIBLE not GONE when hiding it.
- *
+ *
* @return
*/
public FrameLayout getContainer() {
return mContainer;
}
-
+
public int getZoomRingId() {
return mZoomRing.getId();
}
-
+
private void dismissZoomRingDelayed(int delay) {
mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay);
}
-
+
private void resetZoomRing() {
mScroller.abortAnimation();
-
+
mContainerLayoutParams.x = mCenteredContainerX;
mContainerLayoutParams.y = mCenteredContainerY;
-
+
// Reset the thumb
mZoomRing.resetThumbAngle();
}
@@ -461,13 +514,15 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/**
* Should be called by the client for each event belonging to the second tap
* (the down, move, up, and cancel events).
- *
+ *
* @param event The event belonging to the second tap.
* @return Whether the event was consumed.
*/
public boolean handleDoubleTapEvent(MotionEvent event) {
- int action = event.getAction();
+ if (!useThisZoom(mContext)) return false;
+ int action = event.getAction();
+
// TODO: make sure this works well with the
// ownerView.setOnTouchListener(this) instead of window receiving
// touches
@@ -475,19 +530,19 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mTouchMode = TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT;
int x = (int) event.getX();
int y = (int) event.getY();
-
+
refreshPositioningVariables();
setVisible(true);
centerPoint(x, y);
- ensureZoomRingIsCentered();
-
+ ensureZoomRingIsCentered();
+
// Tap drag mode stuff
mTapDragStartX = x;
mTapDragStartY = y;
} else if (action == MotionEvent.ACTION_CANCEL) {
mTouchMode = TOUCH_MODE_IDLE;
-
+
} else { // action is move or up
switch (mTouchMode) {
case TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT: {
@@ -503,29 +558,29 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
setTouchTargetView(mZoomRing);
}
return true;
-
+
case MotionEvent.ACTION_UP:
mTouchMode = TOUCH_MODE_IDLE;
break;
}
break;
}
-
+
case TOUCH_MODE_FORWARDING_FOR_TAP_DRAG: {
switch (action) {
case MotionEvent.ACTION_MOVE:
giveTouchToZoomRing(event);
return true;
-
+
case MotionEvent.ACTION_UP:
mTouchMode = TOUCH_MODE_IDLE;
-
+
/*
* This is a power-user feature that only shows the
* zoom while the user is performing the tap-drag.
* That means once it is released, the zoom ring
* should disappear.
- */
+ */
mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY());
dismissZoomRingDelayed(0);
break;
@@ -534,13 +589,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
}
}
-
+
return true;
}
-
+
private void ensureZoomRingIsCentered() {
LayoutParams lp = mContainerLayoutParams;
-
+
if (lp.x != mCenteredContainerX || lp.y != mCenteredContainerY) {
int width = mContainer.getWidth();
int height = mContainer.getHeight();
@@ -549,21 +604,21 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
}
}
-
+
private void refreshPositioningVariables() {
mZoomRingWidth = mZoomRing.getWidth();
mZoomRingHeight = mZoomRing.getHeight();
-
+
// Calculate the owner view's bounds
mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
-
+
// Get the center
Gravity.apply(Gravity.CENTER, mContainer.getWidth(), mContainer.getHeight(),
mOwnerViewBounds, mTempRect);
mCenteredContainerX = mTempRect.left;
mCenteredContainerY = mTempRect.top;
}
-
+
/**
* Centers the point (in owner view's coordinates).
*/
@@ -572,7 +627,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mCallback.onCenter(x, y);
}
}
-
+
private void giveTouchToZoomRing(MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
@@ -580,11 +635,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
int y = rawY - mContainerLayoutParams.y - mZoomRing.getTop();
mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY);
}
-
+
public void onZoomRingSetMovableHintVisible(boolean visible) {
- setPanningArrowsVisible(visible);
+ setPanningArrowsVisible(visible);
}
-
+
public void onUserInteractionStarted() {
mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
}
@@ -596,24 +651,62 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void onZoomRingMovingStarted() {
mScroller.abortAnimation();
mTouchingEdgeStartTime = 0;
+ mMovingZoomRingOobX = 0;
+ mMovingZoomRingOobY = 0;
if (mCallback != null) {
mCallback.onBeginPan();
}
}
-
+
private void setPanningArrowsVisible(boolean visible) {
mPanningArrows.startAnimation(visible ? mPanningArrowsEnterAnimation
: mPanningArrowsExitAnimation);
mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
-
- public boolean onZoomRingMoved(int deltaX, int deltaY) {
+
+ public boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY) {
+
+ if (mMovingZoomRingOobX != 0) {
+ /*
+ * The finger has moved off the point where it originally touched
+ * the zidget.
+ */
+ boolean wasOobLeft = mMovingZoomRingOobX < 0;
+ mMovingZoomRingOobX += deltaX;
+ if ((wasOobLeft && mMovingZoomRingOobX > 0) ||
+ (!wasOobLeft && mMovingZoomRingOobX < 0)) {
+ /*
+ * Woot, the finger is back on the original point. Infact, it
+ * went PAST its original point, so take the amount it passed
+ * and use that as the delta to move the zoom ring.
+ */
+ deltaX = mMovingZoomRingOobX;
+ // No longer out-of-bounds, reset
+ mMovingZoomRingOobX = 0;
+ } else {
+ // The finger is still not back, eat this movement
+ deltaX = 0;
+ }
+ }
+
+ if (mMovingZoomRingOobY != 0) {
+ // See above for comments
+ boolean wasOobUp = mMovingZoomRingOobY < 0;
+ mMovingZoomRingOobY += deltaY;
+ if ((wasOobUp && mMovingZoomRingOobY > 0) || (!wasOobUp && mMovingZoomRingOobY < 0)) {
+ deltaY = mMovingZoomRingOobY;
+ mMovingZoomRingOobY = 0;
+ } else {
+ deltaY = 0;
+ }
+ }
+
WindowManager.LayoutParams lp = mContainerLayoutParams;
Rect ownerBounds = mOwnerViewBounds;
-
+
int zoomRingLeft = mZoomRing.getLeft();
int zoomRingTop = mZoomRing.getTop();
-
+
int newX = lp.x + deltaX;
int newZoomRingX = newX + zoomRingLeft;
newZoomRingX = (newZoomRingX <= ownerBounds.left) ? ownerBounds.left :
@@ -627,19 +720,26 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
(newZoomRingY + mZoomRingHeight > ownerBounds.bottom) ?
ownerBounds.bottom - mZoomRingHeight : newZoomRingY;
lp.y = newZoomRingY - zoomRingTop;
-
+
mWindowManager.updateViewLayout(mContainer, lp);
-
+
// Check for pan
boolean horizontalPanning = true;
int leftGap = newZoomRingX - ownerBounds.left;
if (leftGap < MAX_PAN_GAP) {
+ if (leftGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
+ // Future moves in this direction should be accumulated in mMovingZoomRingOobX
+ mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
+ }
if (shouldPan(leftGap)) {
mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap));
}
} else {
int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft);
if (rightGap < MAX_PAN_GAP) {
+ if (rightGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
+ mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
+ }
if (shouldPan(rightGap)) {
mPanner.setHorizontalStrength(getStrengthFromGap(rightGap));
}
@@ -648,15 +748,21 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
horizontalPanning = false;
}
}
-
+
int topGap = newZoomRingY - ownerBounds.top;
if (topGap < MAX_PAN_GAP) {
+ if (topGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
+ mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
+ }
if (shouldPan(topGap)) {
mPanner.setVerticalStrength(-getStrengthFromGap(topGap));
}
} else {
int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop);
if (bottomGap < MAX_PAN_GAP) {
+ if (bottomGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
+ mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
+ }
if (shouldPan(bottomGap)) {
mPanner.setVerticalStrength(getStrengthFromGap(bottomGap));
}
@@ -670,13 +776,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
}
}
-
+
return true;
}
-
+
private boolean shouldPan(int gap) {
if (mPanningEnabledForThisInteraction) return true;
-
+
if (gap < MAX_INITIATE_PAN_GAP) {
long time = SystemClock.elapsedRealtime();
if (mTouchingEdgeStartTime != 0 &&
@@ -693,7 +799,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
return false;
}
-
+
public void onZoomRingMovingStopped() {
mPanner.stop();
setPanningArrowsVisible(false);
@@ -701,18 +807,18 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mCallback.onEndPan();
}
}
-
+
private int getStrengthFromGap(int gap) {
return gap > MAX_PAN_GAP ? 0 :
(MAX_PAN_GAP - gap) * 100 / MAX_PAN_GAP;
}
-
+
public void onZoomRingThumbDraggingStarted() {
if (mCallback != null) {
mCallback.onBeginDrag();
}
}
-
+
public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) {
if (mCallback != null) {
int deltaZoomLevel = -numLevels;
@@ -720,17 +826,17 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mZoomRingWidth / 2;
int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() +
mZoomRingHeight / 2;
-
+
return mCallback.onDragZoom(deltaZoomLevel,
globalZoomCenterX - mOwnerViewBounds.left,
globalZoomCenterY - mOwnerViewBounds.top,
(float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER,
(float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER);
}
-
+
return false;
}
-
+
public void onZoomRingThumbDraggingStopped() {
if (mCallback != null) {
mCallback.onEndDrag();
@@ -740,35 +846,35 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void onZoomRingDismissed(boolean dismissImmediately) {
if (dismissImmediately) {
mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
- setVisible(false);
+ setVisible(false);
} else {
dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
}
}
-
+
public void onRingDown(int tickAngle, int touchAngle) {
}
-
+
public boolean onTouch(View v, MotionEvent event) {
if (sTutorialDialog != null && sTutorialDialog.isShowing() &&
SystemClock.elapsedRealtime() - sTutorialShowTime >= TUTORIAL_MIN_DISPLAY_TIME) {
finishZoomTutorial();
}
-
+
int action = event.getAction();
if (mReleaseTouchListenerOnUp) {
- // The ring was dismissed but we need to throw away all events until the up
+ // The ring was dismissed but we need to throw away all events until the up
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mOwnerView.setOnTouchListener(null);
setTouchTargetView(null);
mReleaseTouchListenerOnUp = false;
}
-
+
// Eat this event
return true;
}
-
+
View targetView = mTouchTargetView;
switch (action) {
@@ -776,7 +882,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY());
setTouchTargetView(targetView);
break;
-
+
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
setTouchTargetView(null);
@@ -787,7 +893,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
// The upperleft corner of the target view in raw coordinates
int targetViewRawX = mContainerLayoutParams.x + mTouchTargetLocationInWindow[0];
int targetViewRawY = mContainerLayoutParams.y + mTouchTargetLocationInWindow[1];
-
+
MotionEvent containerEvent = MotionEvent.obtain(event);
// Convert the motion event into the target view's coordinates (from
// owner view's coordinates)
@@ -796,32 +902,32 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
boolean retValue = targetView.dispatchTouchEvent(containerEvent);
containerEvent.recycle();
return retValue;
-
+
} else {
if (action == MotionEvent.ACTION_DOWN) {
dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
}
-
+
return false;
}
}
-
+
private void setTouchTargetView(View view) {
mTouchTargetView = view;
if (view != null) {
view.getLocationInWindow(mTouchTargetLocationInWindow);
}
}
-
+
/**
* Returns the View that should receive a touch at the given coordinates.
- *
+ *
* @param rawX The raw X.
* @param rawY The raw Y.
* @return The view that should receive the touches, or null if there is not one.
*/
private View getViewForTouch(int rawX, int rawY) {
- // Check to see if it is touching the ring
+ // Check to see if it is touching the ring
int containerCenterX = mContainerLayoutParams.x + mContainer.getWidth() / 2;
int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2;
int distanceFromCenterX = rawX - containerCenterX;
@@ -832,7 +938,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
zoomRingRadius * zoomRingRadius) {
return mZoomRing;
}
-
+
// Check to see if it is touching any other clickable View.
// Reverse order so the child drawn on top gets first dibs.
int containerCoordsX = rawX - mContainerLayoutParams.x;
@@ -844,13 +950,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
!child.isClickable()) {
continue;
}
-
+
child.getHitRect(frame);
if (frame.contains(containerCoordsX, containerCoordsY)) {
return child;
}
}
-
+
return null;
}
@@ -861,34 +967,34 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
case KeyEvent.KEYCODE_DPAD_RIGHT:
// Eat these
return true;
-
+
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
// Keep the zoom alive a little longer
dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
// They started zooming, hide the thumb arrows
mZoomRing.setThumbArrowsVisible(false);
-
+
if (mCallback != null && event.getAction() == KeyEvent.ACTION_DOWN) {
mCallback.onSimpleZoom(keyCode == KeyEvent.KEYCODE_DPAD_UP);
}
-
+
return true;
}
-
+
return false;
}
private void onScrollerTick() {
if (!mScroller.computeScrollOffset() || !mIsZoomRingVisible) return;
-
+
mContainerLayoutParams.x = mScroller.getCurrX();
mContainerLayoutParams.y = mScroller.getCurrY();
mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
}
-
+
private void onPostConfigurationChanged() {
dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
refreshPositioningVariables();
@@ -908,7 +1014,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* before. Furthermore, if the application does not have privilege to write
* to the system settings, it will store this bit locally in a shared
* preference.
- *
+ *
* @hide This should only be used by our main apps--browser, maps, and
* gallery
*/
@@ -917,53 +1023,65 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) {
return;
}
-
+
SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) {
return;
}
-
+
if (sTutorialDialog != null && sTutorialDialog.isShowing()) {
sTutorialDialog.dismiss();
}
+
+ LayoutInflater layoutInflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ TextView textView = (TextView) layoutInflater.inflate(
+ com.android.internal.R.layout.alert_dialog_simple_text, null)
+ .findViewById(android.R.id.text1);
+ textView.setText(com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short);
sTutorialDialog = new AlertDialog.Builder(context)
- .setMessage(
- com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short)
+ .setView(textView)
.setIcon(0)
.create();
-
+
Window window = sTutorialDialog.getWindow();
window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
- window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
-
+
sTutorialDialog.show();
sTutorialShowTime = SystemClock.elapsedRealtime();
}
-
- public void finishZoomTutorial() {
+
+ public static void finishZoomTutorial(Context context, boolean userNotified) {
if (sTutorialDialog == null) return;
-
+
sTutorialDialog.dismiss();
sTutorialDialog = null;
-
+
// Record that they have seen the tutorial
- try {
- Settings.System.putInt(mContext.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
- } catch (SecurityException e) {
- /*
- * The app does not have permission to clear this global flag, make
- * sure the user does not see the message when he comes back to this
- * same app at least.
- */
- SharedPreferences sp = mContext.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
- sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit();
+ if (userNotified) {
+ try {
+ Settings.System.putInt(context.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
+ } catch (SecurityException e) {
+ /*
+ * The app does not have permission to clear this global flag, make
+ * sure the user does not see the message when he comes back to this
+ * same app at least.
+ */
+ SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
+ sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit();
+ }
}
}
-
+
+ public void finishZoomTutorial() {
+ finishZoomTutorial(mContext, true);
+ }
+
public void setPannerStartVelocity(float startVelocity) {
mPanner.mStartVelocity = startVelocity;
}
@@ -983,27 +1101,27 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private class Panner implements Runnable {
private static final int RUN_DELAY = 15;
private static final float STOP_SLOWDOWN = 0.8f;
-
+
private final Handler mUiHandler = new Handler();
-
+
private int mVerticalStrength;
private int mHorizontalStrength;
private boolean mStopping;
-
+
/** The time this current pan started. */
private long mStartTime;
-
+
/** The time of the last callback to pan the map/browser/etc. */
private long mPreviousCallbackTime;
-
+
// TODO Adjust to be DPI safe
private float mStartVelocity = 135;
private float mAcceleration = 160;
private float mMaxVelocity = 1000;
private int mStartAcceleratingDuration = 700;
private float mVelocity;
-
+
/** -100 (full left) to 0 (none) to 100 (full right) */
public void setHorizontalStrength(int horizontalStrength) {
if (mHorizontalStrength == 0 && mVerticalStrength == 0 && horizontalStrength != 0) {
@@ -1011,7 +1129,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
} else if (mVerticalStrength == 0 && horizontalStrength == 0) {
stop();
}
-
+
mHorizontalStrength = horizontalStrength;
mStopping = false;
}
@@ -1023,11 +1141,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
} else if (mHorizontalStrength == 0 && verticalStrength == 0) {
stop();
}
-
- mVerticalStrength = verticalStrength;
+
+ mVerticalStrength = verticalStrength;
mStopping = false;
}
-
+
private void start() {
mUiHandler.post(this);
mPreviousCallbackTime = 0;
@@ -1037,37 +1155,37 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void stop() {
mStopping = true;
}
-
+
public void run() {
if (mStopping) {
mHorizontalStrength *= STOP_SLOWDOWN;
mVerticalStrength *= STOP_SLOWDOWN;
}
-
+
if (mHorizontalStrength == 0 && mVerticalStrength == 0) {
return;
}
-
+
boolean firstRun = mPreviousCallbackTime == 0;
long curTime = SystemClock.elapsedRealtime();
int panAmount = getPanAmount(mPreviousCallbackTime, curTime);
mPreviousCallbackTime = curTime;
-
+
if (firstRun) {
mStartTime = curTime;
mVelocity = mStartVelocity;
} else {
int panX = panAmount * mHorizontalStrength / 100;
int panY = panAmount * mVerticalStrength / 100;
-
+
if (mCallback != null) {
mCallback.onPan(panX, panY);
}
}
-
+
mUiHandler.postDelayed(this, RUN_DELAY);
}
-
+
private int getPanAmount(long previousTime, long currentTime) {
if (mVelocity > mMaxVelocity) {
mVelocity = mMaxVelocity;
@@ -1077,14 +1195,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mVelocity += (currentTime - previousTime) * mAcceleration / 1000;
}
}
-
+
return (int) ((currentTime - previousTime) * mVelocity) / 1000;
}
}
-
-
public interface OnZoomListener {
void onBeginDrag();
boolean onDragZoom(int deltaZoomLevel, int centerX, int centerY, float startAngle,