1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
|
/*
* 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.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.View.OnClickListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
/*
* Implementation notes:
* - The zoom controls are displayed in their own window.
* (Easier for the client and better performance)
* - This window is never touchable, and by default is not focusable.
* Its rect is quite big (fills horizontally) but has empty space between the
* edges and center. Touches there should be given to the owner. Instead of
* having the window touchable and dispatching these empty touch events to the
* owner, we set the window to not touchable and steal events from owner
* via onTouchListener.
* - To make the buttons clickable, it attaches an OnTouchListener to the owner
* view and does the hit detection locally (attaches when visible, detaches when invisible).
* - When it is focusable, it forwards uninteresting events to the owner view's
* view hierarchy.
*/
/**
* 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.
* <p>
* 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)}.
* <p>
* 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 <code>visibility != View.VISIBLE</code>.
*
* @deprecated This functionality and UI is better handled with custom views and layouts
* rather than a dedicated zoom-control widget
*/
@Deprecated
public class ZoomButtonsController implements View.OnTouchListener {
private static final String TAG = "ZoomButtonsController";
private static final int ZOOM_CONTROLS_TIMEOUT =
(int) ViewConfiguration.getZoomControlsTimeout();
private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
private int mTouchPaddingScaledSq;
private final Context mContext;
private final WindowManager mWindowManager;
private boolean mAutoDismissControls = true;
/**
* The view that is being zoomed by this zoom controller.
*/
private final View mOwnerView;
/**
* The location of the owner view on the screen. This is recalculated
* each time the zoom controller is shown.
*/
private final int[] mOwnerViewRawLocation = new int[2];
/**
* The container that is added as a window.
*/
private final FrameLayout mContainer;
private LayoutParams mContainerLayoutParams;
private final int[] mContainerRawLocation = 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 final 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
* up/cancel, and then set the owner's touch listener to null.
* <p>
* Otherwise, the owner view would get mismatched events (i.e., touch move
* even though it never got the touch down.)
*/
private boolean mReleaseTouchListenerOnUp;
/** Whether the container has been added to the window manager. */
private boolean mIsVisible;
private final Rect mTempRect = new Rect();
private final int[] mTempIntArray = new int[2];
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 final IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
/**
* Needed to reposition the zoom controls after configuration changes.
*/
private final 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 controller dismissal. */
private static final int MSG_DISMISS_ZOOM_CONTROLS = 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 final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_POST_CONFIGURATION_CHANGED:
onPostConfigurationChanged();
break;
case MSG_DISMISS_ZOOM_CONTROLS:
setVisible(false);
break;
case MSG_POST_SET_VISIBLE:
if (mOwnerView.getWindowToken() == null) {
// 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);
}
break;
}
}
};
/**
* 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);
// 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 = 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;
}
/**
* 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 ((mContainerLayoutParams.flags != oldFlags) && mIsVisible) {
mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
}
}
/**
* Whether the zoom controls will be automatically dismissed after showing.
*
* @return Whether the zoom controls will be auto dismissed after showing.
*/
public boolean isAutoDismissed() {
return mAutoDismissControls;
}
/**
* Sets whether the zoom controls will be automatically dismissed after
* showing.
*/
public void setAutoDismissed(boolean autoDismiss) {
if (mAutoDismissControls == autoDismiss) return;
mAutoDismissControls = autoDismiss;
}
/**
* 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 (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.removeViewImmediate(mContainer);
mHandler.removeCallbacks(mPostedVisibleInitializer);
if (mCallback != null) {
mCallback.onVisibilityChanged(false);
}
}
}
/**
* 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;
}
/**
* Gets the view for the zoom controls.
*
* @return The zoom controls view.
*/
public View getZoomControls() {
return mControls;
}
private void dismissControlsDelayed(int delay) {
if (mAutoDismissControls) {
mHandler.removeMessages(MSG_DISMISS_ZOOM_CONTROLS);
mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_CONTROLS, delay);
}
}
private void refreshPositioningVariables() {
// 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);
}
}
/* 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) {
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 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;
}
}
/**
* @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();
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 void setTouchTargetView(View view) {
mTouchTargetView = view;
if (view != null) {
view.getLocationInWindow(mTouchTargetWindowLocation);
}
}
/**
* 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 findViewForTouch(int rawX, int rawY) {
// 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;
}
private void onPostConfigurationChanged() {
dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT);
refreshPositioningVariables();
}
/**
* Interface that will be called when the user performs an interaction that
* triggers some action, for example zooming.
*/
public interface OnZoomListener {
/**
* 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);
}
}
}
|