解析Android 8.1平台SystemUI 导航栏加载流程
需求
基于MTK81638.1平台定制导航栏部分,在左边增加音量减,右边增加音量加
思路
需求开始做之前,一定要研读SystemUINavigation模块的代码流程!!!不要直接去网上copy别人改的需求代码,盲改的话很容易出现问题,然而无从解决。网上有老平台(8.0-)的讲解SystemUI的导航栏模块的博客,自行搜索。8.0对SystemUI还是做了不少细节上的改动,代码改动体现上也比较多,但是总体基本流程并没变。
源码阅读可以沿着一条线索去跟代码,不要过分在乎代码细节!例如我客制化这个需求,可以跟着导航栏的返回(back),桌面(home),最近任务(recent)中的一个功能跟代码流程,大体知道比如recen这个view是哪个方法调哪个方法最终加载出来,加载的关键代码在哪,点击事件怎么生成,而不在意里面的具体逻辑判断等等。
代码流程
1.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;
从状态栏入口开始看。
protectedvoidmakeStatusBarView(){ finalContextcontext=mContext; updateDisplaySize();//populatesmDisplayMetrics updateResources(); updateTheme(); ... ... try{ booleanshowNav=mWindowManagerService.hasNavigationBar(); if(DEBUG)Log.v(TAG,"hasNavigationBar="+showNav); if(showNav){ createNavigationBar();//创建导航栏 } }catch(RemoteExceptionex){ } }
2.进入createNavigationBar方法,发现主要是用NavigationBarFragment来管理.
protectedvoidcreateNavigationBar(){ mNavigationBarView=NavigationBarFragment.create(mContext,(tag,fragment)->{ mNavigationBar=(NavigationBarFragment)fragment; if(mLightBarController!=null){ mNavigationBar.setLightBarController(mLightBarController); } mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility); }); }
3.看NavigationBarFragment的create方法,终于知道,是WindowManager去addView了导航栏的布局,最终add了fragment的onCreateView加载的布局。(其实SystemUI所有的模块都是WindowManager来加载View)
publicstaticViewcreate(Contextcontext,FragmentListenerlistener){ WindowManager.LayoutParamslp=newWindowManager.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=newBinder(); lp.setTitle("NavigationBar"); lp.windowAnimations=0; ViewnavigationBarView=LayoutInflater.from(context).inflate( R.layout.navigation_bar_window,null); if(DEBUG)Log.v(TAG,"addNavigationBar:abouttoadd"+navigationBarView); if(navigationBarView==null)returnnull; context.getSystemService(WindowManager.class).addView(navigationBarView,lp); FragmentHostManagerfragmentHost=FragmentHostManager.get(navigationBarView); NavigationBarFragmentfragment=newNavigationBarFragment(); fragmentHost.getFragmentManager().beginTransaction() .replace(R.id.navigation_bar_frame,fragment,TAG)//注意!fragment里onCreateView加载的布局是add到这个Window属性的view里的。 .commit(); fragmentHost.addTagListener(TAG,listener); returnnavigationBarView; } }
4.SystemUI\res\layout\navigation_bar_window.xml;
来看WindowManager加载的这个view的布局:navigation_bar_window.xml,发现根布局是自定义的view类NavigationBarFrame.(其实SystemUI以及其他系统应用如Launcher,都是这种自定义view的方式,好多逻辑处理也都是在自定义view里,不能忽略)
5.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFrame.java;
我们进入NavigationBarFrame类。发现类里并不是我们的预期,就是一个FrameLayout,对DeadZone功能下的touch事件做了手脚,不管了。
6.再回来看看NavigationBarFragment的生命周期呢。onCreateView()里,导航栏的真正的rootView。
@Override publicViewonCreateView(LayoutInflaterinflater,@NullableViewGroupcontainer, BundlesavedInstanceState){ returninflater.inflate(R.layout.navigation_bar,container,false); }
进入导航栏的真正根布局:navigation_bar.xml,好吧又是自定义view,NavigationBarView和NavigationBarInflaterView都要仔细研读。
7.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java;继承自FrameLayout
先看构造方法,因为加载xml布局首先走的是初始化
publicNavigationBarInflaterView(Contextcontext,AttributeSetattrs){ super(context,attrs); createInflaters();//根据屏幕旋转角度创建子view(单个backhomeorrecent)的父布局 Displaydisplay=((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); ModedisplayMode=display.getMode(); isRot0Landscape=displayMode.getPhysicalWidth()>displayMode.getPhysicalHeight(); } privatevoidinflateChildren(){ 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(); }
再看onFinishInflate()方法,这是view的生命周期,每个view被inflate之后都会回调。
@Override protectedvoidonFinishInflate(){ super.onFinishInflate(); inflateChildren();//进去看无关紧要忽略 clearViews();//进去看无关紧要忽略 inflateLayout(getDefaultLayout());//关键方法:加载了back.home.recent三个按钮的layout }
看inflateLayout():里面的newLayout参数很重要!!!根据上一个方法看到getDefaultLayout(),他return了一个在xml写死的字符串。再看inflateLayout方法,他解析分割了xml里配置的字符串,并传给了inflateButtons方法
protectedvoidinflateLayout(StringnewLayout){ mCurrentLayout=newLayout; if(newLayout==null){ newLayout=getDefaultLayout(); } String[]sets=newLayout.split(GRAVITY_SEPARATOR,3);//根据“;”号分割成长度为3的数组 String[]start=sets[0].split(BUTTON_SEPARATOR);//根据“,”号分割,包含left[.5W]和back[1WC] String[]center=sets[1].split(BUTTON_SEPARATOR);//包含home String[]end=sets[2].split(BUTTON_SEPARATOR);//包含recent[1WC]和right[.5W] //Inflatetheseinstarttoendorderoraccessibilitytraversalwillbemessedup. 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); } protectedStringgetDefaultLayout(){ returnmContext.getString(R.string.config_navBarLayout); }
SystemUI\res\values\config.xml
left[.5W],back[1WC];home;recent[1WC],right[.5W]
再看inflateButtons()方法,遍历加载inflateButton:
privatevoidinflateButtons(String[]buttons,ViewGroupparent,booleanlandscape, booleanstart){ for(inti=0;i我们来看createView()方法:以home按键为例,加载了home的button,其实是加载了R.layout.home的layout布局
privateViewcreateView(StringbuttonSpec,ViewGroupparent,LayoutInflaterinflater){ Viewv=null; ... ... if(HOME.equals(button)){ v=inflater.inflate(R.layout.home,parent,false); }elseif(BACK.equals(button)){ v=inflater.inflate(R.layout.back,parent,false); }elseif(RECENT.equals(button)){ v=inflater.inflate(R.layout.recent_apps,parent,false); }elseif(MENU_IME.equals(button)){ v=inflater.inflate(R.layout.menu_ime,parent,false); }elseif(NAVSPACE.equals(button)){ v=inflater.inflate(R.layout.nav_key_space,parent,false); }elseif(CLIPBOARD.equals(button)){ v=inflater.inflate(R.layout.clipboard,parent,false); } ... ... returnv; } //SystemUI\res\layout\home.xml //这里布局里没有src显示home的icon,肯定是在代码里设置了 //这里也是自定义view:KeyButtonView8.SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java
先来看KeyButtonView的构造方法:我们之前xml的systemui:keyCode=”3”方法在这里获取。再来看Touch事件,通过sendEvent()方法可以看出,back等view的点击touch事件不是自己处理的,而是交由系统以实体按键(keycode)的形式处理的.
当然KeyButtonView类还处理了支持长按的button,按键的响声等,这里忽略。
至此,导航栏按键事件我们梳理完毕。
publicKeyButtonView(Contextcontext,AttributeSetattrs,intdefStyle){ super(context,attrs); TypedArraya=context.obtainStyledAttributes(attrs,R.styleable.KeyButtonView, defStyle,0); mCode=a.getInteger(R.styleable.KeyButtonView_keyCode,0); mSupportsLongpress=a.getBoolean(R.styleable.KeyButtonView_keyRepeat,true); mPlaySounds=a.getBoolean(R.styleable.KeyButtonView_playSound,true); TypedValuevalue=newTypedValue(); if(a.getValue(R.styleable.KeyButtonView_android_contentDescription,value)){ mContentDescriptionRes=value.resourceId; } a.recycle(); setClickable(true); mTouchSlop=ViewConfiguration.get(context).getScaledTouchSlop(); mAudioManager=(AudioManager)context.getSystemService(Context.AUDIO_SERVICE); mRipple=newKeyButtonRipple(context,this); setBackground(mRipple); } ... ... publicbooleanonTouchEvent(MotionEventev){ ... switch(action){ caseMotionEvent.ACTION_DOWN: mDownTime=SystemClock.uptimeMillis(); mLongClicked=false; setPressed(true); if(mCode!=0){ sendEvent(KeyEvent.ACTION_DOWN,0,mDownTime);//关键方法 }else{ //Providethesamehapticfeedbackthatthesystemoffersforvirtualkeys. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); } playSoundEffect(SoundEffectConstants.CLICK); removeCallbacks(mCheckLongPress); postDelayed(mCheckLongPress,ViewConfiguration.getLongPressTimeout()); break; ... ... } returntrue; } voidsendEvent(intaction,intflags,longwhen){ mMetricsLogger.write(newLogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT) .setType(MetricsEvent.TYPE_ACTION) .setSubtype(mCode) .addTaggedData(MetricsEvent.FIELD_NAV_ACTION,action) .addTaggedData(MetricsEvent.FIELD_FLAGS,flags)); finalintrepeatCount=(flags&KeyEvent.FLAG_LONG_PRESS)!=0?1:0; //这里根据mCodenew了一个KeyEvent事件,通过injectInputEvent使事件生效。 finalKeyEventev=newKeyEvent(mDownTime,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); }9.还遗留一个问题:设置图片的icon到底在哪?我们之前一直阅读的是NavigationBarInflaterView,根据布局我们还有一个类没有看,NavigationBarView.java
SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java;
进入NavigationBarView类里,找到构造方法。
publicNavigationBarView(Contextcontext,AttributeSetattrs){ super(context,attrs); mDisplay=((WindowManager)context.getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay(); ... ... updateIcons(context,Configuration.EMPTY,mConfiguration);//关键方法 mBarTransitions=newNavigationBarTransitions(this); //mButtonDispatchers是维护这些homebackrecent图标view的管理类,会传递到他的child,NavigationBarInflaterView类中 mButtonDispatchers.put(R.id.back,newButtonDispatcher(R.id.back)); mButtonDispatchers.put(R.id.home,newButtonDispatcher(R.id.home)); mButtonDispatchers.put(R.id.recent_apps,newButtonDispatcher(R.id.recent_apps)); mButtonDispatchers.put(R.id.menu,newButtonDispatcher(R.id.menu)); mButtonDispatchers.put(R.id.ime_switcher,newButtonDispatcher(R.id.ime_switcher)); mButtonDispatchers.put(R.id.accessibility_button,newButtonDispatcher(R.id.accessibility_button)); } privatevoidupdateIcons(Contextctx,ConfigurationoldConfig,ConfigurationnewConfig){ ... iconLight=mNavBarPlugin.getHomeImage( ctx.getDrawable(R.drawable.ic_sysbar_home)); iconDark=mNavBarPlugin.getHomeImage( ctx.getDrawable(R.drawable.ic_sysbar_home_dark)); //mHomeDefaultIcon=getDrawable(ctx, //R.drawable.ic_sysbar_home,R.drawable.ic_sysbar_home_dark); mHomeDefaultIcon=getDrawable(iconLight,iconDark); //亮色的icon资源 iconLight=mNavBarPlugin.getRecentImage( ctx.getDrawable(R.drawable.ic_sysbar_recent)); //暗色的icon资源 iconDark=mNavBarPlugin.getRecentImage( ctx.getDrawable(R.drawable.ic_sysbar_recent_dark)); //mRecentIcon=getDrawable(ctx, //R.drawable.ic_sysbar_recent,R.drawable.ic_sysbar_recent_dark); mRecentIcon=getDrawable(iconLight,iconDark); mMenuIcon=getDrawable(ctx,R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark); ... ... }10.从第10可以看到,以recent为例,在初始化时得到了mRecentIcon的资源,再看谁调用了了mRecentIcon就可知道,即反推看调用流程。
privatevoidupdateRecentsIcon(){ getRecentsButton().setImageDrawable(mDockedStackExists?mDockedIcon:mRecentIcon); mBarTransitions.reapplyDarkIntensity(); }updateRecentsIcon这个方法设置了recent图片的资源,再看谁调用了updateRecentsIcon方法:onConfigurationChanged屏幕旋转会重新设置资源图片
@Override protectedvoidonConfigurationChanged(ConfigurationnewConfig){ super.onConfigurationChanged(newConfig); booleanuiCarModeChanged=updateCarMode(newConfig); updateTaskSwitchHelper(); updateIcons(getContext(),mConfiguration,newConfig); updateRecentsIcon(); if(uiCarModeChanged||mConfiguration.densityDpi!=newConfig.densityDpi ||mConfiguration.getLayoutDirection()!=newConfig.getLayoutDirection()){ //Ifcarmodeordensitychanges,weneedtoresettheicons. setNavigationIconHints(mNavigationIconHints,true); } mConfiguration.updateFrom(newConfig); } publicvoidsetNavigationIconHints(inthints,booleanforce){ ... ... mNavigationIconHints=hints; //Wehavetoreplaceorrestorethebackandhomebuttoniconswhenexitingorentering //carmode,respectively.RecentsarenotavailableinCarModeinnavbarsochange //torecenticonisnotrequired. KeyButtonDrawablebackIcon=(backAlt) ?getBackIconWithAlt(mUseCarModeUi,mVertical) :getBackIcon(mUseCarModeUi,mVertical); getBackButton().setImageDrawable(backIcon); updateRecentsIcon(); ... ... }reorient()也调用了setNavigationIconHints()方法:
publicvoidreorient(){ updateCurrentView(); ... setNavigationIconHints(mNavigationIconHints,true); getHomeButton().setVertical(mVertical); }再朝上推,最终追溯到NavigationBarFragment的onConfigurationChanged()方法和NavigationBarView的onAttachedToWindow()和onSizeChanged()方法。也就是说,在NavigationBarView导航栏这个布局加载的时候就会设置图片资源,和长度改变,屏幕旋转都有可能引起重新设置
至此,SystemUI的虚拟导航栏模块代码流程结束。
总结
- 创建一个window属性的父view
- 通过读取解析xml里config的配置,addView需要的icon,或者调换顺序
- src图片资源通过代码设置亮色和暗色
- touch事件以keycode方式交由系统处理
以上所述是小编给大家介绍的Android8.1平台SystemUI导航栏加载流程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。