FileDocCategorySizeDatePackage
ZoomButtonsController.javaAPI DocAndroid 5.1 API25658Thu Mar 12 22:22:10 GMT 2015android.widget

ZoomButtonsController

public class ZoomButtonsController extends Object implements View.OnTouchListener
The {@link ZoomButtonsController} handles showing and hiding the zoom controls and positioning it 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.

Typically, clients should call {@link #setVisible(boolean) setVisible(true)} on a touch down or move (no need to call {@link #setVisible(boolean) setVisible(false)} since it will time out on its own). Also, whenever the owner cannot be zoomed further, the client should update {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}.

If you are using this with a custom View, please call {@link #setVisible(boolean) setVisible(false)} from {@link View#onDetachedFromWindow} and from {@link View#onVisibilityChanged} when visibility != View.VISIBLE.

Fields Summary
private static final String
TAG
private static final int
ZOOM_CONTROLS_TIMEOUT
private static final int
ZOOM_CONTROLS_TOUCH_PADDING
private int
mTouchPaddingScaledSq
private final android.content.Context
mContext
private final android.view.WindowManager
mWindowManager
private boolean
mAutoDismissControls
private final android.view.View
mOwnerView
The view that is being zoomed by this zoom controller.
private final int[]
mOwnerViewRawLocation
The location of the owner view on the screen. This is recalculated each time the zoom controller is shown.
private final FrameLayout
mContainer
The container that is added as a window.
private android.view.WindowManager.LayoutParams
mContainerLayoutParams
private final int[]
mContainerRawLocation
private ZoomControls
mControls
private android.view.View
mTouchTargetView
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 final int[]
mTouchTargetWindowLocation
The {@link #mTouchTargetView}'s location in window, set on touch down.
private boolean
mReleaseTouchListenerOnUp
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 up/cancel, and then set the owner's touch listener to null.

Otherwise, the owner view would get mismatched events (i.e., touch move even though it never got the touch down.)

private boolean
mIsVisible
Whether the container has been added to the window manager.
private final android.graphics.Rect
mTempRect
private final int[]
mTempIntArray
private OnZoomListener
mCallback
private Runnable
mPostedVisibleInitializer
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 final android.content.IntentFilter
mConfigurationChangedFilter
private final android.content.BroadcastReceiver
mConfigurationChangedReceiver
Needed to reposition the zoom controls after configuration changes.
private static final int
MSG_POST_CONFIGURATION_CHANGED
When configuration changes, this is called after the UI thread is idle.
private static final int
MSG_DISMISS_ZOOM_CONTROLS
Used to delay the zoom controller dismissal.
private static final int
MSG_POST_SET_VISIBLE
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 final android.os.Handler
mHandler
Constructors Summary
public ZoomButtonsController(android.view.View ownerView)
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.


                                               
       
        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();
    
Methods Summary
private FrameLayoutcreateContainer()

        LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        // Controls are positioned BOTTOM | CENTER with respect to the owner view.
        lp.gravity = Gravity.TOP | Gravity.START;
        lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
                LayoutParams.FLAG_NOT_FOCUSABLE |
                LayoutParams.FLAG_LAYOUT_NO_LIMITS |
                LayoutParams.FLAG_ALT_FOCUSABLE_IM;
        lp.height = LayoutParams.WRAP_CONTENT;
        lp.width = LayoutParams.MATCH_PARENT;
        lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
        lp.format = PixelFormat.TRANSLUCENT;
        lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons;
        mContainerLayoutParams = lp;

        FrameLayout container = new Container(mContext);
        container.setLayoutParams(lp);
        container.setMeasureAllChildren(true);

        LayoutInflater inflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(com.android.internal.R.layout.zoom_container, 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);
            }
        });

        return container;
    
private voiddismissControlsDelayed(int delay)

        if (mAutoDismissControls) {
            mHandler.removeMessages(MSG_DISMISS_ZOOM_CONTROLS);
            mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_CONTROLS, delay);
        }
    
