天天看點

Android6.0 源碼修改之 仿IOS添加全屏可拖拽浮窗傳回按鈕

Android6.0 源碼修改之 仿IOS添加全屏可拖拽浮窗傳回按鈕

前言

之前寫過屏蔽系統導航欄功能的文章,具體可看Android6.0 源碼修改之屏蔽導航欄虛拟按鍵(Home和RecentAPP)/動态顯示和隐藏NavigationBar

在某些特殊定制的版本中要求完全去掉導航欄,那麼當使用者點進一些系統自帶的應用界面如設定、聯系人等,就沒法退出了,雖然可以在actionBar中添加back按鈕,但總不能每一個app都去添加吧。是以靈機一動我們就給系統添加一個全屏可拖拽的浮窗按鈕,點選的時候處理傳回鍵的邏輯。它大概長這樣(審美可能醜了點,你們可以自由發揮)

在這裡插入圖檔描述

圖1 最終效果圖

思路分析

通過分析之前的NavigationBar代碼,發現系統是通過WindowManager添加View的方式來實作,此處我們也可以模拟這種方法來添加

添加懸浮窗以後監聽觸摸事件,跟随手指移動重新修改view的layoutParam

松手後擷取目前X坐标,小于螢幕width的一半則平移歸位至螢幕左邊

添加系統的傳回按鍵功能

一、添加懸浮窗

private void showFloatingWindow() {

DisplayMetrics outMetrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(outMetrics);
screenWidth = outMetrics.widthPixels;
screenHeight = outMetrics.heightPixels;

layoutParams = new WindowManager.LayoutParams();
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = 100;
layoutParams.height = 100;
layoutParams.x = 200;
layoutParams.y = 200;

button = new ImageButton(mContext);
button.setBackground(mContext.getResources().getDrawable(R.drawable.fab_background));//系統通訊錄裡的藍色圓形圖示
button.setImageResource(R.drawable.ic_sysbar_back);//系統本身的back圖示
mWindowManager.addView(button, layoutParams);
isShowFloatingView = true;           

}

代碼很簡單,就是通過windowManager添加一個ImageButton,寬高都是100的,位置在螢幕左上角為原點的200,200。需要注意的是因為我們是在源碼裡添加,而且是M的版本,是以type為WindowManager.LayoutParams.TYPE_PHONE。如果是在普通的app裡注意事項可參考這篇

二、添加觸摸事件監聽

button.setOnTouchListener(new FloatingOnTouchListener());

private class FloatingOnTouchListener implements View.OnTouchListener {

private int lastX;
private int lastY;

@Override
public boolean onTouch(View view, MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            isDrag = false;
            lastX = (int) event.getRawX();
            lastY = (int) event.getRawY();
            break;

        case MotionEvent.ACTION_MOVE:
            isDrag = true;
            int nowX = (int) event.getRawX();
            int nowY = (int) event.getRawY();
            int movedX = nowX - lastX;
            int movedY = nowY - lastY;
            lastX = nowX;
            lastY = nowY;
            layoutParams.x = layoutParams.x + movedX;
            layoutParams.y = layoutParams.y + movedY;
            //擷取目前手指移動的x和y,通過updateViewLayout方法将改變後的x和y設定給button
            mWindowManager.updateViewLayout(view, layoutParams);
            break;

        case MotionEvent.ACTION_UP:
            if (isDrag) {
                log("lastX=" + lastX + "  screenWidth=" + screenWidth);
                //手指擡起時,判斷是需要滑動到螢幕左邊還是螢幕右邊
                if (lastX >= screenWidth / 2) {
                    setAnimation(view, lastX, screenWidth);
                } else {
                    setAnimation(view, -lastX, 0);
                }
            }
            break;
    }
    //傳回true則消費事件,傳回false則傳遞事件,此處特殊處理是為了和點選事件區分
    return isDrag || view.onTouchEvent(event);
}           

三、添加擡起滑動歸位動畫

