【移動應(yīng)用開發(fā)技術(shù)】Android8.1平臺SystemUI導(dǎo)航欄加載的示例分析_第1頁
【移動應(yīng)用開發(fā)技術(shù)】Android8.1平臺SystemUI導(dǎo)航欄加載的示例分析_第2頁
【移動應(yīng)用開發(fā)技術(shù)】Android8.1平臺SystemUI導(dǎo)航欄加載的示例分析_第3頁
【移動應(yīng)用開發(fā)技術(shù)】Android8.1平臺SystemUI導(dǎo)航欄加載的示例分析_第4頁
【移動應(yīng)用開發(fā)技術(shù)】Android8.1平臺SystemUI導(dǎo)航欄加載的示例分析_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論