Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮

前言

之前写过屏蔽系统导航栏功能的文章,具体可看Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar

在某些特殊定制的版本中要求完全去掉导航栏,那么当用户点进一些系统自带的应用界面如设置、联系人等,就没法退出了,虽然可以在actionBar中添加back按钮,但总不能每一个app都去添加吧。所以灵机一动我们就给系统添加一个全屏可拖拽的浮窗按钮,点击的时候处理返回键的逻辑。它大概长这样(审美可能丑了点,你们可以自由发挥)


图1 最终效果图

思路分析

  1. 通过分析之前的NavigationBar代码,发现系统是通过WindowManager添加View的方式来实现,此处我们也可以模拟这种方法来添加
  2. 添加悬浮窗以后监听触摸事件,跟随手指移动重新修改view的layoutParam
  3. 松手后获取当前X坐标,小于屏幕width的一半则平移归位至屏幕左边
  4. 添加系统的返回按键功能

一、添加悬浮窗

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事件给系统来实现返回功能

源码位置frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.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

时间: 2024-08-09 02:20:17

Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮的相关文章

Android6.0 源码修改之Settings音量调节界面增加通话音量调节

前言 今天客户提了个需求,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就来了.需要优化两个地方 1.在正常情况下,按物理音量加减键都显示 通话音量调节seekBar,可方便快速调节通话音量 2.在Settings中提示音界面点击设置进入,增加通话音量调节seekBar 修改前 修改后 实现 第一个功能 先来完成第一个功能,还是通过Hierarchy View查看布局结构,查找到布局文件id为volume_

在Ubuntu Server14.04上编译Android6.0源码

此前编译过Android4.4的源码,但是现在Android都到了7.0的版本,不禁让我感叹Google的步伐真心难跟上,趁这周周末时间比较充裕,于是在过去的24小时里,毅然花了9个小时编译了一把Android6.0的源码,但是昨天编译完之后已经很晚了,没来得及记录编译的步骤,今天才慢悠悠地来记录一下我在Ubuntu Server14.04上编译Android6.0源码的步骤.好了,废话不多说,我们开始吧! 步骤一: 安装Ubuntu系统.我们既可以通过虚拟机的方式安装Ubuntu,也可以直接在

ubuntu12.04下编译 全志A33 android6.0 源码

有错误请指出. 入职4天了,为了编译android6.0搞的很烦,遇到了很多问题,会将我遇到的问题写出来,做个总结. 我用的是ubuntu12.04,16.04应该也是可行的. 编译时遇到的问题大部分是缺少包的问题,编译之前首先 我装完ubuntu自带了openjdk7,但是之前读开发手册需要jdk1.6,先去下载jdk-6-linux-64.bin安装过后,配置环境变量,配置环境变量后 执行以下命令,单单是编译,这里有很多都不需要,不过为了以防万一 $sudo apt-get install

Ubuntu16.04下编译android6.0源码

http://blog.csdn.net/cnliwy/article/details/52189349 作为一名合格的android开发人员,怎么能不会编译android源码呢!一定要来一次说编译就编译的旅程,否则你的人生是不完整的!好,那么我们进入正题! 本次编译环境采用Ubuntu16.04LTE,android源码选择android6.0!官方建议采用Ubuntu14版本,主要是我刚开始没在意系统版本,然后就直接下载安装16.04版本了,然后也懒得换了,所以就选择了这个版本....下面我

Android6.0源码下载编译刷入真机

编译环境是Ubuntu12.04.手机nexus 5,编译安卓6.0.1源码并烧录到真机. 源码用的是科大的镜像:http://mirrors.ustc.edu.cn/aosp-monthly/,下载完之后会有一个aosp-latest.tar文件,然后新建一个仓库,把它解压到你的仓库里.这里下载会需要点时间. 然后repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b 你的版本,比如现在我们编译6.0.1的版本,就直接r

Android6.0源码分析之录音功能(一)【转】

本文转载自:http://blog.csdn.net/zrf1335348191/article/details/54949549 从现在开始一周时间研究录音,下周出来一个完整的博客,监督,激励!!! 2017-02-09--------2017-02-17 ------------------------------------------------------------------------------------------------------------------------

[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程

本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6.0的源码编译出来了,各种配置,各种error,各种爬坑,特写此博客记录爬坑经历.先上图,Ubuntu上编译完后成功运行模拟器,如图: 2 编译环境 UbuntuKylin 16.04 LTS Android 6.0_r1 Open JDK 7 3 准备工作 (1) 下载android 6.0源码.

Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 (转)

1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6.0的源码编译出来了,各种配置,各种error,各种爬坑,特写此博客记录爬坑经历.先上图,Ubuntu上编译完后成功运行模拟器,如图: 2 编译环境 UbuntuKylin 16.04 LTS Android 6.0_r1 Open JDK 7 3 准备工作 (1) 下载Android 6.0源码. Androdi 6.0源码下载地址: http://pan.baidu.com/s/1o6N86a2 感谢下面这位博主上传

[Android编译(二)] 从谷歌官网下载android 6.0源码、编译并刷入nexus 6p手机

1 前言 经过一周的奋战,终于从谷歌官网上下载最新的android 6.0.1_r62源码,编译成功,并成功的刷入nexus6p,接着root完毕,现写下这篇博客记录一下实践过程. 2 简介 自己下载android系统源码,修改定制,然后编译刷入安卓手机,想想还有点小激动呢.简单点说一句话--定制我们自己的MIUI,这就是android的魅力,这篇博客博主就来教大家实现自己的定制系统. 首先,要明白下面的基础知识: (1) 什么是aosp? aosp就是android open source p