乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Android 8.1 SystemUI虛擬導(dǎo)航鍵加載流程解析

       頭號(hào)碼甲 2022-01-14

      需求

      基于MTK 8.1平臺(tái)定制導(dǎo)航欄部分,在左邊增加音量減,右邊增加音量加

      思路

      需求開(kāi)始做之前,一定要研讀SystemUI Navigation模塊的代碼流程?。?!不要直接去網(wǎng)上copy別人改的需求代碼,盲改的話(huà)很容易出現(xiàn)問(wèn)題,然而無(wú)從解決。網(wǎng)上有老平臺(tái)(8.0-)的講解System UI的導(dǎo)航欄模塊的博客,自行搜索。8.0對(duì)System UI還是做了不少細(xì)節(jié)上的改動(dòng),代碼改動(dòng)體現(xiàn)上也比較多,但是總體基本流程并沒(méi)變。

      源碼閱讀可以沿著一條線(xiàn)索去跟代碼,不要過(guò)分在乎代碼細(xì)節(jié)!例如我客制化這個(gè)需求,可以跟著導(dǎo)航欄的返回(back),桌面(home),最近任務(wù)(recent)中的一個(gè)功能跟代碼流程,大體知道比如recen這個(gè)view是哪個(gè)方法調(diào)哪個(gè)方法最終加載出來(lái),加載的關(guān)鍵代碼在哪,點(diǎn)擊事件怎么生成,而不在意里面的具體邏輯判斷等等。

      代碼流程

      1.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;

      從狀態(tài)欄入口開(kāi)始看。

      protected void makeStatusBarView() {
          final Context context = mContext;
          updateDisplaySize(); // populates mDisplayMetrics
          updateResources();
          updateTheme();
      
          ...
          ...
      
           try {
              boolean showNav = mWindowManagerService.hasNavigationBar();
              if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
              if (showNav) {
                  createNavigationBar();//創(chuàng)建導(dǎo)航欄
              }
          } catch (RemoteException ex) {
      
          }
      }
      

      2.進(jìn)入 createNavigationBar 方法,發(fā)現(xiàn)主要是用 NavigationBarFragment 來(lái)管理.

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

      3.看 NavigationBarFragment 的create方法,終于知道,是WindowManager去addView了導(dǎo)航欄的布局,最終add了fragment的onCreateView加載的布局。(其實(shí)SystemUI所有的模塊都是WindowManager來(lái)加載View)

      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.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) //注意!fragment里onCreateView加載的布局是add到這個(gè)Window屬性的view里的。
                  .commit();
          fragmentHost.addTagListener(TAG, listener);
          return navigationBarView;
      	}
      }
      

      4.SystemUI\res\layout\navigation_bar_window.xml;

      來(lái)看WindowManager加載的這個(gè)view的布局:navigation_bar_window.xml,發(fā)現(xiàn)根布局是自定義的view類(lèi)NavigationBarFrame.(其實(shí)SystemUI以及其他系統(tǒng)應(yīng)用如Launcher,都是這種自定義view的方式,好多邏輯處理也都是在自定義view里,不能忽略)

      <com.android.systemui.statusbar.phone.NavigationBarFrame
          xmlns:android="http://schemas./apk/res/android"
          xmlns:systemui="http://schemas./apk/res-auto"
          android:id="@+id/navigation_bar_frame"
          android:layout_height="match_parent"
          android:layout_width="match_parent">	
      
      </com.android.systemui.statusbar.phone.NavigationBarFrame>
      

      5.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFrame.java;

      我們進(jìn)入NavigationBarFrame類(lèi)。發(fā)現(xiàn)類(lèi)里并不是我們的預(yù)期,就是一個(gè)FrameLayout,對(duì)DeadZone功能下的touch事件做了手腳,不管了。

      6.再回來(lái)看看NavigationBarFragment的生命周期呢。onCreateView()里,導(dǎo)航欄的真正的rootView。

      @Override
      public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
              Bundle savedInstanceState) {
          return inflater.inflate(R.layout.navigation_bar, container, false);
      }
      

      進(jìn)入導(dǎo)航欄的真正根布局:navigation_bar.xml,好吧又是自定義view,NavigationBarView 和 NavigationBarInflaterView 都要仔細(xì)研讀。

      <com.android.systemui.statusbar.phone.NavigationBarView
          xmlns:android="http://schemas./apk/res/android"
          xmlns:systemui="http://schemas./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>
      

      7.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java;繼承自FrameLayout

      先看構(gòu)造方法,因?yàn)榧虞dxml布局首先走的是初始化

      public NavigationBarInflaterView(Context context, AttributeSet attrs) {
          super(context, attrs);
          createInflaters();//根據(jù)屏幕旋轉(zhuǎn)角度創(chuàng)建子view(單個(gè)back home or recent)的父布局
          Display display = ((WindowManager)
                  context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
          Mode displayMode = display.getMode();
          isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
      }
      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();
      }
      

      再看onFinishInflate()方法,這是view的生命周期,每個(gè)view被inflate之后都會(huì)回調(diào)。

      @Override
      protected void onFinishInflate() {
          super.onFinishInflate();
          inflateChildren();//進(jìn)去看無(wú)關(guān)緊要 忽略
          clearViews();//進(jìn)去看無(wú)關(guān)緊要 忽略
          inflateLayout(getDefaultLayout());//關(guān)鍵方法:加載了 back.home.recent三個(gè)按鈕的layout
      }
      

      看inflateLayout():里面的newLayout參數(shù)很重要?。?!根據(jù)上一個(gè)方法看到getDefaultLayout(),他return了一個(gè)在xml寫(xiě)死的字符串。再看inflateLayout方法,他解析分割了xml里配置的字符串,并傳給了inflateButtons方法

      protected void inflateLayout(String newLayout) {
          mCurrentLayout = newLayout;
          if (newLayout == null) {
              newLayout = getDefaultLayout();
          }
          String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//根據(jù)“;”號(hào)分割成長(zhǎng)度為3的數(shù)組
          String[] start = sets[0].split(BUTTON_SEPARATOR);//根據(jù)“,”號(hào)分割,包含 left[.5W]和back[1WC]
          String[] center = sets[1].split(BUTTON_SEPARATOR);//包含home
          String[] end = sets[2].split(BUTTON_SEPARATOR);//包含recent[1WC]和right[.5W]
          // 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);
      }
      
          protected String getDefaultLayout() {
          return mContext.getString(R.string.config_navBarLayout);
      }
      

      SystemUI\res\values\config.xml

       <!-- Nav bar button default ordering/layout -->
      <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>
      

      再看inflateButtons()方法,遍歷加載inflateButton:

      private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
              boolean start) {
          for (int i = 0; i < buttons.length; i++) {
              inflateButton(buttons[i], parent, landscape, start);
          }
      }
      
      @Nullable
      protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
              boolean start) {
          LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
          View v = createView(buttonSpec, parent, inflater);//創(chuàng)建view
          if (v == null) return null;
      
          v = applySize(v, buttonSpec, landscape, start);
          parent.addView(v);//addView到父布局
          addToDispatchers(v);
          View lastView = landscape ? mLastLandscape : mLastPortrait;
          View accessibilityView = v;
          if (v instanceof ReverseFrameLayout) {
              accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
          }
          if (lastView != null) {
              accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
          }
          if (landscape) {
              mLastLandscape = accessibilityView;
          } else {
              mLastPortrait = accessibilityView;
          }
          return v;
      }
      

      我們來(lái)看createView()方法:以home按鍵為例,加載了home的button,其實(shí)是加載了 R.layout.home 的layout布局

      private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
          View v = null;
      
          ...
          ...
      
          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);
          } else if (RECENT.equals(button)) {
              v = inflater.inflate(R.layout.recent_apps, parent, false);
          } else if (MENU_IME.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);
          } 
      
          ...
          ...
      
          return v;
      }
      
      //SystemUI\res\layout\home.xml 
      //這里布局里沒(méi)有src顯示home的icon,肯定是在代碼里設(shè)置了
      //這里也是自定義view:KeyButtonView
      <com.android.systemui.statusbar.policy.KeyButtonView
      xmlns:android="http://schemas./apk/res/android"
      xmlns:systemui="http://schemas./apk/res-auto"
      android:id="@+id/home"
      android:layout_width="@dimen/navigation_key_width"http://引用了dimens.xml里的navigation_key_width
      android:layout_height="match_parent"
      android:layout_weight="0"
      systemui:keyCode="3"http://systemui自定義的屬性
      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"/>
      

      8.SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java

      先來(lái)看KeyButtonView的構(gòu)造方法:我們之前xml的systemui:keyCode=”3”方法在這里獲取。再來(lái)看Touch事件,通過(guò)sendEvent()方法可以看出,back等view的點(diǎn)擊touch事件不是自己處理的,而是交由系統(tǒng)以實(shí)體按鍵(keycode)的形式處理的.

      當(dāng)然KeyButtonView類(lèi)還處理了支持長(zhǎng)按的button,按鍵的響聲等,這里忽略。

      至此,導(dǎo)航欄按鍵事件我們梳理完畢。

      public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs);
      
          TypedArray a = 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);
      
          TypedValue value = new TypedValue();
          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 = new KeyButtonRipple(context, this);
          setBackground(mRipple);
      }
      
      ...
      ...
      
      public boolean onTouchEvent(MotionEvent ev) {
      
         ...
      
          switch (action) {
              case MotionEvent.ACTION_DOWN:
                  mDownTime = SystemClock.uptimeMillis();
                  mLongClicked = false;
                  setPressed(true);
                  if (mCode != 0) {
                      sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);//關(guān)鍵方法
                  } else {
                      // Provide the same haptic feedback that the system offers for virtual keys.
                      performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
                  }
                  playSoundEffect(SoundEffectConstants.CLICK);
                  removeCallbacks(mCheckLongPress);
                  postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
                  break;
      
              ...
              ...
      
          }
      
          return true;
      }
      
      void sendEvent(int action, int flags, long when) {
          mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)
                  .setType(MetricsEvent.TYPE_ACTION)
                  .setSubtype(mCode)
                  .addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)
                  .addTaggedData(MetricsEvent.FIELD_FLAGS, flags));
          final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
          //這里根據(jù)mCode new了一個(gè)KeyEvent事件,通過(guò)injectInputEvent使事件生效。
          final KeyEvent ev = new KeyEvent(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.還遺留一個(gè)問(wèn)題:設(shè)置圖片的icon到底在哪?我們之前一直閱讀的是NavigationBarInflaterView,根據(jù)布局我們還有一個(gè)類(lèi)沒(méi)有看,NavigationBarView.java

      SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java;

      進(jìn)入NavigationBarView類(lèi)里,找到構(gòu)造方法。

      public NavigationBarView(Context context, AttributeSet attrs) {
          super(context, attrs);
      
          mDisplay = ((WindowManager) context.getSystemService(
                  Context.WINDOW_SERVICE)).getDefaultDisplay();
      
      
          ...
          ...
      
          updateIcons(context, Configuration.EMPTY, mConfiguration);//關(guān)鍵方法
      
          mBarTransitions = new NavigationBarTransitions(this);
      
          //mButtonDispatchers 是維護(hù)這些home back recent圖標(biāo)view的管理類(lèi),會(huì)傳遞到他的child,NavigationBarInflaterView類(lèi)中
          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));
      
      }
      
       private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
      
             ...
      
              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為例,在初始化時(shí)得到了mRecentIcon的資源,再看誰(shuí)調(diào)用了了mRecentIcon就可知道,即反推看調(diào)用流程。

      private void updateRecentsIcon() {
          getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
          mBarTransitions.reapplyDarkIntensity();
      }
      

      updateRecentsIcon這個(gè)方法設(shè)置了recent圖片的資源,再看誰(shuí)調(diào)用了updateRecentsIcon方法:onConfigurationChanged屏幕旋轉(zhuǎn)會(huì)重新設(shè)置資源圖片

      @Override
      protected void onConfigurationChanged(Configuration newConfig) {
          super.onConfigurationChanged(newConfig);
          boolean uiCarModeChanged = updateCarMode(newConfig);
          updateTaskSwitchHelper();
          updateIcons(getContext(), mConfiguration, newConfig);
          updateRecentsIcon();
          if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
                  || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
              // If car mode or density changes, we need to reset the icons.
              setNavigationIconHints(mNavigationIconHints, true);
          }
          mConfiguration.updateFrom(newConfig);
      }
      
      public void setNavigationIconHints(int hints, boolean force) {
      
          ...
          ...
      
          mNavigationIconHints = hints;
      
          // We have to replace or restore the back and home button icons when exiting or entering
          // carmode, respectively. Recents are not available in CarMode in nav bar so change
          // to recent icon is not required.
          KeyButtonDrawable backIcon = (backAlt)
                  ? getBackIconWithAlt(mUseCarModeUi, mVertical)
                  : getBackIcon(mUseCarModeUi, mVertical);
      
          getBackButton().setImageDrawable(backIcon);
      
          updateRecentsIcon();
      
          ...
          ...
      
      }
      

      reorient()也調(diào)用了setNavigationIconHints()方法:

      public void reorient() {
          updateCurrentView();
      
          ...
      
          setNavigationIconHints(mNavigationIconHints, true);
      
          getHomeButton().setVertical(mVertical);
      }
      

      再朝上推,最終追溯到NavigationBarFragment的onConfigurationChanged()方法 和 NavigationBarView的onAttachedToWindow()和onSizeChanged()方法。也就是說(shuō),在NavigationBarView導(dǎo)航欄這個(gè)布局加載的時(shí)候就會(huì)設(shè)置圖片資源,和長(zhǎng)度改變,屏幕旋轉(zhuǎn)都有可能引起重新設(shè)置

      至此,SystemUI的虛擬導(dǎo)航欄模塊代碼流程結(jié)束。

      總結(jié)

      1. 創(chuàng)建一個(gè)window屬性的父view
      2. 通過(guò)讀取解析xml里config的配置,addView需要的icon,或者調(diào)換順序
      3. src圖片資源通過(guò)代碼設(shè)置亮色和暗色
      4. touch事件以keycode方式交由系統(tǒng)處理

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶(hù) 評(píng)論公約

        類(lèi)似文章 更多