summaryrefslogtreecommitdiff
path: root/core/java/android/widget/ZoomButtonsController.java
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-13 13:04:22 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-13 13:04:22 -0700
commitba87e3e6c985e7175152993b5efcc7dd2f0e1c93 (patch)
treeee35f76532767dc29411a8738a434d1d88d330f2 /core/java/android/widget/ZoomButtonsController.java
parentc39a6e0c51e182338deb8b63d07933b585134929 (diff)
auto import from //branches/cupcake_rel/...@138607
Diffstat (limited to 'core/java/android/widget/ZoomButtonsController.java')
-rw-r--r--core/java/android/widget/ZoomButtonsController.java374
1 files changed, 309 insertions, 65 deletions
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index 6729fd17a5ef..4daa41949330 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -24,12 +24,15 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings;
+import android.util.Log;
+import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -37,31 +40,51 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewRoot;
import android.view.Window;
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)
-
+/*
+ * Implementation notes:
+ * - The zoom controls are displayed in their own window.
+ * (Easier for the client and better performance)
+ * - This window is not touchable, and by default is not focusable.
+ * - To make the buttons clickable, it attaches a OnTouchListener to the owner
+ * view and does the hit detection locally.
+ * - When it is focusable, it forwards uninteresting events to the owner view's
+ * view hierarchy.
+ */
/**
- * TODO: Docs
- *
+ * The {@link ZoomButtonsController} handles showing and hiding the zoom
+ * controls relative to an owner view. It also gives the client access to the
+ * zoom controls container, allowing for additional accessory buttons to be
+ * shown in the zoom controls window.
+ * <p>
+ * Typical usage involves the client using the {@link GestureDetector} to
+ * forward events from
+ * {@link GestureDetector.OnDoubleTapListener#onDoubleTapEvent(MotionEvent)} to
+ * {@link #handleDoubleTapEvent(MotionEvent)}. Also, whenever the owner cannot
+ * be zoomed further, the client should update
+ * {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}.
+ * <p>
* 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, View.OnKeyListener {
+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 int mTouchPaddingScaledSq;
private Context mContext;
private WindowManager mWindowManager;
@@ -72,17 +95,17 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
private View mOwnerView;
/**
- * The bounds of the owner view in global coordinates. This is recalculated
+ * The location of the owner view on the screen. This is recalculated
* each time the zoom controller is shown.
*/
- private Rect mOwnerViewBounds = new Rect();
+ private int[] mOwnerViewRawLocation = new int[2];
/**
* The container that is added as a window.
*/
private FrameLayout mContainer;
private LayoutParams mContainerLayoutParams;
- private int[] mContainerLocation = new int[2];
+ private int[] mContainerRawLocation = new int[2];
private ZoomControls mControls;
@@ -94,7 +117,7 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
/**
* The {@link #mTouchTargetView}'s location in window, set on touch down.
*/
- private int[] mTouchTargetLocationInWindow = new int[2];
+ private int[] mTouchTargetWindowLocation = new int[2];
/**
* If the zoom controller is dismissed but the user is still in a touch
* interaction, we set this to true. This will ignore all touch events until
@@ -102,15 +125,28 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
*/
private boolean mReleaseTouchListenerOnUp;
+ /**
+ * Whether we are currently in the double-tap gesture, with the second tap
+ * still being performed (i.e., we're waiting for the second tap's touch up).
+ */
private boolean mIsSecondTapDown;
+ /** Whether the container has been added to the window manager. */
private boolean mIsVisible;
private Rect mTempRect = new Rect();
-
+ private int[] mTempIntArray = new int[2];
+
private OnZoomListener mCallback;
/**
+ * In 1.0, the ZoomControls were to be added to the UI by the client of
+ * WebView, MapView, etc. We didn't want apps to break, so we return a dummy
+ * view in place now.
+ */
+ private InvisibleView mDummyZoomControls;
+
+ /**
* 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
@@ -121,6 +157,9 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
private IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+ /**
+ * Needed to reposition the zoom controls after configuration changes.
+ */
private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -161,41 +200,68 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
case MSG_POST_SET_VISIBLE:
if (mOwnerView.getWindowToken() == null) {
- // Doh, it is still null, throw an exception
- throw new IllegalArgumentException(
+ // Doh, it is still null, just ignore the set visible call
+ Log.e(TAG,
"Cannot make the zoom controller visible if the owner view is " +
"not attached to a window.");
+ } else {
+ setVisible(true);
}
- setVisible(true);
break;
}
}
};
- public ZoomButtonsController(Context context, View ownerView) {
- mContext = context;
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ /**
+ * Constructor for the {@link ZoomButtonsController}.
+ *
+ * @param ownerView The view that is being zoomed by the zoom controls. The
+ * zoom controls will be displayed aligned with this view.
+ */
+ public ZoomButtonsController(View ownerView) {
+ mContext = ownerView.getContext();
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mOwnerView = ownerView;
+ mTouchPaddingScaledSq = (int)
+ (ZOOM_CONTROLS_TOUCH_PADDING * mContext.getResources().getDisplayMetrics().density);
+ mTouchPaddingScaledSq *= mTouchPaddingScaledSq;
+
mContainer = createContainer();
}
+ /**
+ * Whether to enable the zoom in control.
+ *
+ * @param enabled Whether to enable the zoom in control.
+ */
public void setZoomInEnabled(boolean enabled) {
mControls.setIsZoomInEnabled(enabled);
}
+ /**
+ * Whether to enable the zoom out control.
+ *
+ * @param enabled Whether to enable the zoom out control.
+ */
public void setZoomOutEnabled(boolean enabled) {
mControls.setIsZoomOutEnabled(enabled);
}
+ /**
+ * Sets the delay between zoom callbacks as the user holds a zoom button.
+ *
+ * @param speed The delay in milliseconds between zoom callbacks.
+ */
public void setZoomSpeed(long speed) {
mControls.setZoomSpeed(speed);
}
private FrameLayout createContainer() {
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- lp.gravity = Gravity.BOTTOM | Gravity.CENTER;
+ // Controls are positioned BOTTOM | CENTER with respect to the owner view.
+ lp.gravity = Gravity.TOP | Gravity.LEFT;
lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
LayoutParams.FLAG_NOT_FOCUSABLE |
LayoutParams.FLAG_LAYOUT_NO_LIMITS;
@@ -206,10 +272,9 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons;
mContainerLayoutParams = lp;
- FrameLayout container = new FrameLayout(mContext);
+ FrameLayout container = new Container(mContext);
container.setLayoutParams(lp);
container.setMeasureAllChildren(true);
- container.setOnKeyListener(this);
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -232,30 +297,51 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
return container;
}
- public void setCallback(OnZoomListener callback) {
- mCallback = callback;
+ /**
+ * Sets the {@link OnZoomListener} listener that receives callbacks to zoom.
+ *
+ * @param listener The listener that will be told to zoom.
+ */
+ public void setOnZoomListener(OnZoomListener listener) {
+ mCallback = listener;
}
+ /**
+ * Sets whether the zoom controls should be focusable. If the controls are
+ * focusable, then trackball and arrow key interactions are possible.
+ * Otherwise, only touch interactions are possible.
+ *
+ * @param focusable Whether the zoom controls should be focusable.
+ */
public void setFocusable(boolean focusable) {
+ int oldFlags = mContainerLayoutParams.flags;
if (focusable) {
mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
} else {
mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
}
- if (mIsVisible) {
+ if ((mContainerLayoutParams.flags != oldFlags) && mIsVisible) {
mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
}
}
+ /**
+ * Whether the zoom controls are visible to the user.
+ *
+ * @return Whether the zoom controls are visible to the user.
+ */
public boolean isVisible() {
return mIsVisible;
}
+ /**
+ * Sets whether the zoom controls should be visible to the user.
+ *
+ * @param visible Whether the zoom controls should be visible to the user.
+ */
public void setVisible(boolean visible) {
- if (!useThisZoom(mContext)) return;
-
if (visible) {
if (mOwnerView.getWindowToken() == null) {
/*
@@ -329,12 +415,13 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
}
/**
- * TODO: docs
- *
- * Notes:
- * - Please ensure you set your View to INVISIBLE not GONE when hiding it.
- *
- * @return TODO
+ * Gets the container that is the parent of the zoom controls.
+ * <p>
+ * The client can add other views to this container to link them with the
+ * zoom controls.
+ *
+ * @return The container of the zoom controls. It will be a layout that
+ * respects the gravity of a child's layout parameters.
*/
public ViewGroup getContainer() {
return mContainer;
@@ -347,14 +434,12 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
/**
* Should be called by the client for each event belonging to the second tap
- * (the down, move, up, and cancel events).
+ * (the down, move, up, and/or 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) {
@@ -382,9 +467,28 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
}
private void refreshPositioningVariables() {
+ // Position the zoom controls on the bottom of the owner view.
+ int ownerHeight = mOwnerView.getHeight();
+ int ownerWidth = mOwnerView.getWidth();
+ // The gap between the top of the owner and the top of the container
+ int containerOwnerYOffset = ownerHeight - mContainer.getHeight();
+
// Calculate the owner view's bounds
- mOwnerView.getGlobalVisibleRect(mOwnerViewBounds);
- mContainer.getLocationOnScreen(mContainerLocation);
+ mOwnerView.getLocationOnScreen(mOwnerViewRawLocation);
+ mContainerRawLocation[0] = mOwnerViewRawLocation[0];
+ mContainerRawLocation[1] = mOwnerViewRawLocation[1] + containerOwnerYOffset;
+
+ int[] ownerViewWindowLoc = mTempIntArray;
+ mOwnerView.getLocationInWindow(ownerViewWindowLoc);
+
+ // lp.x and lp.y should be relative to the owner's window top-left
+ mContainerLayoutParams.x = ownerViewWindowLoc[0];
+ mContainerLayoutParams.width = ownerWidth;
+ mContainerLayoutParams.y = ownerViewWindowLoc[1] + containerOwnerYOffset;
+ if (mIsVisible) {
+ mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ }
+
}
/**
@@ -396,11 +500,65 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
}
}
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
- return false;
+ /* This will only be called when the container has focus. */
+ private boolean onContainerKey(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ if (isInterestingKey(keyCode)) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ setVisible(false);
+ } else {
+ dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
+ }
+
+ // Let the container handle the key
+ return false;
+
+ } else {
+
+ ViewRoot viewRoot = getOwnerViewRoot();
+ if (viewRoot != null) {
+ viewRoot.dispatchKey(event);
+ }
+
+ // We gave the key to the owner, don't let the container handle this key
+ return true;
+ }
}
+ private boolean isInterestingKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_BACK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private ViewRoot getOwnerViewRoot() {
+ View rootViewOfOwner = mOwnerView.getRootView();
+ if (rootViewOfOwner == null) {
+ return null;
+ }
+
+ ViewParent parentOfRootView = rootViewOfOwner.getParent();
+ if (parentOfRootView instanceof ViewRoot) {
+ return (ViewRoot) parentOfRootView;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @hide The ZoomButtonsController implements the OnTouchListener, but this
+ * does not need to be shown in its public API.
+ */
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
@@ -423,14 +581,13 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
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());
+ targetView = findViewForTouch((int) event.getRawX(), (int) event.getRawY());
setTouchTargetView(targetView);
break;
@@ -442,14 +599,22 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
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];
+ int targetViewRawX = mContainerRawLocation[0] + mTouchTargetWindowLocation[0];
+ int targetViewRawY = mContainerRawLocation[1] + mTouchTargetWindowLocation[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);
+ containerEvent.offsetLocation(mOwnerViewRawLocation[0] - targetViewRawX,
+ mOwnerViewRawLocation[1] - targetViewRawY);
+ /* Disallow negative coordinates (which can occur due to
+ * ZOOM_CONTROLS_TOUCH_PADDING) */
+ if (containerEvent.getX() < 0) {
+ containerEvent.offsetLocation(-containerEvent.getX(), 0);
+ }
+ if (containerEvent.getY() < 0) {
+ containerEvent.offsetLocation(0, -containerEvent.getY());
+ }
boolean retValue = targetView.dispatchTouchEvent(containerEvent);
containerEvent.recycle();
return retValue || consumeEvent;
@@ -462,7 +627,7 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
private void setTouchTargetView(View view) {
mTouchTargetView = view;
if (view != null) {
- view.getLocationInWindow(mTouchTargetLocationInWindow);
+ view.getLocationInWindow(mTouchTargetWindowLocation);
}
}
@@ -473,11 +638,15 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
* @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) {
+ private View findViewForTouch(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];
+ int containerCoordsX = rawX - mContainerRawLocation[0];
+ int containerCoordsY = rawY - mContainerRawLocation[1];
Rect frame = mTempRect;
+
+ View closestChild = null;
+ int closestChildDistanceSq = Integer.MAX_VALUE;
+
for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
View child = mContainer.getChildAt(i);
if (child.getVisibility() != View.VISIBLE) {
@@ -485,14 +654,24 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
}
child.getHitRect(frame);
- // Expand the touch region
- frame.top -= ZOOM_CONTROLS_TOUCH_PADDING;
if (frame.contains(containerCoordsX, containerCoordsY)) {
return child;
}
+
+ int distanceX = Math.min(Math.abs(frame.left - containerCoordsX),
+ Math.abs(containerCoordsX - frame.right));
+ int distanceY = Math.min(Math.abs(frame.top - containerCoordsY),
+ Math.abs(containerCoordsY - frame.bottom));
+ int distanceSq = distanceX * distanceX + distanceY * distanceY;
+
+ if ((distanceSq < mTouchPaddingScaledSq) &&
+ (distanceSq < closestChildDistanceSq)) {
+ closestChild = child;
+ closestChildDistanceSq = distanceSq;
+ }
}
- return null;
+ return closestChild;
}
private void onPostConfigurationChanged() {
@@ -518,6 +697,10 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
* gallery
*/
public static void showZoomTutorialOnce(Context context) {
+
+ // TODO: remove this code, but to hit the weekend build, just never show
+ if (true) return;
+
ContentResolver cr = context.getContentResolver();
if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) {
return;
@@ -583,22 +766,83 @@ public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyLi
finishZoomTutorial(mContext, true);
}
- // Temporary methods for different zoom types
- static int getZoomType(Context context) {
- return Settings.System.getInt(context.getContentResolver(), "zoom", 2);
- }
-
- public static boolean useOldZoom(Context context) {
- return getZoomType(context) == 0;
- }
-
- public static boolean useThisZoom(Context context) {
- return getZoomType(context) == 2;
+ /** @hide Should only be used only be WebView and MapView */
+ public View getDummyZoomControls() {
+ if (mDummyZoomControls == null) {
+ mDummyZoomControls = new InvisibleView(mContext);
+ }
+ return mDummyZoomControls;
}
-
+
+ /**
+ * Interface that will be called when the user performs an interaction that
+ * triggers some action, for example zooming.
+ */
public interface OnZoomListener {
+ /**
+ * Called when the given point should be centered. The point will be in
+ * owner view coordinates.
+ *
+ * @param x The x of the point.
+ * @param y The y of the point.
+ */
void onCenter(int x, int y);
+
+ /**
+ * Called when the zoom controls' visibility changes.
+ *
+ * @param visible Whether the zoom controls are visible.
+ */
void onVisibilityChanged(boolean visible);
+
+ /**
+ * Called when the owner view needs to be zoomed.
+ *
+ * @param zoomIn The direction of the zoom: true to zoom in, false to zoom out.
+ */
void onZoom(boolean zoomIn);
}
+
+ private class Container extends FrameLayout {
+ public Container(Context context) {
+ super(context);
+ }
+
+ /*
+ * Need to override this to intercept the key events. Otherwise, we
+ * would attach a key listener to the container but its superclass
+ * ViewGroup gives it to the focused View instead of calling the key
+ * listener, and so we wouldn't get the events.
+ */
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return onContainerKey(event) ? true : super.dispatchKeyEvent(event);
+ }
+ }
+
+ /**
+ * An InvisibleView is an invisible, zero-sized View for backwards
+ * compatibility
+ */
+ private final class InvisibleView extends View {
+
+ private InvisibleView(Context context) {
+ super(context);
+ setVisibility(GONE);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(0, 0);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ }
+ }
+
}