Android 8.1实现Systemui 中的NavigationBar的点击隐藏与滑动显示

此篇文章只做记录一下这个功能自己实现的喜悦。如果能帮助其他人,那也荣幸之至。我会写的比较细,拿到源码谁都能改。要先谢谢网络上两位大神的博文给予的帮助。

请参考     https://blog.csdn.net/cuckoochun/article/details/84109895

                https://blog.csdn.net/u012932409/article/details/89156391

效果:

Android 8.1实现Systemui 中的NavigationBar的点击隐藏与滑动显示_第1张图片Android 8.1实现Systemui 中的NavigationBar的点击隐藏与滑动显示_第2张图片

从图中我们可以看出的效果是在底部导航栏添加一个按钮,他的功能是点击隐藏底部NavigationBar,然后我们在上滑底部,再显示NavigationBar。基本就是这样的一个流程。具体怎么实现,看下面》》》》》


以我之前写文章的习惯,我会先把需要用到的文件列出来(如果你在看这篇文章,那就可以先把这些文件全部打开了)。本文基于MTK平台Android 8.1实现的功能,修改文件均在vendor目录下面(千万不要像我刚开始直接在framework目录,写了一天发现怎么都不对)!

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFragment.java

vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java

frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java(注意这不在vendor)

vendor\mediatek\proprietary\packages\apps\SystemUI\res\values-sw372dp\config.xml

以上就是基本源码里面自带的目录文件,需要自己增加的,后面会详细写到。


我们看到的NavigationBar他是Android 系统Systemui这个app里面的一部分,NavigationBar默认底部是三个按钮,back,home,recent。

我们第一步先在NavigationBar里面添加一个hide的按钮:

首先添加布局:

vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\hide_show.xml




我们所看到的back、home、recent都是上面这样类似的布局,独立存在,为了方便,我把系统中home的布局贴出来对比

KeyButtonView这个自定义的view里面处理了所有这些按钮需要处理的响应操作。也许会有人问没看到设置图片,莫急后面会讲到。



完成这一步我们就要看看这个NavigationBar的加载流程了。

首先在StatusBar.java这个文件中(Android7.0不是这个),有这样一段代码

try {
            boolean showNav = mWindowManagerService.hasNavigationBar();
            if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
            if (showNav) {
                createNavigationBar();
            }
        } catch (RemoteException ex) {
            // no window manager? good luck with that
        }

他先判断需不需要去创建NavigationBar,我们不管其他情况,只讲顺序创建即可,对于createNavigationBar();方法我们需要一探究竟。

 protected void createNavigationBar() {//初始化,我们可以看到,他其实是在创建一个fragment
        mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
            mNavigationBar = (NavigationBarFragment) fragment;
            if (mLightBarController != null) {
                mNavigationBar.setLightBarController(mLightBarController);
            }
            mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
			
        });	
    }

创建一个fragment,就是我们上面目录中的NavigationBarFragment。所有的NavigationBar都在这个fragment里面实现。其中加载底部按键的的代码在NavigationBarInflaterView文件(所有文件都是上文中提到的目录文件)中,那么我们再看看NavigationBarInflaterView中做了些什么操作。

SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java

他通过方法getDefaultLayout()去获取需要展示几个布局,我们需要添加一个按钮,就在config_navBarLayout里面添加这个按钮的名字,因为这个文件在系统中有很多,如果我们不确定当前设备支持哪一种,就打印出这个String,看看具体是哪个文件中的,我的平板支持的是res\values-sw372dp\config.xml这个文件里面的配置。

protected String getDefaultLayout() {
        return mContext.getString(R.string.config_navBarLayout);
    }

我贴出config里面的设置来:

   left[.15W],hide[.5WC];back[.5WC];home[.5WC];recent[.5WC],right[.15W]

加了一个hide,其中left好right用的是W,中间的布局都是WC,5WC,表示布局占得weight。其中的值,大家可以适当修改,目前贴出的是我自己已经修改过的文件。

protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
	Log.w(TAG,"newLayout=="+newLayout);	
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 4);
	String[] hide = sets[0].split(BUTTON_SEPARATOR);//加一个隐藏按钮
        String[] start = sets[1].split(BUTTON_SEPARATOR);
        String[] center = sets[2].split(BUTTON_SEPARATOR);
        String[] end = sets[3].split(BUTTON_SEPARATOR);
		
		inflateButtons(hide, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);// add by jsb  加载隐藏按钮
        inflateButtons(hide, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);// add by jsb
		
        // Inflate these in start to end order or accessibility traversal will be messed up.
        inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
        inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
	
        inflateButtons(center, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
        inflateButtons(center, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
		
        /* addGravitySpacer(mRot0.findViewById(R.id.ends_group));
        addGravitySpacer(mRot90.findViewById(R.id.ends_group)); */

        inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
        inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
    }

inflateButtons是加载填充按钮的,其中我们需要注意的修改有下面几点:

通过inflateButtons(); 添加了一个hide按钮,

原本的inflateButtons(center, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);是inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);大家注意一下这里,就是我们看到的中间的那个home按键,在初始代码里,他是加载在布局center_group中的,这个布局大家可以去源码里面的布局查看,他是固定在中间的,所以如果不改掉这个东西,我们就会看到home键一直固定在那里。这里一定要注意了。!!!!!!!!!!!!!!!

加载完button后,就在createView()方法里面添加下面代码

else if (HIDE.equals(button)){// add by jsb for hide navigationBar
            v = inflater.inflate(R.layout.hide_show, parent, false);
        }//如果读到的initbuttons是hide,那么就加载我们自己刚才创建的布局文件

在全局加一个

public static final String HIDE = "hide";//一定要和config文件中一致

这个文件的修改就已经基本完成了,那么接下来就是看怎么加载这些图片了。我们事先要准备好两种png,

ic_sysbar_down和ic_sysbar_down_dark,一黑一白,只要和系统中一样就行,大小的话各个系统应该不一样,我的像素的22*22,先准备好了。

然后就要看文件systemui\statusbar\phone\NavigationBarView.java

首先看构造方法里面的修改

public NavigationBarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //省略部分代码------------------------------------
        updateIcons(context, Configuration.EMPTY, mConfiguration);

        mBarTransitions = new NavigationBarTransitions(this);
		
	mButtonDispatchers.put(R.id.hide, new ButtonDispatcher(R.id.hide));// add by jsb for hide navigationbar
        // getHideButton().setLongClickable(false);// add by jsb for hide navigationbar
        mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
        mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
        mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
        mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
        mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
        mButtonDispatchers.put(R.id.accessibility_button,
                new ButtonDispatcher(R.id.accessibility_button));
    }

通过mButtonDispatchers.put(R.id.hide, new ButtonDispatcher(R.id.hide));去put我们自定义的hide布局按钮。

之后添加一个公共方法

public ButtonDispatcher getHideButton() {// add by jsb
        return mButtonDispatchers.get(R.id.hide);
    }

 在setDisabledFlags方法中设置可见getHideButton()

public void setDisabledFlags(int disabledFlags, boolean force) {
        if (!force && mDisabledFlags == disabledFlags) return;

       //省略部分代码------------------------
       
	getHideButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
        getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
        getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
        getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
    }

接下来就是设置图片了:

private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
        
            //已经省略了部分代码
            Drawable iconLight, iconDark;

            //// M: Navigation bar plugin changes
            iconLight = mNavBarPlugin.getBackImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_back));
            iconDark = mNavBarPlugin.getBackImage(
                                ctx.getDrawable(R.drawable.ic_sysbar_back_dark));
            mBackIcon = getDrawable(iconLight,iconDark);
            //mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark);

            mBackLandIcon = mBackIcon;

            iconLight = mNavBarPlugin.getBackImeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_down));
            iconDark = mNavBarPlugin.getBackImeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_down_dark));
										
			// mHideDown = getDrawable(ctx,R.drawable.ic_sysbar_down,R.drawable.ic_sysbar_down_dark);//jsb		
			
			mHideDown = getDrawable(iconLight,iconDark);
            //mBackAltIcon = getDrawable(ctx,
            //        R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark);
			
			iconLight = mNavBarPlugin.getBackImeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_back_ime));
            iconDark = mNavBarPlugin.getBackImeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_back_ime_dark));
			
          
        }
    }

在updateIcons()方法里面设置我们之前准备的两张图片,

iconLight = mNavBarPlugin.getBackImeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_down));
iconDark = mNavBarPlugin.getBackImeImage(
                                      ctx.getDrawable(R.drawable.ic_sysbar_down_dark));	
mHideDown = getDrawable(iconLight,iconDark);

最后在setNavigationIconHints方法里面设置getHideButton().setImageDrawable(mHideDown);

public void setNavigationIconHints(int hints, boolean force) {
       //已经省略的部分代码---------------------------
        getBackButton().setImageDrawable(backIcon);

        updateRecentsIcon();

        if (mUseCarModeUi) {
            getHomeButton().setImageDrawable(mHomeCarModeIcon);
        } else {
            getHomeButton().setImageDrawable(mHomeDefaultIcon);
        }
		
	getHideButton().setImageDrawable(mHideDown);//add jsb 添加图片应用
        // The Accessibility button always overrides the appearance of the IME switcher
        final boolean showImeButton =
                !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN)
                        != 0);
    }

通过上面的步骤,我们已经可以看到底部导航栏的新添加的隐藏按钮了。接下来就是实现点击隐藏,和上滑显示的功能。

 


然后我们用Android应用的思路,既然他是一个按钮,那么肯定可以在fragment里面设置监听事件,所以我们回到NavigationBarFragment。

通过下面代码来这是底部各个按钮的监听事件,我们先看源码中如何修改:

private void prepareNavigationBarView() {
        mNavigationBarView.reorient();

        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
        recentsButton.setOnClickListener(this::onRecentsClick);
        recentsButton.setOnTouchListener(this::onRecentsTouch);
        recentsButton.setLongClickable(true);
        recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
	//添加隐藏按钮的点击监听事件	
        ButtonDispatcher hideButton = mNavigationBarView.getHideButton();
        hideButton.setOnClickListener(this::onHideClick);
        

        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
        backButton.setLongClickable(true);
        backButton.setOnLongClickListener(this::onLongPressBackRecents);

        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
        homeButton.setOnTouchListener(this::onHomeTouch);
        homeButton.setOnLongClickListener(this::onHomeLongClick);

        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
        accessibilityButton.setOnClickListener(this::onAccessibilityClick);
        accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
        updateAccessibilityServicesState(mAccessibilityManager);
    }

在fragment中prepareNavigationBarView()方法是来处理所有按钮的点击监听事件的,我们仿造他,也自己加一个。但是我们只需要单击事件,长摁的处理不做。

创建一个onHideClick点击方法

一个OnSureHideListener接口

addOnSureHideListener初始化接口的方法,共外部调用

private OnSureHideListener sureHideListener;//自定义接口监听

private void onHideClick(View v) {
		Log.d("jsb_nav","点击了jsb按钮");
		if(sureHideListener != null){
			sureHideListener.onClick();
		}
	}
	
	//写一个接口回调,监听hide按钮的点击
public interface OnSureHideListener{
        void onClick();
    }
	
public void addOnSureHideListener(OnSureHideListener listener){
        sureHideListener = listener;
    }

在hide被点击后,触发接口回调监听,那么回调监听的处理,需要回到systemui\statusbar\phone\StatusBar.java中

protected void createNavigationBar() {
        mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
            mNavigationBar = (NavigationBarFragment) fragment;
            if (mLightBarController != null) {
                mNavigationBar.setLightBarController(mLightBarController);
            }
            mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
			
            //添加回调监听
	    mNavigationBar.addOnSureHideListener(() -> {
				Log.d("jsb_nav","自定义接口回调成功");
				if (mNavigationBarView == null) return;
                mWindowManager.removeViewImmediate(mNavigationBarView);
                mNavigationBarView = null;
			});
			
        });	
    }

当监听到点击事件后,我们就隐藏mNavigationBarView;

 

上滑监听显示的话,就需要用到广播了,首先在frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java文件里面添加滑动监听触发广播

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);
                        }
						/* jsb add */
			Intent intent = new Intent();
                        intent.setAction("SHOW_NAVIGATION_BAR");
                        context.sendBroadcast(intent);
			Log.i("jsb_nav","上滑");
                    }

该监听方法已经在源码里面存着,我们只需要在onSwipeFromBottom(),里面触发时发送一个SHOW_NAVIGATION_BAR的广播

在StatusBar.java添加注册广播(在文件中搜索一下filter,就可以看到源码里面已经有注册的广播,我们添加一个action就行)

filter.addAction("SHOW_NAVIGATION_BAR");// add by jsb

最后响应广播,显示NavigationBar。(这个代码在哪里加??请在文件中搜索mBroadcastReceiver)。

else if(action.equals("SHOW_NAVIGATION_BAR")){
	        Log.d("jsb_nav","接收到了广播SHOW_NAVIGATION_BAR");
                if (mNavigationBarView != null) return;
                createNavigationBar();
            }

好了,以上就是全部的修改过程了,我这边目前算是比较完整的实现了整个功能。

写完已经是中午了。。。。划了一上午水,老板知道要拉我再去喝个茶了。拜拜~~~~

 

 

 

今夕是何夕~晚风过花庭~,哈哈

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Android开发)