SystemUI下的快速設定面板顯示異常
前言:堅持自己能堅持的,成為自己想成為的,相信時間的累積是有效果的。
Step1復現
這個bug困擾了快一週了,今天終於突然來了靈感解決掉了,記錄之。
測試:
General description:
The notification bar display incorrect when edit quick settings.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Reproducibility:
10/10
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Precondition:
Turn on auto-rotate
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Step:
1.Homescreen->Settings->Display->Rotate dut to landscape mode->View->Display size->Change size then change to default size->Back to home screen-> Rotate dut to portrait mode->Pull down notification bar->EDIT
2.Check the screen display.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Actual result:
The notification bar display incorrect when edit quick settings.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Expect result:
The notification bar display correct when edit quick settings.
效果圖如下:
Step2知識回顧
**A、橫豎屏:**
通過這個bug的復現流程,橫屏模式下設定 displaySize ,嗯,涉及到android橫豎屏切換方面的知識,這方面的知識很薄弱,於是一邊解bug一邊網上查資料。感謝roserose0002,Cynthia&Sky二位的精彩分析,下面我結合他們的部落格做個自己的理解和學習。
當我們在清單檔案中不新增關於橫豎屏的配置資訊時,橫豎屏切換時會有一個Activity的銷燬和重新建立的過程,一般的專案都會通過在清單檔案中配置相關的資訊通過執行onConfigureChanged方法來做一些處理。
即:
<uses-permission
Android:name="android.permission.CHANGE_CONFIGURATION"></uses-permission>
允許改變配置資訊,系統允許我們通過重寫activity中的onConfigurationChanged方法來捕獲和修改某些配置資訊。
另外在我們想配置的相關的Activity需要新增一個節點
<activity android:name=".recents.RecentsActivity"
android:label="@string/accessibility_desc_recent_apps"
android:exported="false"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
android:screenOrientation="behind"
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:theme="@style/RecentsTheme.Wallpaper">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
</intent-filter>
</activity>
它規定了可以再程式中捕獲的事件型別。實際上它有這些欄位
修改字型時會呼叫fontScale,修改display size會呼叫densityDpi。
B、頁面和原始碼的對應(MTK)
這個不是什麼乾貨,相當於自己加深印象了,
a、在SystemUI下的qs資料夾下,QSPanel.java(View that represents the quick settings tile panel)
/** View that represents the quick settings tile panel. **/
public class QSPanel extends LinearLayout implements Tunable, Callback {
......
public QSPanel(Context context) {
this(context, null);
}
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setOrientation(VERTICAL);
mBrightnessView = LayoutInflater.from(context).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
addView(mBrightnessView);
mQuickSettingsPlugin = PluginManager.getQuickSettingsPlugin(mContext);
mQuickSettingsPlugin.addOpViews(this);
setupTileLayout();
mFooter = new QSFooter(this, context);
addView(mFooter.getView());
updateResources();
mBrightnessController = new BrightnessController(getContext(),
(ImageView) findViewById(R.id.brightness_icon),
(ToggleSlider) findViewById(R.id.brightness_slider));
}
......
}
再看看哪裡呼叫了這個控制元件,在Qs_panel.xml
<com.android.systemui.qs.QSContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_settings_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/qs_background_primary"
android:clipToPadding="false"
android:clipChildren="false"
android:elevation="4dp">
<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:background="#0000"
android:layout_marginTop="@dimen/status_bar_header_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp" />
<include layout="@layout/quick_status_bar_expanded_header" />
<include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
<include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
android:visibility="gone" />
</com.android.systemui.qs.QSContainer>
QSPanel屬於QSContainer的子控制元件。效果圖是這樣的:
QSPanel,點選edit
mEditText = (TextView) findViewById(android.R.id.edit);
mEditText.setOnClickListener(view ->
mHost.startRunnableDismissingKeyguard(() -> showEdit(view)));
private void showEdit(final View v) {
v.post(new Runnable() {
@Override
public void run() {
if (mCustomizePanel != null) {
if (!mCustomizePanel.isCustomizing()) {
int[] loc = new int[2];
v.getLocationInWindow(loc);
int x = loc[0];
int y = loc[1];
mCustomizePanel.show(x, y);
}
}
}
});
}
執行mCustomizePanel.show(x, y);方法,這個類的描述是
/**
* Allows full-screen customization of QS, through show() and hide().
*
* This adds itself to the status bar window, so it can appear on top of quick settings and
* *someday* do fancy animations to get into/out of it.
show 或者 hide詳情,是不是這個類的configuration變化,導致quicksetting顯示異常
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
View navBackdrop = findViewById(R.id.nav_bar_background);
if (navBackdrop != null) {
boolean shouldShow = newConfig.smallestScreenWidthDp >= 600
|| newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
navBackdrop.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
}
}
事實上QSCustomizer.java的這個方法比較簡單,就是在實現父類的同時加上了自己navBackdrop 檢視。
在這裡裡面我嘗試reloadwitdth是沒用的
private void reloadWidth(View view) {
LayoutParams params = (LayoutParams) view.getLayoutParams();
params.width = getContext().getResources().getDimensionPixelSize(
R.dimen.notification_panel_width);
view.setLayoutParams(params);
}
Step3 找源頭
偶然發現了這個類
/**
* Custom {@link FrameLayout} that re-inflates when changes to {@link Configuration} happen.
* Currently supports changes to density and locale.
*/
public class AutoReinflateContainer extends FrameLayout {
......
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
boolean shouldInflateLayout = false;
final int density = newConfig.densityDpi;
if (density != mDensity) {
mDensity = density;
shouldInflateLayout = true;
}
final LocaleList localeList = newConfig.getLocales();
if (localeList != mLocaleList) {
mLocaleList = localeList;
shouldInflateLayout = true;
}
if (shouldInflateLayout) {
inflateLayout();
}
}
private void inflateLayout() {
removeAllViews();
LayoutInflater.from(getContext()).inflate(mLayout, this);
final int N = mInflateListeners.size();
for (int i = 0; i < N; i++) {
mInflateListeners.get(i).onInflated(getChildAt(0));
}
}
......
}
當density 和locale變化時會重新載入view,所以我強行的去掉if,只要配置資訊變化就加在view,這個是治標不治本的。
// if (shouldInflateLayout) {
inflateLayout();
// }
至於為什麼橫屏切換時導致計算錯誤還要進一步找原因。
最終的圖是這樣的
謝謝。