您的位置:首页 > 产品设计 > UI/UE

[置顶] Android 6.0 Marshmallow 通知栏中QuickSetting分析

2016-05-29 18:12 465 查看
在SystemUI中,状态栏和通知栏都是在PhoneStatusBar的makeStatusBarView方法添加进来的,这里主要说说状态栏中的QuickSettingPanel

// ================================================================================
// Constructing the view
// ================================================================================
protected PhoneStatusBarView makeStatusBarView() {
final Context context = mContext;

Resources res = context.getResources();

updateDisplaySize(); // populates mDisplayMetrics
updateResources();

mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
....

mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
mStatusBarView.setBar(this);

PanelHolder holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder);
mStatusBarView.setPanelHolder(holder);

mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
R.id.notification_panel);
mNotificationPanel.setStatusBar(this);

// Set up the quick settings tile panel
mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
if (mQSPanel != null) {
final QSTileHost qsh = new QSTileHost(mContext, this,
mBluetoothController, mLocationController, mRotationLockController,
mNetworkController, mZenModeController, mHotspotController,
mCastController, mFlashlightController,
mUserSwitcherController, mKeyguardMonitor,
mSecurityController);
mQSPanel.setHost(qsh);
mQSPanel.setTiles(qsh.getTiles());
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mHeader.setQSPanel(mQSPanel);
qsh.setCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
mQSPanel.setTiles(qsh.getTiles());
}
});
}
....
return mStatusBarView;
}
下面主要说下图中QuickSetting是什么显示出来以及图中的每个控件功能是何如实现的



具体图标对应的id在这就不一一说明了,可以自己对着上面的图去看,下面主要分析下这个界面是怎么显示出来的,在PhoneStatusBar的makeStatusBarView方法中加载了super_status_bar.xml文件

mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
<com.android.systemui.statusbar.phone.StatusBarWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

....

<include layout="@layout/status_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height" />

<FrameLayout android:id="@+id/brightness_mirror"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:paddingLeft="@dimen/notification_side_padding"
android:paddingRight="@dimen/notification_side_padding"
android:visibility="invisible">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="2dp"
android:background="@drawable/brightness_mirror_background">
<include layout="@layout/quick_settings_brightness_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
</FrameLayout>

<com.android.systemui.statusbar.phone.PanelHolder
android:id="@+id/panel_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent" >
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</com.android.systemui.statusbar.phone.PanelHolder>

....

</com.android.systemui.statusbar.phone.StatusBarWindowView>
在上面这个布局文件中include了status_bar_expanded.xml,在这个文件中差不多就包含了图中所有控件,其中qs_panel.xml主要包括亮度调节框及以下的那一块,status_bar_expanded_header主要是亮度调节框以上部分
<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:id="@+id/notification_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
>
....

<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:id="@+id/notification_container_parent"
android:clipToPadding="false"
android:clipChildren="false">

<com.android.systemui.statusbar.phone.ObservableScrollView
android:id="@+id/scroll_view"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:scrollbars="none"
android:overScrollMode="never"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
layout="@layout/qs_panel"
android:layout_marginTop="@dimen/status_bar_header_height_expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/notification_side_padding"
android:layout_marginRight="@dimen/notification_side_padding"/>

<!-- A view to reserve space for the collapsed stack -->
<!-- Layout height: notification_min_height + bottom_stack_peek_amount -->
<View
android:id="@+id/reserve_notification_space"
android:layout_height="@dimen/min_stack_height"
android:layout_width="match_parent"
android:layout_marginTop="@dimen/notifications_top_padding" />

<View
android:layout_height="@dimen/notification_side_padding"
android:layout_width="match_parent" />
</LinearLayout>
</com.android.systemui.statusbar.phone.ObservableScrollView>

<com.android.systemui.statusbar.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:layout_marginBottom="@dimen/close_handle_underlap"
android:importantForAccessibility="no" />

<ViewStub
android:id="@+id/keyguard_user_switcher"
android:layout="@layout/keyguard_user_switcher"
android:layout_height="match_parent"
android:layout_width="match_parent" />

<include
layout="@layout/keyguard_status_bar"
android:visibility="invisible" />

</com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>

<include
layout="@layout/keyguard_bottom_area"
android:visibility="gone" />

<include layout="@layout/status_bar_expanded_header" />

....

</com.android.systemui.statusbar.phone.NotificationPanelView>