private android.view.ViewfindViewForTouch(int rawX, int rawY)
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.

        // Reverse order so the child drawn on top gets first dibs.
        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) {
                continue;
            }

            child.getHitRect(frame);
            if (frame.contains(containerCoordsX, containerCoordsY)) {
                return child;
            }

            int distanceX;
            if (containerCoordsX >= frame.left && containerCoordsX <= frame.right) {
                distanceX = 0;
            } else {
                distanceX = Math.min(Math.abs(frame.left - containerCoordsX),
                    Math.abs(containerCoordsX - frame.right));
            }
            int distanceY;
            if (containerCoordsY >= frame.top && containerCoordsY <= frame.bottom) {
                distanceY = 0;
            } else {
                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 closestChild;
    
public android.view.ViewGroupgetContainer()
Gets the container that is the parent of the zoom controls.

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.

        return mContainer;
    
public android.view.ViewgetZoomControls()
Gets the view for the zoom controls.

return
The zoom controls view.

        return mControls;
    
public booleanisAutoDismissed()
Whether the zoom controls will be automatically dismissed after showing.

return
Whether the zoom controls will be auto dismissed after showing.

        return mAutoDismissControls;
    
private booleanisInterestingKey(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;
        }
    
public booleanisVisible()
Whether the zoom controls are visible to the user.

return
Whether the zoom controls are visible to the user.

        return mIsVisible;
    
private booleanonContainerKey(android.view.KeyEvent event)

        int keyCode = event.getKeyCode();
        if (isInterestingKey(keyCode)) {

            if (keyCode == KeyEvent.KEYCODE_BACK) {
                if (event.getAction() == KeyEvent.ACTION_DOWN
                        && event.getRepeatCount() == 0) {
                    if (mOwnerView != null) {
                        KeyEvent.DispatcherState ds = mOwnerView.getKeyDispatcherState();
                        if (ds != null) {
                            ds.startTracking(event, this);
                        }
                    }
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP
                        && event.isTracking() && !event.isCanceled()) {
                    setVisible(false);
                    return true;
                }
                
            } else {
                dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
            }

            // Let the container handle the key
            return false;

        } else {

            ViewRootImpl viewRoot = mOwnerView.getViewRootImpl();
            if (viewRoot != null) {
                viewRoot.dispatchInputEvent(event);
            }

            // We gave the key to the owner, don't let the container handle this key
            return true;
        }
    
private voidonPostConfigurationChanged()

        dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
        refreshPositioningVariables();
    
public booleanonTouch(android.view.View v, android.view.MotionEvent event)

hide
The ZoomButtonsController implements the OnTouchListener, but this does not need to be shown in its public API.

        int action = event.getAction();

        if (event.getPointerCount() > 1) {
            // ZoomButtonsController doesn't handle mutitouch. Give up control.
            return false;
        }

        if (mReleaseTouchListenerOnUp) {
            // The controls were 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;
        }

        dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);

        View targetView = mTouchTargetView;

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                targetView = findViewForTouch((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 = 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(mOwnerViewRawLocation[0] - targetViewRawX,
                    mOwnerViewRawLocation[1] - targetViewRawY);
            /* Disallow negative coordinates (which can occur due to
             * ZOOM_CONTROLS_TOUCH_PADDING) */
            // These are floats because we need to potentially offset away this exact amount
            float containerX = containerEvent.getX();
            float containerY = containerEvent.getY();
            if (containerX < 0 && containerX > -ZOOM_CONTROLS_TOUCH_PADDING) {
                containerEvent.offsetLocation(-containerX, 0);
            }
            if (containerY < 0 && containerY > -ZOOM_CONTROLS_TOUCH_PADDING) {
                containerEvent.offsetLocation(0, -containerY);
            }
            boolean retValue = targetView.dispatchTouchEvent(containerEvent);
            containerEvent.recycle();
            return retValue;

        } else {
            return false;
        }
    
private voidrefreshPositioningVariables()

        // if the mOwnerView is detached from window then skip.
        if (mOwnerView.getWindowToken() == null) return;

        // 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.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);
        }

    
public voidsetAutoDismissed(boolean autoDismiss)
Sets whether the zoom controls will be automatically dismissed after showing.

        if (mAutoDismissControls == autoDismiss) return;
        mAutoDismissControls = autoDismiss;
    
public voidsetFocusable(boolean focusable)
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.

        int oldFlags = mContainerLayoutParams.flags;
        if (focusable) {
            mContainerLayoutParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
        } else {
            mContainerLayoutParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
        }

        if ((mContainerLayoutParams.flags != oldFlags) && mIsVisible) {
            mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
        }
    
public voidsetOnZoomListener(android.widget.ZoomButtonsController$OnZoomListener listener)
Sets the {@link OnZoomListener} listener that receives callbacks to zoom.

param
listener The listener that will be told to zoom.

        mCallback = listener;
    
private voidsetTouchTargetView(android.view.View view)

        mTouchTargetView = view;
        if (view != null) {
            view.getLocationInWindow(mTouchTargetWindowLocation);
        }
    
public voidsetVisible(boolean visible)
Sets whether the zoom controls should be visible to the user.

param
visible Whether the zoom controls should be visible to the user.


        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);
            }
        }

    
public voidsetZoomInEnabled(boolean enabled)
Whether to enable the zoom in control.

param
enabled Whether to enable the zoom in control.

        mControls.setIsZoomInEnabled(enabled);
    
public voidsetZoomOutEnabled(boolean enabled)
Whether to enable the zoom out control.

param
enabled Whether to enable the zoom out control.

        mControls.setIsZoomOutEnabled(enabled);
    
public voidsetZoomSpeed(long speed)
Sets the delay between zoom callbacks as the user holds a zoom button.

param
speed The delay in milliseconds between zoom callbacks.

        mControls.setZoomSpeed(speed);