




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
【移動應(yīng)用開發(fā)技術(shù)】Android8.1平臺SystemUI導(dǎo)航欄加載的示例分析
在下給大家分享一下Android8.1平臺SystemUI導(dǎo)航欄加載的示例分析,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!需求基于MTK81638.1平臺定制導(dǎo)航欄部分,在左邊增加音量減,右邊增加音量加思路需求開始做之前,一定要研讀SystemUINavigation模塊的代碼流程?。。〔灰苯尤ゾW(wǎng)上copy別人改的需求代碼,盲改的話很容易出現(xiàn)問題,然而無從解決。網(wǎng)上有老平臺(8.0-)的講解SystemUI的導(dǎo)航欄模塊的博客,自行搜索。8.0對SystemUI還是做了不少細(xì)節(jié)上的改動,代碼改動體現(xiàn)上也比較多,但是總體基本流程并沒變。源碼閱讀可以沿著一條線索去跟代碼,不要過分在乎代碼細(xì)節(jié)!例如我客制化這個需求,可以跟著導(dǎo)航欄的返回(back),桌面(home),最近任務(wù)(recent)中的一個功能跟代碼流程,大體知道比如recen這個view是哪個方法調(diào)哪個方法最終加載出來,加載的關(guān)鍵代碼在哪,點擊事件怎么生成,而不在意里面的具體邏輯判斷等等。代碼流程1.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;從狀態(tài)欄入口開始看。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();//創(chuàng)建導(dǎo)航欄
}
}
catch
(RemoteException
ex)
{
}
}2.進(jìn)入createNavigationBar方法,發(fā)現(xiàn)主要是用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了導(dǎo)航欄的布局,最終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,發(fā)現(xiàn)根布局是自定義的view類NavigationBarFrame.(其實SystemUI以及其他系統(tǒng)應(yīng)用如Launcher,都是這種自定義view的方式,好多邏輯處理也都是在自定義view里,不能忽略)<com.android.systemui.statusbar.phone.NavigationBarFrame
xmlns:android="/apk/res/android"
xmlns:systemui="/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;我們進(jìn)入NavigationBarFrame類。發(fā)現(xiàn)類里并不是我們的預(yù)期,就是一個FrameLayout,對DeadZone功能下的touch事件做了手腳,不管了。6.再回來看看NavigationBarFragment的生命周期呢。onCreateView()里,導(dǎo)航欄的真正的rootView。@Override
public
View
onCreateView(LayoutInflater
inflater,
@Nullable
ViewGroup
container,
Bundle
savedInstanceState)
{
return
inflater.inflate(R.layout.navigation_bar,
container,
false);
}進(jìn)入導(dǎo)航欄的真正根布局:navigation_bar.xml,好吧又是自定義view,NavigationBarView和NavigationBarInflaterView都要仔細(xì)研讀。<com.android.systemui.statusbar.phone.NavigationBarView
xmlns:android="/apk/res/android"
xmlns:systemui="/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先看構(gòu)造方法,因為加載xml布局首先走的是初始化public
NavigationBarInflaterView(Context
context,
AttributeSet
attrs)
{
super(context,
attrs);
createInflaters();//根據(jù)屏幕旋轉(zhuǎn)角度創(chuàng)建子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之后都會回調(diào)。@Override
protected
void
onFinishInflate()
{
super.onFinishInflate();
inflateChildren();//進(jìn)去看無關(guān)緊要
忽略
clearViews();//進(jìn)去看無關(guān)緊要
忽略
inflateLayout(getDefaultLayout());//關(guān)鍵方法:加載了
back.home.recent三個按鈕的layout
}看inflateLayout():里面的newLayout參數(shù)很重要?。。「鶕?jù)上一個方法看到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);//根據(jù)“;”號分割成長度為3的數(shù)組
String[]
start
=
sets[0].split(BUTTON_SEPARATOR);//根據(jù)“,”號分割,包含
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);//創(chuàng)建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,肯定是在代碼里設(shè)置了
//這里也是自定義view:KeyButtonView
<com.android.systemui.statusbar.policy.KeyButtonView
xmlns:android="/apk/res/android"
xmlns:systemui="/apk/res-auto"
android:id="@+id/home"
android:layout_width="@dimen/navigation_key_width"http://引用了dimens.xml里的navigation_key_width
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="3"http://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的構(gòu)造方法:我們之前xml的systemui:keyCode=”3”方法在這里獲取。再來看Touch事件,通過sendEvent()方法可以看出,back等view的點擊touch事件不是自己處理的,而是交由系統(tǒng)以實體按鍵(keycode)的形式處理的.當(dāng)然KeyButtonView類還處理了支持長按的button,按鍵的響聲等,這里忽略。至此,導(dǎo)航欄按鍵事件我們梳理完畢。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);//關(guān)鍵方法
}
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;
//這里根據(jù)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.還遺留一個問題:設(shè)置圖片的icon到底在哪?我們之前一直閱讀的是NavigationBarInflaterView,根據(jù)布局我們還有一個類沒有看,NavigationBarView.javaSystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java;進(jìn)入NavigationBarView類里,找到構(gòu)造方法。public
NavigationBarView(Context
context,
AttributeSet
attrs)
{
super(context,
attrs);
mDisplay
=
((WindowManager)
context.getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay();
...
...
updateIcons(context,
Configuration.EMPTY,
mConfiguration);//關(guān)鍵方法
mBarTransitions
=
new
NavigationBarTransitions(this);
//mButtonDispatchers
是維護(hù)這些home
back
recent圖標(biāo)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的資源,再看誰調(diào)用了了mRecentIcon就可知道,即反推看調(diào)用流程。private
void
updateRecentsIcon()
{
getRecentsButton().setImageDrawable(mDockedStackExists
?
mDockedIcon
:
mRecentIcon);
mBarTransitions.reapplyDarkIntensity();
}up
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 北海市初中數(shù)學(xué)試卷
- 豆類項目風(fēng)險識別與評估綜合報告
- 邊坡錨桿錨索腰梁施工方案
- 浙江油田油管清洗施工方案
- 房屋地面鋪裝工程施工方案
- 三水裝配式檢查井施工方案
- “油茶+N”混交造林模式的技術(shù)創(chuàng)新與應(yīng)用實踐的效益詳述
- 智能制造與供應(yīng)鏈管理的策略及實施路徑
- 數(shù)字化改造的必要性與挑戰(zhàn)
- 變電站巡檢的重要性
- 國家級自然保護(hù)區(qū)不可避讓論證報告-概述說明以及解釋
- 新教材統(tǒng)編版高中語文古代詩歌閱讀講與練 22 從七大常見題材入手把握詩歌內(nèi)容情感
- 2024-2025學(xué)年天津市和平區(qū)天津一中高三綜合測試英語試題試卷含解析
- 2024-2030年中國地鐵廣告行業(yè)市場現(xiàn)狀供需分析及投資評估規(guī)劃分析研究報告
- 高等職業(yè)學(xué)校人工智能技術(shù)應(yīng)用專業(yè)實訓(xùn)教學(xué)條件建設(shè)標(biāo)準(zhǔn)
- 2024年水利安全員(B證)考試題庫-上(單選題)
- 2025年高考生物總復(fù)習(xí):減數(shù)分裂和受精作用
- 輻射防護(hù)試題庫+答案
- DWI高信號常見疾病的鑒別診斷課件-2
- 酸堿滴定分析與討論實驗報告
- 2024醫(yī)療器械運輸合同范本
評論
0/150
提交評論