在这个布局中包含了status_bar_expanded_header.xml,下面具体看看status_bar_expanded_header这个布局文件,这个布局文件中主要包含了亮度调节框上面那一部分
<com.android.systemui.statusbar.phone.StatusBarHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/header"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="@dimen/status_bar_header_height"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:paddingStart="@dimen/notification_side_padding"
android:paddingEnd="@dimen/notification_side_padding"
android:baselineAligned="false"
android:elevation="4dp"
android:background="@drawable/notification_header_bg"
android:clickable="true"
android:focusable="true"
>

<com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
android:layout_width="@dimen/multi_user_switch_width_collapsed"
android:layout_height="@dimen/status_bar_header_height"
android:layout_alignParentEnd="true"
android:background="@drawable/ripple_drawable" >
<ImageView android:id="@+id/multi_user_avatar"
android:layout_width="@dimen/multi_user_avatar_expanded_size"
android:layout_height="@dimen/multi_user_avatar_expanded_size"
android:layout_gravity="center"
android:scaleType="centerInside"/>
</com.android.systemui.statusbar.phone.MultiUserSwitch>

<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/settings_button_container"
android:layout_width="48dp"
android:layout_height="@dimen/status_bar_header_height"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_toStartOf="@id/multi_user_switch">

<com.android.systemui.statusbar.phone.SettingsButton android:id="@+id/settings_button"
style="@android:style/Widget.Material.Button.Borderless"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ripple_drawable"
android:src="@drawable/ic_settings"
android:contentDescription="@string/accessibility_desc_settings" />
<com.android.systemui.statusbar.AlphaOptimizedImageView android:id="@+id/tuner_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="36dp"
android:tint="#4DFFFFFF"
android:tintMode="src_in"
android:visibility="invisible"
android:src="@drawable/tuner" />

</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>

<LinearLayout android:id="@+id/system_icons_super_container"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_bar_header_height"
android:layout_toStartOf="@id/multi_user_switch"
android:layout_alignWithParentIfMissing="true"
android:layout_marginStart="16dp"
android:background="@drawable/ripple_drawable"
android:paddingEnd="4dp" >
<FrameLayout android:id="@+id/system_icons_container"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_bar_height"
android:layout_gravity="center_vertical"
>
<include layout="@layout/system_icons" />
</FrameLayout>
<TextView android:id="@+id/battery_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/header_battery_margin_expanded"
android:paddingEnd="@dimen/battery_level_padding_end"
android:textColor="#ffffff"
android:textSize="@dimen/battery_level_text_size"
android:importantForAccessibility="noHideDescendants"/>
</LinearLayout>

<TextView
android:id="@+id/header_emergency_calls_only"
android:layout_height="@dimen/status_bar_header_height"
android:layout_width="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/system_icons_super_container"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
android:text="@*android:string/emergency_calls_only"
android:singleLine="true"
android:gravity="center_vertical" />

<FrameLayout
android:id="@+id/date_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/clock_collapsed_bottom_margin"
android:layout_alignParentBottom="true">
<com.android.systemui.statusbar.policy.DateView android:id="@+id/date_collapsed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
android:layout_below="@id/clock"
systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
/>

<com.android.systemui.statusbar.policy.DateView android:id="@+id/date_expanded"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
android:layout_below="@id/clock"
systemui:datePattern="eeeeMMMMd"
/>
</FrameLayout>

<include layout="@layout/split_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_above="@id/date_group"
android:id="@+id/clock"
/>

<com.android.systemui.statusbar.AlphaOptimizedButton android:id="@+id/alarm_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toEndOf="@id/date_group"
android:layout_marginBottom="4dp"
android:drawablePadding="6dp"
android:drawableStart="@drawable/ic_access_alarms_small"
android:textColor="#64ffffff"
android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
android:paddingEnd="6dp"
android:paddingStart="6dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="?android:attr/selectableItemBackground"
android:visibility="gone"
/>

<include
android:id="@+id/qs_detail_header"
layout="@layout/qs_detail_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
/>

<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/qs_detail_header_progress"
android:src="@drawable/indeterminate_anim"
android:alpha="0"
android:background="@color/qs_detail_progress_track"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
systemui:hasOverlappingRendering="false"
/>

<TextView
android:id="@+id/header_debug_info"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:fontFamily="sans-serif-condensed"
android:textSize="11dp"
android:textStyle="bold"
android:textColor="#00A040"
android:padding="2dp"
/>

</com.android.systemui.statusbar.phone.StatusBarHeaderView>
其余的控件差不多都在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:paddingTop="8dp"
android:paddingBottom="8dp"
android:elevation="2dp">

