解析Android 8.1平台SystemUI 导航栏加载流程

需求

基于MTK8163 8.1平台定制导航栏部分,在左边增加音量减,右边增加音量加

思路

需求开始做之前,一定要研读SystemUI Navigation模块的代码流程!!!不要直接去网上copy别人改的需求代码,盲改的话很容易出现问题,然而无从解决。网上有老平台(8.0-)的讲解System UI的导航栏模块的博客,自行搜索。8.0对System UI还是做了不少细节上的改动,代码改动体现上也比较多,但是总体基本流程并没变。

源码阅读可以沿着一条线索去跟代码,不要过分在乎代码细节!例如我客制化这个需求,可以跟着导航栏的返回(back),桌面(home),最近任务(recent)中的一个功能跟代码流程,大体知道比如recen这个view是哪个方法调哪个方法最终加载出来,加载的关键代码在哪,点击事件怎么生成,而不在意里面的具体逻辑判断等等。

代码流程

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

从状态栏入口开始看。

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();//创建导航栏
    }
  } catch (RemoteException ex) {
  }
}

2.进入 createNavigationBar 方法,发现主要是用 NavigationBarFragment 来管理.

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了导航栏的布局,最终add了fragment的onCreateView加载的布局。(其实SystemUI所有的模块都是WindowManager来加载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到这个Window属性的view里的。
      .commit();
  fragmentHost.addTagListener(TAG, listener);
  return navigationBarView;
 }
}

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

来看WindowManager加载的这个view的布局:navigation_bar_window.xml,发现根布局是自定义的view类NavigationBarFrame.(其实SystemUI以及其他系统应用如Launcher,都是这种自定义view的方式,好多逻辑处理也都是在自定义view里,不能忽略)

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

我们进入NavigationBarFrame类。发现类里并不是我们的预期,就是一个FrameLayout,对DeadZone功能下的touch事件做了手脚,不管了。

6.再回来看看NavigationBarFragment的生命周期呢。onCreateView()里,导航栏的真正的rootView。

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

进入导航栏的真正根布局:navigation_bar.xml,好吧又是自定义view,NavigationBarView 和 NavigationBarInflaterView 都要仔细研读。

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

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

先看构造方法,因为加载xml布局首先走的是初始化

public NavigationBarInflaterView(Context context, AttributeSet attrs) {
  super(context, attrs);
  createInflaters();//根据屏幕旋转角度创建子view(单个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的生命周期,每个view被inflate之后都会回调。

@Override
protected void onFinishInflate() {
  super.onFinishInflate();
  inflateChildren();//进去看无关紧要 忽略
  clearViews();//进去看无关紧要 忽略
  inflateLayout(getDefaultLayout());//关键方法:加载了 back.home.recent三个按钮的layout
}

看inflateLayout():里面的newLayout参数很重要!!!根据上一个方法看到getDefaultLayout(),他return了一个在xml写死的字符串。再看inflateLayout方法,他解析分割了xml里配置的字符串,并传给了inflateButtons方法

protected void inflateLayout(String newLayout) {
  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]
  // 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);//创建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;
}

我们来看createView()方法:以home按键为例,加载了home的button,其实是加载了 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
//这里布局里没有src显示home的icon,肯定是在代码里设置了
//这里也是自定义view:KeyButtonView
<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"//引用了dimens.xml里的navigation_key_width
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="3"//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

先来看KeyButtonView的构造方法:我们之前xml的systemui:keyCode=”3”方法在这里获取。再来看Touch事件,通过sendEvent()方法可以看出,back等view的点击touch事件不是自己处理的,而是交由系统以实体按键(keycode)的形式处理的.

当然KeyButtonView类还处理了支持长按的button,按键的响声等,这里忽略。

至此,导航栏按键事件我们梳理完毕。

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);//关键方法
      } 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;
  //这里根据mCode new了一个KeyEvent事件,通过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.还遗留一个问题:设置图片的icon到底在哪?我们之前一直阅读的是NavigationBarInflaterView,根据布局我们还有一个类没有看,NavigationBarView.java

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

进入NavigationBarView类里,找到构造方法。

