近期项目由于没有预留硬件返回按键,所以只能做成跟全面屏一样的左滑返回的手势逻辑。
实现方案:
1.参考android官方文档,https://developer.android.google.cn/training/gestures,针对apk层面的监听可以用,这样子做就需要所有的apk页面都需要监听,如果是自己设计的apk还可以,但是第三方apk则不能监听,无法实现返回。
framework/base\core\java\android\view\GestureDetector.java中接口监听
public class MainActivity extends Activity implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener{
private static final String DEBUG_TAG = "Gestures";
private GestureDetectorCompat mDetector;
// Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Instantiate the gesture detector with the
// application context and an implementation of
// GestureDetector.OnGestureListener
mDetector = new GestureDetectorCompat(this,this);
// Set the gesture detector as the double tap
// listener.
mDetector.setOnDoubleTapListener(this);
}
@Override
public boolean onTouchEvent(MotionEvent event){
if (this.mDetector.onTouchEvent(event)) {
return true;
}
return super.onTouchEvent(event);
}
@Override
public boolean onDown(MotionEvent event) {
Log.d(DEBUG_TAG,"onDown: " + event.toString());
return true;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
return true;
}
@Override
public void onLongPress(MotionEvent event) {
Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
}
@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
float distanceY) {
Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
return true;
}
@Override
public void onShowPress(MotionEvent event) {
Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
}
@Override
public boolean onSingleTapUp(MotionEvent event) {
Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
return true;
}
@Override
public boolean onDoubleTap(MotionEvent event) {
Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent event) {
Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
return true;
}
}
2.想要第三方也能监听按键的back按键以及退出,则必须要把手势的逻辑做在系统层。
想到系统层的导航栏上下滑隐藏的逻辑,在framework\base\services\core\java\com\android\server\policy\PhoneWindowManager.java中ini有手势检测的逻辑。
mSystemGestures = new SystemGesturesPointerEventListener(context,
new SystemGesturesPointerEventListener.Callbacks() {
@Override
/*上滑*/
public void onSwipeFromTop() {
if (mStatusBar != null) {
requestTransientBars(mStatusBar);
}
}
/*下滑*/
@Override
public void onSwipeFromBottom() {
if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_BOTTOM) {
requestTransientBars(mNavigationBar);
}
}
/*右滑*/
@Override
public void onSwipeFromRight() {
if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_RIGHT) {
requestTransientBars(mNavigationBar);
}
}
/*左滑*/
@Override
public void onSwipeFromLeft() {
/*导航栏的相关逻辑**/
if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_LEFT) {
requestTransientBars(mNavigationBar);
}
//左滑发送返回按键
sendKeyCode(KeyEvent.KEYCODE_BACK);
}
@Override
public void onFling(int duration) {
if (mPowerManagerInternal != null) {
mPowerManagerInternal.powerHint(
PowerHint.INTERACTION, duration);
}
}
@Override
public void onDebug() {
// no-op
}
@Override
public void onDown() {
mOrientationListener.onTouchStart();
}
@Override
public void onUpOrCancel() {
mOrientationListener.onTouchEnd();
}
@Override
public void onMouseHoverAtTop() {
mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS;
mHandler.sendMessageDelayed(msg, 500);
}
@Override
public void onMouseHoverAtBottom() {
mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION;
mHandler.sendMessageDelayed(msg, 500);
}
@Override
public void onMouseLeaveFromEdge() {
mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
}
});
mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext);
//手势的回调监听注册
mWindowManagerFuncs.registerPointerEventListener(mSystemGestures);
发送按键的相关逻辑实现如下,主要是参考网上模拟按键的按下,Instrumentation的sendKeyDownUpSync最终会调用input相关接口进行模拟按键的按下与松开。
/**
* Simulate key down and up
*/
private void sendKeyCode(final int keyCode) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Slog.d(TAG,"sendKeyCode by Instrumentation,keyCode="+keyCode);
Instrumentation inst = new Instrumentation();
inst.sendKeyDownUpSync(keyCode);//模拟按键的按下与松开
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
最后发现左滑无法触发onSwipeFromLeft的回调,查阅代码发现要满足一定左滑位置以及距离才能触发
/*检测手势的滑动距离*/
private int detectSwipe(int i, long time, float x, float y) {
final float fromX = mDownX[i];
final float fromY = mDownY[i];
final long elapsed = time - mDownTime[i];
if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
+ " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
if (fromY <= mSwipeStartThreshold
&& y > fromY + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_TOP;
}
if (fromY >= screenHeight - mSwipeStartThreshold
&& y < fromY - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_BOTTOM;
}
if (fromX >= screenWidth - mSwipeStartThreshold
&& x < fromX - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_RIGHT;
}
/*左滑的逻辑*/
if (fromX <= mSwipeStartThreshold
&& x > fromX + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_LEFT;
}
return SWIPE_NONE;
}
左滑成立要满足一下条件:
(1)起点x坐标<mSwipeStartThreshold
(2)终点的x坐标>(起点x坐标+mSwipeStartThreshold)
(3)elapsed < SWIPE_TIMEOUT_MS表示滑动速度要在SWIPE_TIMEOUT_MS内
其中mSwipeStartThreshold是初始化的时候com.android.internal.R.dimen.status_bar_height获取的配置项
public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) {
mContext = context;
mCallbacks = checkNull("callbacks", callbacks);
mSwipeStartThreshold = checkNull("context", context).getResources()
.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
mSwipeDistanceThreshold = mSwipeStartThreshold;
if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold
+ " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
}
./frameworks/base/core/res/res/values/dimens.xml
<dimen name="status_bar_height">0dp</dimen>
理论上每次从左侧边界滑动的时候x起点坐标应该是0,但是由于触摸板上报的坐标实际上根本无法上报x轴为0的点,基本上都是坐标点为1才可以上报,而且一般边界的坐标点都会为1-10以内的随机点,结合手的大小以及触摸板的精度,因为触摸板很少能做到这么精准的点触的。所以修改代码如下,再次编译就ok了
<dimen name="status_bar_height">12dp</dimen>