<com.android.systemui.qs.QSPanel
android:id="@+id/quick_settings_panel"
android:background="#0000"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.android.systemui.qs.QSContainer>
其中QSPanel是继承自ViewGroup的一个控件,里面包含了亮度调节框,以及wifi,蓝牙这些图标
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;

mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false);
mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content);
mDetailSettingsButton = (TextView) mDetail.findViewById(android.R.id.button2);
mDetailDoneButton = (TextView) mDetail.findViewById(android.R.id.button1);
updateDetailText();
mDetail.setVisibility(GONE);
mDetail.setClickable(true);
mBrightnessView = LayoutInflater.from(context).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
mFooter = new QSFooter(this, context);
addView(mDetail);
addView(mBrightnessView);
addView(mFooter.getView());
mClipper = new QSDetailClipper(mDetail);
updateResources();

mBrightnessController = new BrightnessController(getContext(),
(ImageView) findViewById(R.id.brightness_icon),
(ToggleSlider) findViewById(R.id.brightness_slider));
// Set up the quick settings tile panel
mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
if (mQSPanel != null) {
final QSTileHost qsh = new QSTileHost(mContext, this,
mBluetoothController, mLocationController, mRotationLockController,
mNetworkController, mZenModeController, mHotspotController,
mCastController, mFlashlightController,
mUserSwitcherController, mKeyguardMonitor,
mSecurityController);
mQSPanel.setHost(qsh);
mQSPanel.setTiles(qsh.getTiles());
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mHeader.setQSPanel(mQSPanel);
qsh.setCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
mQSPanel.setTiles(qsh.getTiles());
}
});
}
在QSTileHost.java中会去加载默认配置需要显示的图标
protected List<String> loadTileSpecs(String tileList) {
final Resources res = mContext.getResources();
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
if (tileList == null) {
tileList = res.getString(R.string.quick_settings_tiles);
if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
} else {
if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
}
final ArrayList<String> tiles = new ArrayList<String>();
boolean addedDefault = false;
for (String tile : tileList.split(",")) {
tile = tile.trim();
if (tile.isEmpty()) continue;
if (tile.equals("default")) {
if (!addedDefault) {
tiles.addAll(Arrays.asList(defaultTileList.split(",")));
addedDefault = true;
}
} else {
tiles.add(tile);
}
}
return tiles;
}
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
wifi,bt,inversion,dnd,cell,airplane,rotation,flashlight,location,cast,hotspot
</string>
只有配置了才可能会显示wifi,bt等这些图标,这些图标都对应着一个实体类,这些类都继承自QSTile<QSTile.BooleanState>,实际上是否显示都有这些实体类来决定的。比如flashlight对应着FlashlightTile.java,在handleUpdateState中会实时更新控件的状态,会根据mFlashlightController.isAvailable()的值,来决定是否显示闪光灯
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.visible = mFlashlightController.isAvailable();
state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
if (arg instanceof UserBoolean) {
boolean value = ((UserBoolean) arg).value;
if (value == state.value) {
return;
}
state.value = value;
} else {
state.value = mFlashlightController.isEnabled();
}
final AnimationIcon icon = state.value ? mEnable : mDisable;
icon.setAllowAnimation(arg instanceof UserBoolean && ((UserBoolean) arg).userInitiated);
state.icon = icon;
int onOrOffId = state.value
? R.string.accessibility_quick_settings_flashlight_on
: R.string.accessibility_quick_settings_flashlight_off;
state.contentDescription = mContext.getString(onOrOffId);
}
其中反色和热点两个图标会根据是不是最近使用过mUsageTracker.isRecentlyUsed(),来动态显示或隐藏
HotspotTile.java
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed();
state.label = mContext.getString(R.string.quick_settings_hotspot_label);

if (arg instanceof Boolean) {
state.value = (boolean) arg;
} else {
state.value = mController.isHotspotEnabled();
}
state.icon = state.visible && state.value ? mEnable : mDisable;
}
ColorInversionTile.java
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
state.visible = enabled || mUsageTracker.isRecentlyUsed();
state.value = enabled;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
state.icon = enabled ? mEnable : mDisable;
}
其它几个控件就不在详细说明了,可以对着第一张图看。另外提一点就是自己最近遇到的问题,亮度条不能拖动调节,只能点击调节。如果遇到通知栏里的控件不能click或move事件没有效果,可以在NotificationPanelView.java和PanelView.java的onInterceptTouchEvent和onTouchEvent中添加log,
看是不是有对应event事件被拦截了,导致控件本身的event事件没有响应到。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: