Android SystemUI之NavigationBar,导航栏(四)

Android  SystemUI系列:

     1.Android  SystemUI之启动流程(一)

     2.Android SystemUI之StatusBar,状态栏(二)

     3.Android SystemUI之下拉菜单,通知栏,快捷面板(三)

     4.Android SystemUI之NavigationBar,导航栏(四)

     5.Android SystemUI之Recent,近期列表(五)

 

一、导航栏的创建

     1.先上一幅导航栏的View结构图。如下。

    2.导航栏的创建

      StatusBar.makeStatusBarView


        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
        }
 protected void createNavigationBar() {
        mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
            mNavigationBar = (NavigationBarFragment) fragment;
            if (mLightBarController != null) {
                mNavigationBar.setLightBarController(mLightBarController);
            }
            mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
        });
    }

 NavigationBarFragment.create

public static View create(Context context, FragmentListener listener) {
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        lp.token = new Binder();
        lp.setTitle("NavigationBar");
        lp.accessibilityTitle = context.getString(R.string.nav_bar);
        lp.windowAnimations = 0;

        View navigationBarView = LayoutInflater.from(context).inflate(
                R.layout.navigation_bar_window, null);

        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
        if (navigationBarView == null) return null;

        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
        FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
        NavigationBarFragment fragment = new NavigationBarFragment();
        fragmentHost.getFragmentManager().beginTransaction()
                .replace(R.id.navigation_bar_frame, fragment, TAG)
                .commit();
        fragmentHost.addTagListener(TAG, listener);
        return navigationBarView;
    }

上述代码做了两件事:1.创建navigationBarView 并且把navigationBarView添加到windowManager中。

                                 2.创建NavigationBarFragment 替换navigation_bar_window的布局文件,改成navigation_bar

navigation_bar.xml

<com.android.systemui.statusbar.phone.NavigationBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:background="@drawable/system_bar_background">

    <com.android.systemui.statusbar.phone.NavigationBarInflaterView
        android:id="@+id/navigation_inflater"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.android.systemui.statusbar.phone.NavigationBarView>

我们先看NavigationBarInflaterView

 public NavigationBarInflaterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        createInflaters();
        mDisplay = ((WindowManager)
                context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        Mode displayMode = mDisplay.getMode();
        isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
	
        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
    }

    private void createInflaters() {
        mLayoutInflater = LayoutInflater.from(mContext);
        Configuration landscape = new Configuration();
        landscape.setTo(mContext.getResources().getConfiguration());
        landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
        mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();//添加mRot0和mRot90,就是横竖屏的布局
        clearViews();//清空所有的view
        
        inflateLayout(getDefaultLayout());
    }

    private void inflateChildren() {
        removeAllViews();
        mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);
        mRot0.setId(R.id.rot0);
        addView(mRot0);
        mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this,
                false);
        mRot90.setId(R.id.rot90);
        addView(mRot90);
        updateAlternativeOrder();
    }

上述代码就是在加载导航栏布局文件,如果是0度的时候加载navigation_layout,如果是90度的时候加载navigation_layout_rot90,横屏竖屏加载的layout不同。

 protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);
        if (sets.length != 3) {
            Log.d(TAG, "Invalid layout.");
            newLayout = getDefaultLayout();
            sets = newLayout.split(GRAVITY_SEPARATOR, 3);
        }
        String[] start = sets[0].split(BUTTON_SEPARATOR);
        String[] center = sets[1].split(BUTTON_SEPARATOR);
        String[] end = sets[2].split(BUTTON_SEPARATOR);
        // Inflate these in start to end order or accessibility traversal will be messed up.
        inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true);
        inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);

        inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
        inflateButtons(center, mRot90.findViewById(R.id.center_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);

        updateButtonDispatchersCurrentView();
    }
 protected String getDefaultLayout() {
        final int defaultResource = mOverviewProxyService.shouldShowSwipeUpUI()
                ? R.string.config_navBarLayoutQuickstep
                : R.string.config_navBarLayout;
        return mContext.getString(defaultResource);
    }
 protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
            boolean start) {
 
        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
        View v = createView(buttonSpec, parent, inflater);
        if (v == null) return null;

        v = applySize(v, buttonSpec, landscape, start);//计算view的宽的大小
        parent.addView(v);
        addToDispatchers(v);
        View lastView = landscape ? mLastLandscape : mLastPortrait;
        View accessibilityView = v;
        if (v instanceof ReverseRelativeLayout) {
            accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
        }
        if (lastView != null) {
            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
        }
        if (landscape) {
            mLastLandscape = accessibilityView;
        } else {
            mLastPortrait = accessibilityView;
        }
        return v;
    }
 private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
        View v = null;
        String button = extractButton(buttonSpec);
        if (LEFT.equals(button)) {
            String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
            button = extractButton(s);
        } else if (RIGHT.equals(button)) {
            String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME_ROTATE);
            button = extractButton(s);
        }
        // Let plugins go first so they can override a standard view if they want.
        for (NavBarButtonProvider provider : mPlugins) {
            v = provider.createView(buttonSpec, parent);
            if (v != null) return v;
        }
        if (HOME.equals(button)) {
            v = inflater.inflate(R.layout.home, parent, false);
        } else if (BACK.equals(button)) {
            v = inflater.inflate(R.layout.back, parent, false);
        } 
            //laiyw add for volume Up/Down
        else if (VOLUME_DOWN.equals(button)) {
            v = inflater.inflate(R.layout.volume_down, parent, false);
        }
        else if (VOLUME_UP.equals(button)) {
            v = inflater.inflate(R.layout.volume_up, parent, false);
        
            //laiyw add for volume Up/Down
        } else if (RECENT.equals(button)) {
            v = inflater.inflate(R.layout.recent_apps, parent, false);
        } else if (MENU_IME_ROTATE.equals(button)) {
            v = inflater.inflate(R.layout.menu_ime, parent, false);
        } else if (NAVSPACE.equals(button)) {
            v = inflater.inflate(R.layout.nav_key_space, parent, false);
        } else if (CLIPBOARD.equals(button)) {
            v = inflater.inflate(R.layout.clipboard, parent, false);
        } else if (CONTEXTUAL.equals(button)) {
            v = inflater.inflate(R.layout.contextual, parent, false);
        } else if (button.startsWith(KEY)) {
            String uri = extractImage(button);
            int code = extractKeycode(button);
            v = inflater.inflate(R.layout.custom_key, parent, false);
            ((KeyButtonView) v).setCode(code);
            if (uri != null) {
                if (uri.contains(":")) {
                    ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
                } else if (uri.contains("/")) {
                    int index = uri.indexOf('/');
                    String pkg = uri.substring(0, index);
                    int id = Integer.parseInt(uri.substring(index + 1));
                    ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
                }
            }
        }
        return v;
    }

上述的代码都比较简单,有两点:

第一、导航栏显示哪些控件是由getDefaultLayout来决定。

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

一般情况下我们是home,recent,back这三个键,如果你需要加其他的就在这个配置文件夹。同时在createView添加对应的布局文件。

第二、createView方法创建对应的布局文件,并且添加到导航栏中。

home.xml

<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="center"
    android:contentDescription="@string/accessibility_home"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

back.xml

<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/back"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="4"
    android:scaleType="center"
    android:contentDescription="@string/accessibility_back"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

recent_app.xml

<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/recent_apps"
    android:layout_width="@dimen/navigation_key_width"
    android:layout_height="match_parent"
    android:layout_weight="0"
    android:scaleType="center"
    android:contentDescription="@string/accessibility_recent"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />

那么我们现在布局文件都添加完成了,但是你会发现在NavigationBarInflaterView没有对资源文件添加的代码已经控件点击触摸事件处理逻辑。那么这两部分代码在哪里呢?

答案是:

1.NavigationBarView 完成资源文件添加。

2.NavigationBarFragment  添加点击事件和触摸事件的处理逻辑。