public NavigationBarView(Context context, AttributeSet attrs) {
  super(context, attrs);
  mDisplay = ((WindowManager) context.getSystemService(
      Context.WINDOW_SERVICE)).getDefaultDisplay();
  ...
  ...
  updateIcons(context, Configuration.EMPTY, mConfiguration);//关键方法
  mBarTransitions = new NavigationBarTransitions(this);
  //mButtonDispatchers 是维护这些home back recent图标view的管理类,会传递到他的child,NavigationBarInflaterView类中
  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为例,在初始化时得到了mRecentIcon的资源,再看谁调用了了mRecentIcon就可知道,即反推看调用流程。

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

updateRecentsIcon这个方法设置了recent图片的资源,再看谁调用了updateRecentsIcon方法:onConfigurationChanged屏幕旋转会重新设置资源图片

@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()也调用了setNavigationIconHints()方法:

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

再朝上推,最终追溯到NavigationBarFragment的onConfigurationChanged()方法 和 NavigationBarView的onAttachedToWindow()和onSizeChanged()方法。也就是说,在NavigationBarView导航栏这个布局加载的时候就会设置图片资源,和长度改变,屏幕旋转都有可能引起重新设置

至此,SystemUI的虚拟导航栏模块代码流程结束。

总结

  1. 创建一个window属性的父view
  2. 通过读取解析xml里config的配置,addView需要的icon,或者调换顺序
  3. src图片资源通过代码设置亮色和暗色
  4. touch事件以keycode方式交由系统处理

以上所述是小编给大家介绍的Android 8.1平台SystemUI 导航栏加载流程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • Android中TabLayout+ViewPager 简单实现app底部Tab导航栏

    前言 在谷歌发布Android Design Support Library之前,app底部tab布局的实现方法就有很多种,其中有RadioGroup+FrameLayout.TabHost+Fragment.FragmentPagerAdapter+ViewPager等方法,虽然这些方法虽然能达到同样的效果,但我个人总觉得有些繁琐.然而,Google在2015的IO大会上,给开发者们带来了全新的Android Design Support Library,里面包含了许多新控件,这些新控件有许多

  • Android自定义顶部导航栏控件实例代码

    下面一段代码给大家介绍了android 自定义顶部导航栏控件功能,具体代码如下所示: class HeaderBar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { //重写构造方法 在java里面 我们一般是重写三个构造方法//在kotlin中 我们可以使用

  • Android实现简单底部导航栏 Android仿微信滑动切换效果

    Android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义View配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏的设置方法类似于TabLayout的关联,View需要创建关联方法,用来关联VIewPager; 3. 通过关联方法获取ViewPager实例后,根据ViewPager页面数创建底部导航栏的图标按钮; 代码实现: 1. 新建第一个自定义View, 图标 + 文字 的底部按钮; /** * 自定义控件

  • 超简单的几行代码搞定Android底部导航栏功能

    超简单,几行代码搞定Android底部导航栏-–应项目需求以及小伙伴的留言,新加了两个方法: 设置底部导航栏背景图片 添加底部导航栏选项卡切换监听事件 底部导航栏的实现也不难,就是下边是几个Tab切换,上边一般是一个FrameLayout,然后FrameLayout中切换fragment. 网上有不少关于Android底部导航栏的文章,不过好像都只是关于下边Tab切的,没有实现Tab与fragment的联动,用的时候还要自己手写这部分代码,对我这个比较懒(据说,懒是程序员的一种美德_#)得程序员

  • Android自定义ViewPagerIndicator实现炫酷导航栏指示器(ViewPager+Fragment)

    ViewPagerIndicator导航栏指示器运行效果: 实现这个效果,我是看了很多大神写的博客和视频后自己敲的,欢迎指正 github地址:https://github.com/dl10210950/TabViewPagerIndicator 自定义一个ViewPagerIndicator 自定义一个Indicator继承LinearLayout,在构造方法里面设置画笔的一些属性 public ViewPagerIndicator(Context context, AttributeSet

  • Android 沉浸式状态栏与隐藏导航栏实例详解

    1 前言 一般我们在Android的APP开发中,APP的界面如下: 可以看到,有状态栏.ActionBar(ToolBar).导航栏等,一般来说,APP实现沉浸式有三种需求:沉浸式状态栏,隐藏导航栏,APP全屏 沉浸式状态栏是指状态栏与ActionBar颜色相匹配, 隐藏导航栏不用多说,就是将导航栏隐藏,去掉下面的黑条. APP全屏是指将状态栏与导航栏都隐藏,例如很多游戏界面,都是APP全屏. 所以,在做这一步时,关键要问清楚产品狗的需求,免得白费功夫. 下面,分别来介绍这三种方式的实现. 2

  • Android虚拟导航栏遮挡底部的输入框的解决方法

    1.场景还原 最近忙着app的适配,在这个过程问题中,各种机型的奇葩问题都出来了,适配真尼玛痛苦!今天就oppo机型虚拟导航栏遮挡底部的输入框的问题作个记录. 2.解决方法 ① 在该Activity的根layout配置如下属性: android:fitsSystemWindows="true" android:clipToPadding="false" 第一个属性: 如果为true,将调整系统窗口布局以适应你自定义的布局. 第二个属性: 控件的绘制区域是否在padd

  • Android开发之判断有无虚拟按键(导航栏)的实例

    判断有无虚拟按键(导航栏) 现在很大一部分手机没有虚拟按键,一部分有.我们在做适配的时候可能会用到这方面的知识. 例如:屏幕填充整个屏幕的时候,没办法只能连导航栏一起填充了,但是这个不是我们想要的,我们要给布局试着paddingbottom,这个时候我们就要判断有么有导航栏,导航栏高度是多少了. /** * 获取是否存在NavigationBar * @param context * @return */ public boolean checkDeviceHasNavigationBar(Co

  • Android9.0 SystemUI 网络信号栏定制修改的流程解析

    前情提要 Android 8.1平台SystemUI 导航栏加载流程解析 9.0 改动点简要说明 1.新增 StatusBarMobileView 替代 SignalClusterView,用以控制信号栏显示 同时增加的还有 StatusBarIconView.StatusBarWifiView 2.整体流程和 8.1 类似 效果图 整体流程图 上代码 先来看初始赋值的地方 MobileSignalController.java,在 notifyListeners() 方法中进行我们对应的定制,

  • 解析Android 8.1平台SystemUI 导航栏加载流程

    需求 基于MTK8163 8.1平台定制导航栏部分,在左边增加音量减,右边增加音量加 思路 需求开始做之前,一定要研读SystemUI Navigation模块的代码流程!!!不要直接去网上copy别人改的需求代码,盲改的话很容易出现问题,然而无从解决.网上有老平台(8.0-)的讲解System UI的导航栏模块的博客,自行搜索.8.0对System UI还是做了不少细节上的改动,代码改动体现上也比较多,但是总体基本流程并没变. 源码阅读可以沿着一条线索去跟代码,不要过分在乎代码细节!例如我客制

  • Android ActionBar完全解析使用官方推荐的最佳导航栏(下)

    本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文. http://developer.android.com/guide/topics/ui/actionbar.html 限于篇幅的原因,在上篇文章中我们只学习了ActionBar基础部分的知识,那么本篇文章我们将接着上一章的内容继续学习,探究一下ActionBar更加高级的知识.如果你还没有看过前面一篇文章的话,建议先去阅读Android ActionBar完全解析,使用官方推荐的最佳导航栏(

  • Android ActionBar完全解析使用官方推荐的最佳导航栏(上)

    本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文. http://developer.android.com/guide/topics/ui/actionbar.html Action Bar是一种新増的导航栏功能,在Android 3.0之后加入到系统的API当中,它标识了用户当前操作界面的位置,并提供了额外的用户动作.界面导航等功能.使用ActionBar的好处是,它可以给提供一种全局统一的UI界面,使得用户在使用任何一款软件时都懂得该如何

  • Android仿网易客户端顶部导航栏效果

    最近刚写了一个网易客户端首页导航条的动画效果,现在分享出来给大家学习学习.我说一下这个效果的核心原理.下面是效果图: 首先是布局,这个布局是我从网易客户端反编译后弄来的.大家看后应该明白,布局文件如下: <FrameLayout android:id="@id/column_navi" android:layout_width="fill_parent" android:layout_height="wrap_content" androi

  • Android自定义View实现字母导航栏的代码

    思路分析: 1.自定义View实现字母导航栏 2.ListView实现联系人列表 3.字母导航栏滑动事件处理 4.字母导航栏与中间字母的联动 5.字母导航栏与ListView的联动 效果图: 首先,我们先甩出主布局文件,方便后面代码的说明 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/re

  • Android 自定义Dialog去除title导航栏的解决方法

    如下所示: Dialog dialog = new Dialog(context); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setContentView(view); 以上这篇Android 自定义Dialog去除title导航栏的解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • 解决android 显示内容被底部导航栏遮挡的问题

    描述: 由于产品需求,要求含有EditText的界面全屏显示,最好的解决方式是使用AndroidBug5497Workaround.assistActivity(this) 的方式来解决,但是华为和魅族手机系统自带的有底部导航栏,会造成一些布局被遮挡. 解决方案:在values-21的style.xml中添加android:windowDrawsSystemBarBackgrounds"并将值设置为false,方式如下 在style引用的主题里面加入android:windowDrawsSyst

  • Android开发快速实现底部导航栏示例

    目录 Tint 着色器 依赖(AndroidX) 布局 编写渲染颜色选择器-tint_selector_menu_color menu 文件中 icon-nav_bottom_menu BottomNavigationView的点击事件 配合ViewPager实现Tab栏 对应的适配器 Tint 着色器 优点:去除“无用”图片,节省空间 配合BottomNavigationView,实现一个快速,简洁的Tab栏 传统做法:Tab 切换,字体变色.图片变色.至少给我提供八张图,四张默认,四张选中,

  • Android程序开发ListView+Json+异步网络图片加载+滚动翻页的例子(图片能缓存,图片不错乱)

    例子中用于解析Json的Gson请自己Google下载 主Activity: package COM.Example.Main; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import COM.Example.Main.R; import COM.Example.Main.stringG

  • Android优化方案之Fragment的懒加载实现代码

    一.背景 在Android应用中,ViewPager是我们不可避免使用的一个控件,因为它可以使我们在占用较少空间的同时,增强内容的丰富性,同时以其内部流淌着Google的血液,所以它几乎成了每一个App的标配控件.但是,假如ViewPager的每一个Fragment都需要通过网络拉取数据加载,而ViewPager是默认加载前两项的,所以在很容易造成网络丢包或者网络堵塞等问题,所以Fragment使用懒加载是非常有必要的. 举个栗子: 如上图所示,我们有两个大的Tab:人物和风景.而人物Tab下有

随机推荐