• Android底层摸索改BUG(一):Android系统状态栏显示不下Wifi图标


    这是我入职的第一个BUG,头疼,隔壁一周解决了,我多花了几天

    其中最大的原因就是我思考复杂了,在公司系统上,此BUG标题为:

    请确认Wifi优先级,状态栏Wifi被忽略

    BUG意思就是:当前安卓系统状态栏图标有显示尺寸的测量,如果比如需要显示8个图标,已经在状态栏绘制不下,则显示一个点表示省略,而不希望wifi被省略

    我思考了一下一直以为就是优先级问题,是不是Android底层状态栏有各个图标优先级,图标过多的时候优先级高的就不会被隐藏,而其实最后参考了公司以前相关BUG的修改,其实只是状态栏的图标大小修改即可,将图标改小,状态栏就可以多一个显示图标,Wifi就不会被隐藏。(点向后一个图标挪了一位)

    在尺寸文件中进行修改:对这两个尺寸进行改小(可以调一下刷机看一下)

    1. <dimen name="status_bar_icon_size">14dip</dimen>
    2. <dimen name="status_bar_system_icon_size">11.5dp</dimen>

    值得一提的是上面的逻辑:超出的图标隐藏、绘制、测量相关的Java类是:StatusIconContainer.Java(虽然和BUG不在这改,记录一下)

    1. /**
    2. * StatusIconContainer 是 StatusBarMobileView 内部的一个自定义视图容器,用于管理状态图标的显示。
    3. * 它负责在 StatusBarMobileView 中管理状态图标的布局和可见性。
    4. */
    5. public class StatusIconContainer extends AlphaOptimizedLinearLayout {
    6. //TAG
    7. private static final String TAG = "StatusIconContainer";
    8. //是否打开DEBUG模式,会Log相关信息
    9. private static final boolean DEBUG = false;
    10. //是否打开DEBUG_OVERFLOW,会在状态栏绘制边框用来显示区域什么的
    11. private static final boolean DEBUG_OVERFLOW = false;
    12. // 最多可以显示 8 个状态图标,包括电池图标
    13. private static final int MAX_ICONS = 7;
    14. // private static final int MAX_ICONS = 8;
    15. //点的个数
    16. private static final int MAX_DOTS = 1;
    17. //Dot 点 Icon图标 相关属性
    18. private int mDotPadding;
    19. private int mIconSpacing;
    20. private int mStaticDotDiameter;
    21. private int mUnderflowWidth;
    22. private int mUnderflowStart = 0;
    23. // 是否可以在溢出空间中绘制
    24. private boolean mNeedsUnderflow;
    25. // 单个 StatusBarIconView 的点图标在此宽度内居中显示
    26. private int mIconDotFrameWidth;
    27. private boolean mShouldRestrictIcons = true;
    28. // 用于计算哪些状态要在布局期间可见
    29. private ArrayList mLayoutStates = new ArrayList<>();
    30. // 为了正确计数和测量
    31. private ArrayList mMeasureViews = new ArrayList<>();
    32. // 任何被忽略的图标将不会被添加为子视图
    33. private ArrayList mIgnoredSlots = new ArrayList<>();
    34. /**
    35. * 创建 StatusIconContainer 的构造函数。
    36. *
    37. * @param context Android 上下文对象
    38. */
    39. public StatusIconContainer(Context context) {
    40. this(context, null);
    41. }
    42. /**
    43. * 创建 StatusIconContainer 的构造函数。
    44. *
    45. * @param context Android 上下文对象
    46. * @param attrs 属性集
    47. */
    48. public StatusIconContainer(Context context, AttributeSet attrs) {
    49. super(context, attrs);
    50. initDimens();
    51. setWillNotDraw(!DEBUG_OVERFLOW);
    52. }
    53. @Override
    54. protected void onFinishInflate() {
    55. super.onFinishInflate();
    56. }
    57. /**
    58. * 设置是否限制状态图标的显示。
    59. *
    60. * @param should 是否应该限制状态图标的显示
    61. */
    62. public void setShouldRestrictIcons(boolean should) {
    63. mShouldRestrictIcons = should;
    64. }
    65. /**
    66. * 检查是否正在限制状态图标的显示。
    67. *
    68. * @return 如果正在限制状态图标的显示,则返回 true;否则返回 false。
    69. */
    70. public boolean isRestrictingIcons() {
    71. return mShouldRestrictIcons;
    72. }
    73. /**
    74. * 初始化尺寸参数。
    75. */
    76. private void initDimens() {
    77. // 这是与 StatusBarIconView 使用相同的值
    78. mIconDotFrameWidth = getResources().getDimensionPixelSize(
    79. com.android.internal.R.dimen.status_bar_icon_size);
    80. mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
    81. mIconSpacing = getResources().getDimensionPixelSize(R.dimen.status_bar_system_icon_spacing);
    82. // 点图标
    83. int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
    84. // 计算点的直径 R.dimen.overflow_dot_radius 2dp
    85. mStaticDotDiameter = 2 * radius;
    86. // 不能超过宽度 mIconDotFrameWidth 图标宽度 最大图标个数-1(可能还有个点,所以-1) 每个宽高
    87. mUnderflowWidth = mIconDotFrameWidth + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding);
    88. android.util.Log.d(TAG, "initDimens: " + mUnderflowStart);
    89. }
    90. @Override
    91. protected void onLayout(boolean changed, int l, int t, int r, int b) {
    92. float midY = getHeight() / 2.0f;
    93. // 首先对所有子视图进行布局,以便稍后移动它们
    94. for (int i = 0; i < getChildCount(); i++) {
    95. View child = getChildAt(i);
    96. int width = child.getMeasuredWidth();
    97. int height = child.getMeasuredHeight();
    98. int top = (int) (midY - height / 2.0f);
    99. child.layout(0, top, width, top + height);
    100. }
    101. resetViewStates();
    102. calculateIconTranslations();
    103. applyIconStates();
    104. }
    105. //绘制
    106. @Override
    107. protected void onDraw(Canvas canvas) {
    108. super.onDraw(canvas);
    109. if (DEBUG_OVERFLOW) {
    110. //如果开启了DEBUG_OVERFLOW模式画框框
    111. Paint paint = new Paint();
    112. paint.setStyle(Style.STROKE);
    113. paint.setColor(Color.RED);
    114. // 显示边界框
    115. canvas.drawRect(getPaddingStart(), 0, getWidth() - getPaddingEnd(), getHeight(), paint);
    116. // 显示溢出框
    117. paint.setColor(Color.GREEN);
    118. canvas.drawRect(
    119. mUnderflowStart, 0, mUnderflowStart + mUnderflowWidth, getHeight(), paint);
    120. }
    121. }
    122. //测量
    123. @Override
    124. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    125. mMeasureViews.clear();
    126. int mode = MeasureSpec.getMode(widthMeasureSpec);
    127. final int width = MeasureSpec.getSize(widthMeasureSpec);
    128. final int count = getChildCount();
    129. // 收集所有希望进行布局的视图
    130. for (int i = 0; i < count; i++) {
    131. // 获取子视图
    132. StatusIconDisplayable icon = (StatusIconDisplayable) getChildAt(i);
    133. // 如果视图可见且没有被阻止,则加入可见视图列表
    134. if (icon.isIconVisible() && !icon.isIconBlocked()
    135. && !mIgnoredSlots.contains(icon.getSlot())) {
    136. mMeasureViews.add((View) icon);
    137. }
    138. }
    139. // 可见视图数量
    140. int visibleCount = mMeasureViews.size();
    141. int maxVisible = visibleCount <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
    142. int totalWidth = mPaddingLeft + mPaddingRight;
    143. boolean trackWidth = true;
    144. // 测量所有子视图,以便它们报告正确的宽度
    145. int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
    146. mNeedsUnderflow = mShouldRestrictIcons && visibleCount > MAX_ICONS;
    147. for (int i = 0; i < visibleCount; i++) {
    148. View child = mMeasureViews.get(i);
    149. measureChild(child, childWidthSpec, heightMeasureSpec);
    150. int spacing = i == visibleCount - 1 ? 0 : mIconSpacing;
    151. if (mShouldRestrictIcons) {
    152. if (i < maxVisible && trackWidth) {
    153. totalWidth += getViewTotalMeasuredWidth(child) + spacing;
    154. } else if (trackWidth) {
    155. // 达到图标限制;为点图标添加空间
    156. totalWidth += mUnderflowWidth;
    157. trackWidth = false;
    158. }
    159. } else {
    160. totalWidth += getViewTotalMeasuredWidth(child) + spacing;
    161. }
    162. }
    163. if (mode == MeasureSpec.EXACTLY) {
    164. if (!mNeedsUnderflow && totalWidth > width) {
    165. mNeedsUnderflow = true;
    166. }
    167. setMeasuredDimension(width, MeasureSpec.getSize(heightMeasureSpec));
    168. } else {
    169. if (mode == MeasureSpec.AT_MOST && totalWidth > width) {
    170. mNeedsUnderflow = true;
    171. totalWidth = width;
    172. }
    173. setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
    174. }
    175. }
    176. @Override
    177. public void onViewAdded(View child) {
    178. super.onViewAdded(child);
    179. StatusIconState vs = new StatusIconState();
    180. vs.justAdded = true;
    181. child.setTag(R.id.status_bar_view_state_tag, vs);
    182. }
    183. @Override
    184. public void onViewRemoved(View child) {
    185. super.onViewRemoved(child);
    186. child.setTag(R.id.status_bar_view_state_tag, null);
    187. }
    188. /**
    189. * 添加要忽略的图标槽的名称。它将不会显示在布局中也不会被测量。
    190. *
    191. * @param slotName 图标的名称,就像在 frameworks/base/core/res/res/values/config.xml 中定义的那样
    192. */
    193. public void addIgnoredSlot(String slotName) {
    194. android.util.Log.d(TAG, "addIgnoredSlot: " + slotName);
    195. boolean added = addIgnoredSlotInternal(slotName);
    196. if (added) {
    197. requestLayout();
    198. }
    199. }
    200. /**
    201. * 添加要忽略的图标槽的名称的列表。
    202. *
    203. * @param slots 要忽略的图标的名称列表
    204. */
    205. public void addIgnoredSlots(List slots) {
    206. for (String slot : slots) {
    207. android.util.Log.d(TAG, "addIgnoredSlots: " + slot);
    208. }
    209. boolean willAddAny = false;
    210. for (String slot : slots) {
    211. willAddAny |= addIgnoredSlotInternal(slot);
    212. }
    213. if (willAddAny) {
    214. requestLayout();
    215. }
    216. }
    217. /**
    218. * 内部添加要忽略的图标槽名称。
    219. *
    220. * @param slotName 图标的名称,就像在 frameworks/base/core/res/res/values/config.xml 中定义的那样
    221. * @return 如果成功添加,则返回 true,否则返回 false
    222. */
    223. private boolean addIgnoredSlotInternal(String slotName) {
    224. android.util.Log.d(TAG, "addIgnoredSlotInternal: " + slotName);
    225. if (mIgnoredSlots.contains(slotName)) {
    226. return false;
    227. }
    228. mIgnoredSlots.add(slotName);
    229. return true;
    230. }
    231. /**
    232. * 从忽略的图标槽中移除一个名称。
    233. *
    234. * @param slotName 要移除的图标槽的名称
    235. */
    236. public void removeIgnoredSlot(String slotName) {
    237. android.util.Log.d(TAG, "removeIgnoredSlot: " + slotName);
    238. boolean removed = mIgnoredSlots.remove(slotName);
    239. if (removed) {
    240. requestLayout();
    241. }
    242. }
    243. /**
    244. * 从忽略的图标槽中移除名称的列表。
    245. *
    246. * @param slots 要移除的图标槽的名称列表
    247. */
    248. public void removeIgnoredSlots(List slots) {
    249. for (String slot: slots) {
    250. android.util.Log.d(TAG, "removeIgnoredSlot: " + slot);
    251. }
    252. boolean removedAny = false;
    253. for (String slot : slots) {
    254. removedAny |= mIgnoredSlots.remove(slot);
    255. }
    256. if (removedAny) {
    257. requestLayout();
    258. }
    259. }
    260. /**
    261. * 设置要忽略的图标槽的列表,清除当前的列表。
    262. *
    263. * @param slots 要忽略的图标槽的名称列表
    264. */
    265. public void setIgnoredSlots(List slots) {
    266. mIgnoredSlots.clear();
    267. addIgnoredSlots(slots);
    268. }
    269. /**
    270. * 返回与特定槽名称相对应的视图。
    271. * 仅用于操作它如何呈现。
    272. *
    273. * @param slot 槽名称,与 com.android.internal.R.config_statusBarIcons 中定义的名称相对应
    274. * @return 如果此容器拥有相应的视图,则返回该视图,否则返回 null
    275. */
    276. public View getViewForSlot(String slot) {
    277. for (int i = 0; i < getChildCount(); i++) {
    278. View child = getChildAt(i);
    279. if (child instanceof StatusIconDisplayable
    280. && ((StatusIconDisplayable) child).getSlot().equals(slot)) {
    281. return child;
    282. }
    283. }
    284. return null;
    285. }
    286. /**
    287. * 布局从右到左发生。
    288. */
    289. private void calculateIconTranslations() {
    290. mLayoutStates.clear();
    291. float width = getWidth();
    292. float translationX = width - getPaddingEnd();
    293. float contentStart = getPaddingStart();
    294. int childCount = getChildCount();
    295. // Underflow === 不显示内容直到此索引
    296. if (DEBUG) Log.d(TAG, "calculateIconTranslations: start=" + translationX
    297. + " width=" + width + " underflow=" + mNeedsUnderflow);
    298. // 收集所有希望可见的状态
    299. for (int i = childCount - 1; i >= 0; i--) {
    300. View child = getChildAt(i);
    301. StatusIconDisplayable iconView = (StatusIconDisplayable) child;
    302. StatusIconState childState = getViewStateFromChild(child);
    303. if (!iconView.isIconVisible() || iconView.isIconBlocked()
    304. || mIgnoredSlots.contains(iconView.getSlot())) {
    305. childState.visibleState = STATE_HIDDEN;
    306. if (DEBUG) Log.d(TAG, "skipping child (" + iconView.getSlot() + ") not visible");
    307. continue;
    308. }
    309. // 移动 translationX 到布局的位置,以添加视图而不截断子视图
    310. translationX -= getViewTotalWidth(child);
    311. childState.visibleState = STATE_ICON;
    312. childState.xTranslation = translationX;
    313. mLayoutStates.add(0, childState);
    314. // 为下一个视图移动 translationX 以腾出间隔
    315. translationX -= mIconSpacing;
    316. }
    317. // 显示 1 至 MAX_ICONS 个图标,或 (MAX_ICONS - 1) 个图标 + 溢出
    318. int totalVisible = mLayoutStates.size();
    319. int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
    320. mUnderflowStart = 0;
    321. int visible = 0;
    322. int firstUnderflowIndex = -1;
    323. for (int i = totalVisible - 1; i >= 0; i--) {
    324. StatusIconState state = mLayoutStates.get(i);
    325. // 如果在测量时发现需要溢出,则允许在此之前腾出空间
    326. if (mNeedsUnderflow && (state.xTranslation < (contentStart + mUnderflowWidth)) ||
    327. (mShouldRestrictIcons && visible >= maxVisible)) {
    328. firstUnderflowIndex = i;
    329. break;
    330. }
    331. mUnderflowStart = (int) Math.max(
    332. contentStart, state.xTranslation - mUnderflowWidth - mIconSpacing);
    333. visible++;
    334. }
    335. //开始判断是否超出 超出则画点
    336. if (firstUnderflowIndex != -1) {
    337. int totalDots = 0;
    338. int dotWidth = mStaticDotDiameter + mDotPadding;
    339. int dotOffset = mUnderflowStart + mUnderflowWidth - mIconDotFrameWidth;
    340. for (int i = firstUnderflowIndex; i >= 0; i--) {
    341. StatusIconState state = mLayoutStates.get(i);
    342. if (totalDots < MAX_DOTS) {
    343. state.xTranslation = dotOffset;
    344. state.visibleState = STATE_DOT;
    345. dotOffset -= dotWidth;
    346. totalDots++;
    347. } else {
    348. state.visibleState = STATE_HIDDEN;
    349. }
    350. }
    351. }
    352. // 从 NotificationIconContainer 中拿来的,不是最优解,但保持布局逻辑简洁
    353. if (isLayoutRtl()) {
    354. for (int i = 0; i < childCount; i++) {
    355. View child = getChildAt(i);
    356. StatusIconState state = getViewStateFromChild(child);
    357. state.xTranslation = width - state.xTranslation - child.getWidth();
    358. }
    359. }
    360. }
    361. private void applyIconStates() {
    362. for (int i = 0; i < getChildCount(); i++) {
    363. View child = getChildAt(i);
    364. StatusIconState vs = getViewStateFromChild(child);
    365. if (vs != null) {
    366. vs.applyToView(child);
    367. }
    368. }
    369. }
    370. private void resetViewStates() {
    371. for (int i = 0; i < getChildCount(); i++) {
    372. View child = getChildAt(i);
    373. StatusIconState vs = getViewStateFromChild(child);
    374. if (vs == null) {
    375. continue;
    376. }
    377. vs.initFrom(child);
    378. vs.alpha = 1.0f;
    379. vs.hidden = false;
    380. }
    381. }
    382. private static @Nullable StatusIconState getViewStateFromChild(View child) {
    383. return (StatusIconState) child.getTag(R.id.status_bar_view_state_tag);
    384. }
    385. private static int getViewTotalMeasuredWidth(View child) {
    386. return child.getMeasuredWidth() + child.getPaddingStart() + child.getPaddingEnd();
    387. }
    388. private static int getViewTotalWidth(View child) {
    389. return child.getWidth() + child.getPaddingStart() + child.getPaddingEnd();
    390. }
    391. public static class StatusIconState extends ViewState {
    392. /// StatusBarIconView.STATE_*
    393. public int visibleState = STATE_ICON;
    394. public boolean justAdded = true;
    395. // 从视图末尾的距离是最相关的,用于动画
    396. float distanceToViewEnd = -1;
    397. @Override
    398. public void applyToView(View view) {
    399. float parentWidth = 0;
    400. if (view.getParent() instanceof View) {
    401. parentWidth = ((View) view.getParent()).getWidth();
    402. }
    403. float currentDistanceToEnd = parentWidth - xTranslation;
    404. if (!(view instanceof StatusIconDisplayable)) {
    405. return;
    406. }
    407. StatusIconDisplayable icon = (StatusIconDisplayable) view;
    408. AnimationProperties animationProperties = null;
    409. boolean animateVisibility = true;
    410. // 用于计算哪些状态在布局过程中是可见的,找出哪些属性的状态转换(如果有的话)我们需要动画
    411. // 确定要动画的状态转换的属性(如果有)
    412. if (justAdded
    413. || icon.getVisibleState() == STATE_HIDDEN && visibleState == STATE_ICON) {
    414. // 图标正在出现,通过将其放在它将出现的位置并动画 alpha 来淡入它
    415. super.applyToView(view);
    416. view.setAlpha(0.f);
    417. icon.setVisibleState(STATE_HIDDEN);
    418. animationProperties = ADD_ICON_PROPERTIES;
    419. } else if (icon.getVisibleState() != visibleState) {
    420. if (icon.getVisibleState() == STATE_ICON && visibleState == STATE_HIDDEN) {
    421. // 消失,不要执行任何复杂的操作
    422. animateVisibility = false;
    423. } else {
    424. // 所有其他转换(到/从点等)
    425. animationProperties = ANIMATE_ALL_PROPERTIES;
    426. }
    427. } else if (visibleState != STATE_HIDDEN && distanceToViewEnd != currentDistanceToEnd) {
    428. // 可见性不在发生变化,只需动画位置
    429. animationProperties = X_ANIMATION_PROPERTIES;
    430. }
    431. icon.setVisibleState(visibleState, animateVisibility);
    432. if (animationProperties != null) {
    433. view.animate().cancel();
    434. icon.addTransformationToViewGroup(view, animationProperties, null);
    435. }
    436. icon.applyInShelfTransformation(view, this, animationProperties, animationProperties);
    437. }
    438. }
    439. }

  • 相关阅读:
    网安之python基础作业(2-3)
    linux中好玩的数据流定向和管道命令一
    Android 性能优化如何深入学习:启动、内存、崩溃优化一个都不能少
    Allegro差分自动添加回流地孔操作指导
    k8s 存储卷详解与动静部署详解
    微信小程序:全新独家云开发微群人脉
    Elasticsearch各个版本重要特性
    Android(Linux)常用的Shell指令
    SpringCloud-Sleuth服务追踪
    设计模式学习(三):工厂模式
  • 原文地址:https://blog.csdn.net/m0_59558544/article/details/134038994