diff options
Diffstat (limited to 'core/java')
3 files changed, 190 insertions, 3 deletions
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 4bb6d06..ff9678c 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -16,9 +16,6 @@ package com.android.internal.view.menu; -import com.android.internal.view.ActionBarPolicy; -import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView; - import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -27,11 +24,18 @@ import android.os.Parcelable; import android.util.SparseBooleanArray; import android.view.ActionProvider; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.View; +import android.view.ViewConfiguration; import android.view.View.MeasureSpec; import android.view.ViewGroup; +import android.widget.AbsListView; import android.widget.ImageButton; +import android.widget.ListPopupWindow; + +import com.android.internal.view.ActionBarPolicy; +import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView; import java.util.ArrayList; @@ -559,6 +563,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter setFocusable(true); setVisibility(VISIBLE); setEnabled(true); + setOnTouchListener(new OverflowForwardListener(context)); } @Override @@ -572,10 +577,12 @@ public class ActionMenuPresenter extends BaseMenuPresenter return true; } + @Override public boolean needsDividerBefore() { return false; } + @Override public boolean needsDividerAfter() { return false; } @@ -675,4 +682,45 @@ public class ActionMenuPresenter extends BaseMenuPresenter mPostedOpenRunnable = null; } } + + private class OverflowForwardListener extends TouchForwardingListener { + /** Scaled touch slop, used for detecting movement outside bounds. */ + private final float mScaledTouchSlop; + + private int mActivePointerId = MotionEvent.INVALID_POINTER_ID; + + public OverflowForwardListener(Context context) { + mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + } + + @Override + public boolean onTouchObserved(View v, MotionEvent ev) { + if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && v.isEnabled() + && !v.pointInView(ev.getX(), ev.getY(), mScaledTouchSlop)) { + mActivePointerId = ev.getPointerId(0); + v.performClick(); + return true; + } + + return false; + } + + @Override + public boolean onTouchForwarded(View v, MotionEvent ev) { + if (!v.isEnabled() || mOverflowPopup == null || !mOverflowPopup.isShowing()) { + return false; + } + + if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) { + if (mOverflowPopup.forwardMotionEvent(v, ev, mActivePointerId)) { + return true; + } + + mActivePointerId = MotionEvent.INVALID_POINTER_ID; + } + + mOverflowPopup.dismiss(); + return false; + } + } } diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index 6d39860..945f42b 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -22,10 +22,12 @@ import android.os.Parcelable; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; @@ -46,6 +48,8 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout; + private final int[] mTempLocation = new int[2]; + private final Context mContext; private final LayoutInflater mInflater; private final MenuBuilder mMenu; @@ -158,6 +162,69 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On return mPopup != null && mPopup.isShowing(); } + public boolean forwardMotionEvent(View v, MotionEvent ev, int activePointerId) { + if (mPopup == null || !mPopup.isShowing()) { + return false; + } + + final AbsListView dstView = mPopup.getListView(); + if (dstView == null || !dstView.isShown()) { + return false; + } + + boolean cancelForwarding = false; + final int actionMasked = ev.getActionMasked(); + switch (actionMasked) { + case MotionEvent.ACTION_CANCEL: + cancelForwarding = true; + break; + case MotionEvent.ACTION_UP: + cancelForwarding = true; + // $FALL-THROUGH$ + case MotionEvent.ACTION_MOVE: + final int activeIndex = ev.findPointerIndex(activePointerId); + if (activeIndex < 0) { + return false; + } + + final int[] location = mTempLocation; + int x = (int) ev.getX(activeIndex); + int y = (int) ev.getY(activeIndex); + + // Convert to global coordinates. + v.getLocationOnScreen(location); + x += location[0]; + y += location[1]; + + // Convert to local coordinates. + dstView.getLocationOnScreen(location); + x -= location[0]; + y -= location[1]; + + final int position = dstView.pointToPosition(x, y); + if (position >= 0) { + final int childCount = dstView.getChildCount(); + final int firstVisiblePosition = dstView.getFirstVisiblePosition(); + final int index = position - firstVisiblePosition; + if (index < childCount) { + final View child = dstView.getChildAt(index); + if (actionMasked == MotionEvent.ACTION_UP) { + // Touch ended, click highlighted item. + final long id = dstView.getItemIdAtPosition(position); + dstView.performItemClick(child, position, id); + } else if (actionMasked == MotionEvent.ACTION_MOVE) { + // TODO: Highlight touched item, activate after + // long-hover. Consider forwarding events as HOVER and + // letting ListView handle this. + } + } + } + break; + } + + return true; + } + @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { MenuAdapter adapter = mAdapter; diff --git a/core/java/com/android/internal/view/menu/TouchForwardingListener.java b/core/java/com/android/internal/view/menu/TouchForwardingListener.java new file mode 100644 index 0000000..d1086de --- /dev/null +++ b/core/java/com/android/internal/view/menu/TouchForwardingListener.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view.menu; + +import android.view.MotionEvent; +import android.view.View; + +/** + * Touch listener used to intercept touches and forward them out of a view. + */ +abstract class TouchForwardingListener implements View.OnTouchListener { + /** Whether this listener is currently forwarding touch events. */ + private boolean mForwarding; + + @Override + public boolean onTouch(View v, MotionEvent ev) { + final int actionMasked = ev.getActionMasked(); + + if (mForwarding) { + // Rejecting the event or ending the stream stops forwarding. + if (!onTouchForwarded(v, ev) || actionMasked == MotionEvent.ACTION_UP + || actionMasked == MotionEvent.ACTION_CANCEL) { + stopForwarding(); + } + } else { + if (onTouchObserved(v, ev)) { + startForwarding(); + } + } + + return mForwarding; + } + + public void startForwarding() { + mForwarding = true; + } + + public void stopForwarding() { + mForwarding = false; + } + + /** + * Attempts to start forwarding motion events. + * + * @param v The view that triggered forwarding. + * @return True to start forwarding motion events, or false to cancel. + */ + public abstract boolean onTouchObserved(View v, MotionEvent ev); + + /** + * Handles forwarded motion events. + * + * @param v The view from which the event was forwarded. + * @param ev The forwarded motion event. + * @return True to continue forwarding motion events, or false to cancel. + */ + public abstract boolean onTouchForwarded(View v, MotionEvent ev); +} |