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

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

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

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

效果:

从图中我们可以看出的效果是在底部导航栏添加一个按钮,他的功能是点击隐藏底部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

<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.statusbar.policy.KeyButtonView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/hide"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    android:scaleType="fitCenter"
    android:contentDescription="@string/accessibility_key"
	android:paddingTop="@dimen/home_padding"
    android:paddingBottom="@dimen/home_padding"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"/>

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

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


<!-- 这个是系统的home布局 -->
<com.android.systemui.statusbar.policy.KeyButtonView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="3"
    android:scaleType="fitCenter"
    android:contentDescription="@string/accessibility_home"
    android:paddingTop="@dimen/home_padding"
    android:paddingBottom="@dimen/home_padding"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

完成这一步我们就要看看这个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里面的设置来:

   <string name="config_navBarLayout" translatable="false">left[.15W],hide[.5WC];back[.5WC];home[.5WC];recent[.5WC],right[.15W]</string>

加了一个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();
            }

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

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

 

 

 

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SystemUIAndroid系统的一部分,它提供了状态栏、导航栏、锁屏等与用户界面相关的服务。如果你想开发SystemUI,需要先了解Android系统开发的基础知识和相关API。以下是一些基本步骤: 1. 下载并安装Android Studio和Android SDK。 2. 创建一个新的Android项目,并选择“Add No Activity”选项。 3. 在项目的build.gradle文件添加依赖项: ``` dependencies { implementation 'com.android.systemui:systemui:XXX' } ``` 其,XXX是你希望使用的SystemUI版本。 4. 在AndroidManifest.xml文件添加以下内容: ``` <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.systemui"> <application android:name=".SystemUIApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher"> <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts" /> <receiver android:name=".SystemUIBroadcastReceiver" /> <service android:name=".SystemUIService" /> </application> </manifest> ``` 其SystemUIApplication、SystemUIBroadcastReceiver和SystemUIService是你需要根据自己的需求创建的类。 5. 在Android Studio创建一个新的Module,选择“Android Library”,并将它的名称设置为“SystemUI”。 6. 在SystemUI Module的build.gradle文件添加以下依赖项: ``` dependencies { implementation 'com.android.systemui:systemui:XXX' implementation 'com.android.support:support-v4:XXX' implementation 'com.android.support:design:XXX' implementation 'com.android.support:cardview-v7:XXX' implementation 'com.android.support:recyclerview-v7:XXX' } ``` 其,XXX是你希望使用的SystemUI版本和Android Support库版本。 7. 在SystemUI Module创建你需要的类,例如StatusBar、NavigationBar等。 8. 在AndroidManifest.xml文件声明你的SystemUI类,例如: ``` <service android:name=".StatusBar" /> <service android:name=".NavigationBar" /> ``` 9. 在Android系统启动你的SystemUI,例如: ``` adb shell am startservice com.android.systemui/.StatusBar adb shell am startservice com.android.systemui/.NavigationBar ``` 以上是开发SystemUI的基本步骤,但是要想真正实现一个完整的SystemUI,需要深入了解Android系统的架构和API。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值