private void setAnimation(final View view, int fromX, int toX) {

final ValueAnimator animator = ValueAnimator.ofInt(fromX, toX);
    if (Math.abs(fromX) < screenWidth / 4 || fromX > screenWidth * 3 / 4)
        animator.setDuration(300);
    else
        animator.setDuration(600);

    animator.setInterpolator(new LinearInterpolator());
    animator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {}
        @Override
        public void onAnimationEnd(Animator animation) {
            log("onAnimationEnd=");
            savePreValue(layoutParams.x, layoutParams.y);
        }
        @Override
        public void onAnimationCancel(Animator animation) {}
        @Override
        public void onAnimationRepeat(Animator animation) {}
    });
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int current = (int) animator.getAnimatedValue();
            log("current=" + current);

            layoutParams.x = Math.abs(current);
            mWindowManager.updateViewLayout(view, layoutParams);
        }
    });
    animator.start();
}           

同樣是通過改變button的x和y值來達到滑動效果,隻不過我隻需要x平移,y為0,需要斜着滑的你們可自由發揮,為了使滑動看上去平滑,給動畫添加了一個線性插值器,設定滑動時間,監聽傳回插值進度,這樣動态設定給button。為了儲存button的最終位置,添加了一個動畫完成監聽,并将x和y寫入到SharedPreferences中儲存。

四、添加點選傳回功能

通過列印日志分析,系統導航欄的傳回按鍵,發現其原理是通過KeyButtonView的觸摸事件發送一個KeyEvent事件給系統來實作傳回功能

源碼位置frameworksbasepackagesSystemUIsrccomandroidsystemuistatusbarpolicyKeyButtonView.java

public boolean onTouchEvent(MotionEvent ev) {

final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
    mGestureAborted = false;
}
if (mGestureAborted) {
    return false;
}

switch (action) {
    case MotionEvent.ACTION_DOWN:
        //按下的時間
        mDownTime = SystemClock.uptimeMillis();
        setPressed(true);
        if (mCode != 0) {//按下事件
            sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
        } else {
            // Provide the same haptic feedback that the system offers for virtual keys.
            performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
        }
        removeCallbacks(mCheckLongPress);
        postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
        break;
    case MotionEvent.ACTION_MOVE:
        x = (int)ev.getX();
        y = (int)ev.getY();
        setPressed(x >= -mTouchSlop
                && x < getWidth() + mTouchSlop
                && y >= -mTouchSlop
                && y < getHeight() + mTouchSlop);
        break;
    case MotionEvent.ACTION_CANCEL:
        setPressed(false);
        if (mCode != 0) {
            sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
        }
        removeCallbacks(mCheckLongPress);
        break;
    case MotionEvent.ACTION_UP:
        final boolean doIt = isPressed();
        setPressed(false);
        if (mCode != 0) {
            if (doIt) {//擡起事件
                sendEvent(KeyEvent.ACTION_UP, 0);
                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
                playSoundEffect(SoundEffectConstants.CLICK);
            } else {
                sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
            }
        } else {
            // no key code, just a regular ImageView
            if (doIt) {
                performClick();
            }
        }
        removeCallbacks(mCheckLongPress);
        break;
}

return true;           

//以下為我們給button添加的點選事件

private void sendEvent(int action, int flags, long when) {

int mCode = 4;
Log.e(TAG, "mCode="+mCode + "  flags="+flags);
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(when - 100, when, action, mCode, repeatCount,
        0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
        flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
        InputDevice.SOURCE_KEYBOARD);
InputManager.getInstance().injectInputEvent(ev,
        InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);           

button.setOnClickListener(new View.OnClickListener() {

@Override
    public void onClick(View v) {
        Log.e(TAG,"click dragButton ...");
        final long mDownTime = SystemClock.uptimeMillis();
        //onBackPressed();
        sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                sendEvent(KeyEvent.ACTION_UP, 0, SystemClock.uptimeMillis());
            }
        }, 300);
    }
});           

需要注意的地方,系統傳回鍵對應的code為4,是以mCode=4,KeyButtonView的觸摸事件包含按下和擡起,是以我們隻需模拟發送按下和擡起事件,可以看到擡起事件加了300ms的延時發送,這是關鍵不然系統不會處理。

原文位址

https://www.cnblogs.com/cczheng-666/p/10741082.html

繼續閱讀