Loading core/java/com/android/internal/view/menu/ActionMenuPresenter.java +51 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -559,6 +563,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter setFocusable(true); setVisibility(VISIBLE); setEnabled(true); setOnTouchListener(new OverflowForwardListener(context)); } @Override Loading @@ -572,10 +577,12 @@ public class ActionMenuPresenter extends BaseMenuPresenter return true; } @Override public boolean needsDividerBefore() { return false; } @Override public boolean needsDividerAfter() { return false; } Loading Loading @@ -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; } } } core/java/com/android/internal/view/menu/MenuPopupHelper.java +67 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading core/java/com/android/internal/view/menu/TouchForwardingListener.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * 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); } Loading
core/java/com/android/internal/view/menu/ActionMenuPresenter.java +51 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -559,6 +563,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter setFocusable(true); setVisibility(VISIBLE); setEnabled(true); setOnTouchListener(new OverflowForwardListener(context)); } @Override Loading @@ -572,10 +577,12 @@ public class ActionMenuPresenter extends BaseMenuPresenter return true; } @Override public boolean needsDividerBefore() { return false; } @Override public boolean needsDividerAfter() { return false; } Loading Loading @@ -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; } } }
core/java/com/android/internal/view/menu/MenuPopupHelper.java +67 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading
core/java/com/android/internal/view/menu/TouchForwardingListener.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * 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); }