<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android導(dǎo)航欄適配的實現(xiàn)

          共 10834字,需瀏覽 22分鐘

           ·

          2022-11-07 23:23

          8e162d87ee08acf167c82bdcbf1f7513.webp

          和你一起終身學(xué) 習(xí),這里是程序員Android

          經(jīng)典好文推薦,通過閱讀本文,您將收獲以下知識點(diǎn):

          一、前言
          二、導(dǎo)航欄的處理
          三、 修改StatusHostLayout方案
          四、總結(jié)

          一、前言

          在之前的文章中,大家比較關(guān)注宿主侵入的方式,并且有要求適配導(dǎo)航欄的操作。

          其實大部分的應(yīng)用都只需要使用到狀態(tài)欄,導(dǎo)航欄由系統(tǒng)去管理,為什么不自己管理導(dǎo)航欄,就是導(dǎo)航欄的坑太多。

          背景設(shè)置的坑,判斷是否存在的坑,手動設(shè)置隱藏顯示導(dǎo)航欄的坑,導(dǎo)航欄高度獲取的坑。

          如果項目中確實需要用到操作導(dǎo)航欄怎么辦?

          二、導(dǎo)航欄的處理

          導(dǎo)航欄為什么難處理,因為之前的一些添加Flag的方案有些不實用,有兼容問題,也可以說手機(jī)廠商并沒有完全適配,導(dǎo)致兼容性有問題。

          而我們通過 WindowInsetsController / WindowInsets 的一些方式則可以相對方便的操作導(dǎo)航欄。

          那么是不是 WindowInsetsController / WindowInsets 的方式就完全兼容了呢?也并不是,只是相對好一點(diǎn),重要的功能能用而已。

          下面介紹一下相對穩(wěn)定的一些操作方法。

          2.1 判斷當(dāng)前是否顯示了導(dǎo)航欄
                  /**
          * 當(dāng)前是否顯示了底部導(dǎo)航欄
          */

          public static void hasNavigationBars(Activity activity, BooleanValueCallback callback) {

          View decorView = activity.findViewById(android.R.id.content);
          boolean attachedToWindow = decorView.isAttachedToWindow();

          if (attachedToWindow) {

          WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(decorView);

          if (windowInsets != null) {

          boolean hasNavigationBar = windowInsets.isVisible(WindowInsetsCompat.Type.navigationBars()) &&
          windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0;

          callback.onBoolean(hasNavigationBar);
          }

          } else {

          decorView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
          @Override
          public void onViewAttachedToWindow(View v) {

          WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(v);

          if (windowInsets != null) {

          boolean hasNavigationBar = windowInsets.isVisible(WindowInsetsCompat.Type.navigationBars()) &&
          windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0;

          callback.onBoolean(hasNavigationBar);
          }
          }

          @Override
          public void onViewDetachedFromWindow(View v) {
          }
          });
          }
          }
          復(fù)制代碼

          其實核心代碼是一樣的,只是區(qū)分了是否已經(jīng)onAttach了,防止在onCreate方法中調(diào)用的時候會報錯。

          它的核心思路是和老版本的方法是相似的,只是老版本是從window中找到導(dǎo)航欄布局去判斷是否隱藏和顯示和判斷高度。而新版本通過WindowInset 的方式獲取導(dǎo)航欄對象相對比較穩(wěn)妥。

          2.2 獲取導(dǎo)航欄的高度
                  /**
          * 獲取底部導(dǎo)航欄的高度
          */

          public static void getNavigationBarHeight(View view, HeightValueCallback callback) {

          boolean attachedToWindow = view.isAttachedToWindow();

          if (attachedToWindow) {

          WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(view);
          assert windowInsets != null;
          int top = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).top;
          int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
          int height = Math.abs(bottom - top);
          if (height > 0) {
          callback.onHeight(height);
          } else {
          callback.onHeight(getNavigationBarHeight(view.getContext()));
          }

          } else {

          view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
          @Override
          public void onViewAttachedToWindow(View v) {

          WindowInsetsCompat windowInsets = ViewCompat.getRootWindowInsets(v);
          assert windowInsets != null;
          int top = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).top;
          int bottom = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom;
          int height = Math.abs(bottom - top);
          if (height > 0) {
          callback.onHeight(height);
          } else {
          callback.onHeight(getNavigationBarHeight(view.getContext()));
          }
          }

          @Override
          public void onViewDetachedFromWindow(View v) {
          }
          });
          }
          }

          /**
          * 老的方法獲取導(dǎo)航欄的高度
          */

          private static int getNavigationBarHeight(Context context) {
          int result = 0;
          int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
          if (resourceId > 0) {
          result = context.getResources().getDimensionPixelSize(resourceId);
          }
          return result;
          }
          復(fù)制代碼

          新版的方法和老版本的方法都定義了,通常我們使用 WindowInsets 的方式即可獲取到導(dǎo)航欄對象,然后去獲取它的高度。

          而老版本的方式則是通過獲取系列內(nèi)置的一個高度值,而一些手機(jī)并不會按這個高度設(shè)置導(dǎo)航欄高度,所以獲取出來的值則是錯誤的。

          如下圖所示:

          1433c3b1d58710235347e3f5df9d7b9c.webp

          image.png

          2.3 導(dǎo)航欄的隱藏與沉浸式處理

          在一些應(yīng)用需要全屏的時候,我們需要隱藏導(dǎo)航欄(是的,你無法返回了)。

                  /**
          * 顯示隱藏底部導(dǎo)航欄(注意不是沉浸式效果)
          */

          public static void showHideNavigationBar(Activity activity, boolean isShow) {

          View decorView = activity.findViewById(android.R.id.content);
          WindowInsetsControllerCompat controller = ViewCompat.getWindowInsetsController(decorView);

          if (controller != null) {
          if (isShow) {
          controller.show(WindowInsetsCompat.Type.navigationBars());
          controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH);
          } else {
          controller.hide(WindowInsetsCompat.Type.navigationBars());
          controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
          }
          }
          }
          復(fù)制代碼

          而在一些常規(guī)的頁面,我們?nèi)绻胂駹顟B(tài)欄一樣獲取沉浸式體驗,我們則是不同的處理邏輯:

                  /**
          * 5.0以上-設(shè)置NavigationBar底部導(dǎo)航欄的沉浸式
          */

          public static void immersiveNavigationBar(Activity activity) {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          Window window = activity.getWindow();
          View decorView = window.getDecorView();
          decorView.setSystemUiVisibility(decorView.getSystemUiVisibility()
          | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
          | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

          window.setNavigationBarColor(Color.TRANSPARENT);
          }
          }
          復(fù)制代碼

          我們把導(dǎo)航欄常用的一些操作理清之后,我們再來看 StatusHostLayout 這樣的宿主方案如何幫助我們管理導(dǎo)航欄。

          三、 修改StatusHostLayout方案

          前文我們講到過狀態(tài)欄的管理,如果加入導(dǎo)航欄的管理,我們需要做哪些操作?

          先理清一下思路:

          1. 定義一個自定義的ViewGroup,內(nèi)部順序排列狀態(tài)欄,內(nèi)容容器,導(dǎo)航欄三個布局。

          2. 我們需要強(qiáng)制設(shè)置狀態(tài)欄和導(dǎo)航欄的沉浸式,讓我們自己的狀態(tài)欄.導(dǎo)航欄View的布局展示出來。

          3. 自定義狀態(tài)欄View,與導(dǎo)航欄View,我們只需要獲取到正確的高度,然后測量的時候定死指定的高度即可。

          4. 我們可以以View的形式來操作自定義導(dǎo)航欄/狀態(tài)欄的背景,圖片,顯示隱藏等操作。

          5. 把我們DecorView中的跟視圖替換為我們自定義的布局。

          6. 暴露一個inject方法注入到指定的Activity中去,并提供自定義布局的對象。

          之前狀態(tài)欄的邏輯已經(jīng)做好了,現(xiàn)在我們只需要處理導(dǎo)航欄的邏輯。我們定義好上面的一些導(dǎo)航欄操作工具類方法。

          先定義一個自己的導(dǎo)航欄View,只需要處理高度即可。

              /**
          * 自定義底部導(dǎo)航欄的View,用于StatusBarHostLayout中使用
          */

          class NavigationView extends View {

          private int mBarSize;

          public NavigationView(Context context) {
          this(context, null, 0);
          }

          public NavigationView(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
          }

          public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);

          StatusBarHostUtils.getNavigationBarHeight(this, new HeightValueCallback() {
          @Override
          public void onHeight(int height) {

          mBarSize = height;
          }
          });
          }

          @Override
          protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mBarSize);
          } else {
          setMeasuredDimension(0, 0);
          }
          }

          public int getBarSize() {
          return mBarSize;
          }

          }
          復(fù)制代碼

          然后在自定義的布局中添加我們的導(dǎo)航欄View

                  //加載自定義的宿主布局
          if (mStatusView == null && mContentLayout == null) {
          setOrientation(LinearLayout.VERTICAL);

          mStatusView = new StatusView(mActivity);
          mStatusView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
          addView(mStatusView);

          mContentLayout = new FrameLayout(mActivity);
          mContentLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f));
          addView(mContentLayout);

          mNavigationView = new NavigationView(mActivity);
          mNavigationView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
          addView(mNavigationView);
          }
          復(fù)制代碼

          核心方法是替換掉 DecorView 中的 ContentView:

                  private void replaceContentView() {
          Window window = mActivity.getWindow();
          ViewGroup contentLayout = window.getDecorView().findViewById(Window.ID_ANDROID_CONTENT);
          if (contentLayout.getChildCount() > 0) {
          //先找到DecorView的容器移除掉已經(jīng)設(shè)置的ContentView
          View contentView = contentLayout.getChildAt(0);
          contentLayout.removeView(contentView);
          ViewGroup.LayoutParams contentParams = contentView.getLayoutParams();

          //外部設(shè)置的ContentView添加到宿主中來
          mContentLayout.addView(contentView, contentParams.width, contentParams.height);
          }
          //再把整個宿主添加到Activity對應(yīng)的DecorView中去
          contentLayout.addView(this, -1, -1);
          }
          復(fù)制代碼

          然后我們暴露一些公共的方法供外界操作我們自定義的導(dǎo)航欄:

               /**
          * 設(shè)置導(dǎo)航欄圖片顏色為黑色
          */

          public StatusBarHostLayout setNavigatiopnBarIconBlack() {
          StatusBarHostUtils.setNavigationBarDrak(mActivity, true);
          return this;
          }

          /**
          * 設(shè)置導(dǎo)航欄圖片顏色為白色
          */

          public StatusBarHostLayout setNavigatiopnBarIconWhite() {
          StatusBarHostUtils.setNavigationBarDrak(mActivity, false);
          return this;
          }

          /**
          * 設(shè)置自定義狀態(tài)欄布局的背景顏色
          */

          public StatusBarHostLayout setNavigationBarBackground(int color) {
          if (mNavigationView != null)
          mNavigationView.setBackgroundColor(color);
          return this;
          }

          /**
          * 設(shè)置自定義狀態(tài)欄布局的背景圖片
          */

          public StatusBarHostLayout setNavigationBarBackground(Drawable drawable) {
          if (mNavigationView != null)
          mNavigationView.setBackground(drawable);
          return this;
          }

          /**
          * 設(shè)置自定義狀態(tài)欄布局的透明度
          */

          public StatusBarHostLayout setNavigationBarBackgroundAlpha(int alpha) {
          if (mNavigationView != null) {
          Drawable background = mNavigationView.getBackground();
          if (background != null) {
          background.mutate().setAlpha(alpha);
          }
          }
          return this;
          }

          /**
          * 設(shè)置自定義導(dǎo)航欄的沉浸式
          */

          public StatusBarHostLayout setNavigationBarImmersive(boolean needImmersive, int color) {
          if (mNavigationView != null) {
          if (needImmersive) {
          mNavigationView.setVisibility(GONE);
          } else {
          mNavigationView.setVisibility(VISIBLE);
          mNavigationView.setBackgroundColor(color);
          }
          }
          return this;
          }
          復(fù)制代碼

          使用的時候我們這樣用:

                 val hostLayout = StatusBarHost.inject(this)
          .setStatusBarBackground(startColor)
          .setStatusBarBlackText()
          .setNavigationBarBackground(startColor)

          //修改導(dǎo)航欄的圖標(biāo)顏色 - 深色
          fun btn07(view: View) {
          hostLayout.setNavigationBarIconBlack()
          }

          //修改導(dǎo)航欄的圖標(biāo)顏色 - 亮色
          fun btn08(view: View) {
          hostLayout.setNavigationBarIconWhite()
          }

          fun btn06(view: View) {
          hostLayout.setNavigationBarBackground(resources.getColor(R.color.teal_200))
          }
          復(fù)制代碼

          其中的一些效果如下圖所示,更多的示例代碼可以查看源碼:

          狀態(tài)欄的操作:

          2d5da76de3169f3a4dfdffdd7e2d2899.webp

          image.png

          7185d3ad5f9e2cbb9827c7244568c7a7.webp

          image.png

          導(dǎo)航欄的操作:

          cb282d14f45b7d4302279c966b5744f2.webp

          image.png


          5f6e9ed56a96a7bb4d8ee2ffc607b8dc.webp

          image.png

          狀態(tài)欄與導(dǎo)航欄的沉浸式處理


          7122066837cd404f0b609d816cec8d11.webp

          image.png


          4fcfcd520d1c101c98df7d9ab8a63576.webp

          image.png

          狀態(tài)欄與導(dǎo)航欄圖片背景的設(shè)置

          7d79112586e7f3f66222c5432e7a7020.webp

          image.png

          全面屏手機(jī)與老款的可動態(tài)隱藏導(dǎo)航欄的手機(jī)都能正確的判斷是否有導(dǎo)航欄:

          f49702c72db46133fa4215c442d0cc4f.webp

          image.png

          Android5.0的老款手機(jī),不帶內(nèi)置導(dǎo)航欄的:

          53f4c6308c719932ad8c17dd95a6864d.webp

          image.png

          Android12三星手機(jī)滾動的效果:

          eb047d3fb450b37052c80253f24afb4f.webp

          image.png

          四、總結(jié)

          由于使用了 WindowInsetsController 的Api,所以本方案支持Android5.0+版本。

          有關(guān)更多的Demo與效果可以查看我的源碼項目,點(diǎn)擊查看,我會持續(xù)更新和優(yōu)化。大家可以點(diǎn)個Star關(guān)注一波。

          關(guān)于本文的Demo我也單獨(dú)做了項目與Demo的效果,點(diǎn)擊查看。

          如果你想直接使用,我也已經(jīng)上傳到 MavenCentral ,直接依賴即可。

              implementation "com.gitee.newki123456:status_host_layout:1.0.0"
          復(fù)制代碼

          慣例,我如有講解不到位或錯漏的地方,希望同學(xué)們可以指出交流。

          作者:newki
          鏈接:https://juejin.cn/post/7150088571313979399

          友情推薦:

          Android 開發(fā)干貨集錦

          至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章,小編覺得很優(yōu)秀,歡迎點(diǎn)擊閱讀原文,支持原創(chuàng)作者,如有侵權(quán),懇請聯(lián)系小編刪除,歡迎您的建議與指正。同時期待您的關(guān)注,感謝您的閱讀,謝謝!

          點(diǎn)擊閱讀原文,為大佬點(diǎn)贊!

          瀏覽 157
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  不卡的自排偷排视频网站 | 影音成人| 波多野结衣被干 | 毛片在线看不卡 | 亚州最大操B网站 |