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

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

前言


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


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


20190404142711489.gif


思路分析


  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的延时发送,这是关键不然系统不会处理。

目录
相关文章
|
4天前
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
4天前
|
安全 数据安全/隐私保护 Android开发
探索Android与iOS的隐私保护策略
在数字时代,智能手机已成为我们生活中不可或缺的一部分,而随之而来的则是对个人隐私和数据安全的日益关注。本文将深入探讨Android与iOS两大操作系统在隐私保护方面的策略和实践,分析它们如何应对日益严峻的隐私挑战,以及用户应如何保护自己的数据安全。通过对比分析,我们将揭示两大系统在隐私保护方面的优势和不足,为用户提供有价值的见解和建议。
|
4天前
|
Android开发 Swift iOS开发
深入探索iOS与Android操作系统的架构差异及其对应用开发的影响
在当今数字化时代,移动设备已经成为我们日常生活和工作不可或缺的一部分。其中,iOS和Android作为全球最流行的两大移动操作系统,各自拥有独特的系统架构和设计理念。本文将深入探讨iOS与Android的系统架构差异,并分析这些差异如何影响应用开发者的开发策略和用户体验设计。通过对两者的比较,我们可以更好地理解它们各自的优势和局限性,从而为开发者提供有价值的见解,帮助他们在这两个平台上开发出更高效、更符合用户需求的应用。
|
5天前
|
前端开发 Android开发 开发者
探索Android与iOS的跨平台开发策略
在当今多元化的移动设备市场中,开发者面临着为不同操作系统设计应用的挑战。本文深入探讨了Android和iOS两大主流平台的跨平台开发策略。我们将分析使用Flutter、React Native等框架进行跨平台开发的优劣,并讨论如何克服各平台间的差异性,以实现高效、一致的用户体验。此外,文章还将提供一些实用的技巧和最佳实践,帮助开发者优化跨平台应用的性能和兼容性。
20 4
|
7天前
|
安全 Android开发 iOS开发
深入探讨Android与iOS操作系统的差异性
本文旨在通过对比分析Android和iOS两大主流移动操作系统,揭示它们在设计理念、用户体验、安全性、应用生态及系统更新等方面的根本差异。不同于传统的功能列表式摘要,本摘要强调了两大系统背后的哲学思想及其对用户日常使用的实际影响,为读者提供了一个宏观且深入的视角来理解这两种操作系统的独特之处。
|
6天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
6天前
|
存储 安全 数据安全/隐私保护
深入探索Android与iOS的隐私保护机制:一场没有硝烟的较量####
本文深度剖析了Android与iOS两大移动操作系统在用户隐私保护方面的策略与实践,揭示两者在设计理念、技术实现及用户体验上的异同。通过对比分析,旨在为读者提供一个全面而深入的视角,理解两大平台如何在保障用户隐私的同时,实现功能的丰富与便捷。本文不涉及具体产品推荐或品牌偏好,仅从技术角度出发,探讨隐私保护的现状与挑战。 ####
|
6天前
|
前端开发 Android开发 iOS开发
探索Android与iOS的跨平台开发策略
在移动应用开发的多元化时代,跨平台开发已成为开发者追求效率和广泛覆盖的重要手段。本文深入探讨了Android与iOS两大主流平台下的跨平台开发策略,分析了各自的优势与挑战,并通过实际案例展示了如何有效实施跨平台解决方案,以期为开发者提供有价值的参考和启示。
|
5天前
|
安全 Android开发 iOS开发
深入探讨Android与iOS的系统架构差异
本文旨在通过对比分析Android和iOS两大移动操作系统的系统架构,揭示它们在设计理念、安全性、应用生态及开发环境等方面的显著差异。我们将从底层架构出发,逐步剖析至用户界面层面,为开发者和科技爱好者提供一份详尽的技术参考。
16 1
|
11天前
|
移动开发 Java Android开发
探索Android与iOS开发的差异性与互联性
【10月更文挑战第32天】在移动开发的大潮中,Android和iOS两大平台各领风骚。本文将深入浅出地探讨这两个平台的开发差异,并通过实际代码示例,展示如何在各自平台上实现相似的功能。我们将从开发环境、编程语言、用户界面设计、性能优化等多个角度进行对比分析,旨在为开发者提供跨平台开发的实用指南。
33 0