3.NavigationBarView

    public NavigationBarView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mDisplay = ((WindowManager) context.getSystemService(
                Context.WINDOW_SERVICE)).getDefaultDisplay();

        mVertical = false;
        mShowMenu = false;

        mShowAccessibilityButton = false;
        mLongClickableAccessibilityButton = false;

        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
        mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);

        mConfiguration = new Configuration();
        mConfiguration.updateFrom(context.getResources().getConfiguration());

        M : New plugin architecture - Navigation bar plugin
        try {
            mSystemUICustomizationFactory =
                    OpSystemUICustomizationFactoryBase.getOpFactory(context);
            mNavBarPlugin = mSystemUICustomizationFactory.makeNavigationBar(context);
        } catch (Exception e) {
            if (DEBUG) Log.d(TAG,"mNavBarPlugin init fail");
            e.printStackTrace();
        }
        reloadNavIcons();//初始化加载资源,主要是图片。

        mBarTransitions = new NavigationBarTransitions(this);

        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.volume_down, new ButtonDispatcher(R.id.volume_down));	
		mButtonDispatchers.put(R.id.volume_up, new ButtonDispatcher(R.id.volume_up));
		getVolumeDownButton().setLongClickable(false);
		getVolumeUpButton().setLongClickable(false);
		   
        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.rotate_suggestion,
                new ButtonDispatcher(R.id.rotate_suggestion));
        mButtonDispatchers.put(R.id.menu_container,
                new ButtonDispatcher(R.id.menu_container));
        mDeadZone = new DeadZone(this);
    }
private void reloadNavIcons() {
        updateIcons(mContext, Configuration.EMPTY, mConfiguration);
    }
 private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
        int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme);
        int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme);
        Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme);
        Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme);

        if (oldConfig.orientation != newConfig.orientation
                || oldConfig.densityDpi != newConfig.densityDpi) {
            mDockedIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_docked);
            mHomeDefaultIcon = getHomeDrawable(lightContext, darkContext);
        }
        if (oldConfig.densityDpi != newConfig.densityDpi
                || oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) {
					
			Log.d("yangxiucheng", "oldConfig.densityDpi != newConfig.densityDpi");
            mBackIcon = getBackDrawable(lightContext, darkContext);
            mRecentIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_recent);
            mMenuIcon = getDrawable(lightContext, darkContext, R.drawable.ic_sysbar_menu);
																			
            mAccessibilityIcon = getDrawable(lightContext, darkContext,
                    R.drawable.ic_sysbar_accessibility_button, false /* hasShadow */);

            //mImeIcon = getDrawable(lightContext, darkContext, R.drawable.ic_ime_switcher_default,
                    //false /* hasShadow */);

            updateRotateSuggestionButtonStyle(mRotateBtnStyle, false);

            if (ALTERNATE_CAR_MODE_UI) {
                updateCarModeIcons(ctx);
            }
        }

		mVolumeDown = getDrawable(ctx,R.drawable.ic_sysbar_volume_down,R.drawable.ic_sysbar_volume_down_dark);
		mVolumeUp = getDrawable(ctx,R.drawable.ic_sysbar_volume_up,R.drawable.ic_sysbar_volume_up_dark);
		
    }

上述代码都比较简单,可以结合源码看一些细节,就不做太多说明

4.NavigationBarFragment

  public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mNavigationBarView = (NavigationBarView) view;

        mNavigationBarView.setDisabledFlags(mDisabledFlags1);
        mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel());
        mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
        mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
        if (savedInstanceState != null) {
            mNavigationBarView.getLightTransitionsController().restoreState(savedInstanceState);
        }

        prepareNavigationBarView();//添加home  ,recent触摸事件回调
        checkNavBarModes();

        setDisabled2Flags(mDisabledFlags2);

        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_USER_SWITCHED);
        getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
        notifyNavigationBarScreenOn();
        mOverviewProxyService.addCallback(mOverviewProxyListener);
    }
 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 backButton = mNavigationBarView.getBackButton();
        backButton.setLongClickable(true);

        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);

        ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
        rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
        rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover);
        updateScreenPinningGestures();
    }

上述代码中的setOnClickListener,setOnTouchListener,setLongClickable,setOnLongClickListener就是给对应的控件添加控制代码

到现在为止整个导航栏的View的添加,资源图片的加载,以及点击触摸事件的响应逻辑都讲了,其中还有一些细节,结合源码看应该不难,导航栏的讲解也就到此结束。

  • 9
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值