A. android 沉浸式/透明式狀態欄、導航欄
Android 從4.4開始引進透明狀態欄和導航欄的概念,並且在5.0進行了改進,將透明變成了半透明的效果。雖然此特性最早出現在ios,但不否認效果還是很贊的。
至於4.4以下的手機,就不要考慮此特性了,好在4.4以下的手機份額已經非常小了。
我們先來看一下透明狀態欄的實現,兩種常見效果圖如下:
虛擬導航欄並不是所有的手機都有,華為的手機多比較常見,就是上圖屏幕底部按鈕那塊區域。設置導航欄和狀態欄類似:
這是官方的解釋,大致意思就是我們在布局的最外層設置 android:fitsSystemWindows="true",會在屏幕最上方預留出狀態欄高度的padding。
由於fitsSystemWindows屬性本質上是給當前控制項設置了一個padding,所以我們設置到根布局的話,會導致狀態欄是透明的,並且和窗口背景一樣。
但是多數情況,我們並不在根布局設置這個屬性,我們想要的無外乎是讓內容沉浸在狀態欄之中。所以我們經常設置在最上端的圖片背景、Banner之類的,如果是Toolbar的,我們可以使用一層LinearLayout包裹,並把這個屬性設置給LinearLayout,這樣就可以避免Toolbar的內容下沉了。如:
上述方法可以解決普通頁面的透明式狀態欄需求,如有復雜需求可以參考下面這些:
Android 系統狀態欄沉浸式/透明化完整解決方案
Android 沉浸式狀態欄的實現
Android沉浸式狀態欄(透明狀態欄)最佳實現
還有開源庫推薦: ImmersionBar
B. 如何使用Android ConstraintLayout
ConstraintLayout最低兼容Android 2.3;
目前Android Studio 2.3默認使用ConstraintLayout作為布局文件的根布局;
想要使用ConstraintLayout,需在項目的build.gradle添加com.android.support.constraint:constraint-layout:XXX版本號依賴;
C. 關於Android布局你不知道的
Android常見的5個布局,我想大家一定不會陌生。LinearLayout、RelativeLayout和FrameLayout也是使用頻率較高的布局方式,做Android開發的一定使用過。
傳統的5種布局方式:
不過我的問題並不是問面試者如何使用這些基礎的布局,而是要看面試者怎麼解決布局嵌套(影響性能)和屏幕適配問題。
我們都清楚Android界面的布局太復雜,嵌套層次過深,會使整個界面的測量、布局和繪制變得更復雜,對性能會造成影響。所以我們在寫Layout文件時,也要盡量避免布局的嵌套層次過深的問題。
在怎麼解決問題之前,我們得有一個好方法先判斷當前的問題情況。Android SDK工具箱中有一個叫做Hierarchy Viewer的工具,能夠在App運行時分析Layout。
注意: 在ROOT的手機,或者是安裝開發版的ROM的手機可以直接使用Hierarchy Viewer。如果沒有Root的手機(SDK 4.1及以上),需要在你的PC端添加一個環境變數「ANDROID_HVPROTO=ddm」。
下面列舉一些面試者常使用的方式。
merge merge標簽的作用是合並UI布局,使用該標簽能降低UI布局的嵌套層次。
merge標簽可用於兩種情況:
ViewStub ViewStub標簽引入的布局默認不會inflate,既不會顯示也不會佔用位置。 ViewStub常用來引入那些默認不會顯示,只在特殊情況下顯示的布局,如數據載入進度布局、出錯提示布局等。
需要在使用時手動inflate:
ViewStub在一定的程度可以起到減少嵌套層次的作用,特別是很多時候我們的程序可能不需要走到ViewStub的界面。
include 將可復用的組件抽取出來並通過include標簽使用,但<include>標簽能減少布局的層次嗎?
我認為不能。include主要解決的是相同布局的復用問題,它並不能減少布局的層次。
用RelativeLayout代替LinearLayout
很多人為了減少布局層次喜歡用RelativeLayout代替LinearLayout,不過可能達到的效果並不會很明顯。層次是減少了,但本身RelativeLayout就會比LinearLayout性能差一點。
有一些界面,比如一個圖片和一個文本的布局(ListItem常見的布局方式),可以利用TextView有drawableLeft, drawableRight等屬性,完全不需要RelativeLayout或者LinearLayout布局。
傳統的布局方式存在一定的缺陷,如RelativeLayout要兩次測量(measure)它的子View才能知道確切的高度;如果LinearLayout布局的子View有設置了layout_weight,那麼它也需要測量兩次才能獲得布局的高度。
相對於傳統的布局方式,Android官方還推出了兩種新的布局方式:ConstraintLayout和FlexboxLayout。
ConstraintLayout ConstraintLayout即約束布局,在2016年由Google I/O推出。ConstraintLayout和RelativeLayout有點類似,控制項之間根據依賴關系而存在,但比RelativeLayout更加靈活。創建大型復雜的布局仍然可以使用扁平的層級(不用嵌套View Group),說的簡單些就是,再復雜的界面也可以只有2層層次。
要使用ConstraintLayout需要在build.gradle中添加相關的support庫:
使用ConstraintLayout可以有效的解決布局嵌套過多導致的性能問題,官方也對其渲染性能進行了優化,並且ConstraintLayout支持可視化的方式編寫布局。
不過學會熟練使用ConstraintLayout會需要一點時間,但這是值得的。
FlexBoxLayout 做過前端開發(CSS方面)的同學對FlexBox一定不會陌生,最近我在做微信小程序開發時也涉及到FlexBox。FlexBox(彈性布局)是w3c在2009年提出的一種新的布局方案,解決以前那種傳統css的盒模型的局限性。
Google開源了FlexboxLayout布局和前端CSS FlexBox布局具有相同的功能(肯定有不一樣的地方),但已經足夠在Android上改進布局的構建方式。
FlexBoxLayout可以理解成一種更高級的LinearLayout,不過比LinearLayout更加強大和靈活。如果我們使用LinearLayout布局的話,那麼不同的解析度,也許我們要重新調整布局,勢必會需要跟多的布局文件放在不同的資源目錄。而使用FlexBoxLayout來布局的話,它可以適應各種界面的改變(所以叫響應式布局)。
如果對前端的Flexbox不太了解的話,你還需要補一些概念,好在這些東西在網上很容易找到。
可能很多讀者會覺這樣的面試題是吹毛求疵,很多項目中哪有這么復雜的界面,根本就用不到這些優化措施。
可以說厲害的人,或者叫高手,可能只是比較多在意這些細節而已。在實踐中的經歷告訴我,很多難於解決的性能問題,並不是因為有一個影響性能的問題無法攻克,而是沒有一個明顯的制約因素,是有各種小問題一點一點堆積起來,最終積重難返。
所以,把細節做好,或者意識到細節的地方可能引發的問題,對我們解決問題是很有幫助的,不要浪費了讓你可以成長的細節。
有需要更多Android高級進階和面試資料的朋友可以私信我獲取
D. 安卓視圖層級大揭秘
最近接了一個語音控制的功能,UI上的具體實現就是在應用上遮蓋一個透明防觸層,在語音狀態下阻止用戶點擊,但不能影響物理返回鍵的Dialog呼出即控制,同時對於非物理返回鍵呼出的Dialog也要阻止操作。功能看起來很繞,我們用一張圖片來具體說明一下。
通過圖片不難看出,我們要實現的語音控制層其實是介於應用視圖與視圖內部提示框之上,同時又在Back返回鍵彈窗之下的一個層級。因為一直以來對安卓視圖層級的探究不是很深入,所以借著做這個功能對安卓視圖層級這一塊的知識進行了一下總結梳理。
首先讓我們通過一張層級圖來明確幾個重要的概念Window,DecorView和mContentParent。
在Android中不管是Activity、Toast、ActionBar還是Dialog,他們的視圖都是附加到Window上,其實基本上所有的view同時通過Window來呈現的,因此Window可以理解為是view的承載者和管理者。Window 有三種類型,分別是應用 Window、子 Window 和系統 Window。應用類 Window 對應一個 Acitivity,子 Window 不能單獨存在,需要依附在特定的父 Window 中,比如常見的一些 Dialog 就是一個子 Window。系統 Window是需要聲明許可權才能創建的 Window,比如 Toast 和系統狀態欄都是系統 Window。
DecorView是Windows中的View的最頂層View。其實DecorView是FrameLayout的子類,它裡麵包含了一個存有ActionBar以及mContentParent的LinearLayout。
mContentParent這個名字可能會有些陌生,其實他就是我們經常使用的應用根布局,即android.R.id.content。Activity中的setContentView其實就是通過LayoutInflater將XML布局轉換成View並添加到mContentParent中。
每個Activity都會持有一個Window,而在安卓中,Window只有唯一的一個實現類PhoneWindow ,所以每個Activity都會持有一個PhoneWindow,在PhoneWindow中會持有頂層視圖DecorView。那麼Activity是怎麼建立與PhoneWindow的聯系的呢,讓我們通過源碼來探究一下:
在Activity的啟動過程中會執行ActivityThread的performLaunchActivity方法,其中調用Activity的attach。在attach()方法中實例化Activity持有的mWindow。由於 Activity 實現了 Window 的 Callback 介面,因此當 Window 接受到外界的狀態改變時就會回調 Activity 的方法。
可以看到,在PhoneWindow裡面,出現了成員變數DecorView。而這里,DecorView則是PhoneWindow裡面的一個內部類,它是繼承於FrameLayout。
這是我們每次寫Activity都會調用的setContentView方法,它的內部調用了getWindow()的setContentView,這個mWindow就是PhoneWindow。
我們看到在PhoneWindow中有三個setContentView的重載方法。在setContentView(int layoutResID)中,首先判斷了mContentParent ,如果mContentParent 為空即為第一次調用的時候,就執行installDecor()方法,創建DecorView,並添加到mContentParent上。如果mContentParent不為空,那麼將mContentParent中的view移除。接著通過mLayoutInflater將XML轉換為View樹,並且添加至mContentParent視圖中。 添加完成後回調通知onContentChanged,表示完成界面載入。
首先判斷mDecor是否為空,如果為空則通過generateDecor創建一個DecorView,緊接著設置DecorView的獲取焦點能力為FOCUS_AFTER_DESCENDANTS,即先分發給Child View進行處理,如果所有的Child View都沒有處理,則自己再處理。第一次DecorView未載入到mContentParent,所以mContentParent為空,調用generateLayout將setContentView內容添加到mContentParent。
定製過Acitivity的Actionbar或是Fullscreen的同學一定都知道,requesetFeature方法需要在setContentView之前調用,這就是原因。setContentView的實質顯示是觸發了Activity的resume狀態,也就是觸發了makeVisible方法。
這里將getWindow().getAttributes()作為了LayoutParams,在WindowManager中:
可以看到Activity的窗口類型是TYPE_APPLICATION,這個TYPE類型決定了在Window層的顯示層級,TYPE類型總覽如下:
Dialog不屬於View,他是應用的子window,所以這也是為什麼我們通過給mContentParent添加view無法實現遮擋Dialog的原因。Dialog 中 Window 同樣是通過 PolicyManager 的 makeNewWindow 方法來完成的,普通的 Dialog 必須採用 Activity 的 Context,如果採用 Application 的 Context 就會報錯。這是因為沒有應用 token 導致的,而應用 token 一般只有 Activity 擁有。常規Dialog的TYPE為TYPE_APPLICATION_ATTACHED_DIALOG,通過不同的TYPE層級劃分我們可以找到置於常規Dialog之上的WindowManager LayoutParams 屬性,例如TYPE_SYSTEM_ALERT與TYPE_TOAST,設置了這兩個屬性的布局是可以將常規Dialog完全遮蓋的。他們的區別在於一個是系統級別的Dialog一個是Toast,系統Dialog需要申請許可權。所以我們的第一個方案就是可遮擋的Dialog使用常規Dialog,語音提示框採用TYPE_SYSTEM_ALERT。但是都知道安卓有一個無法逃避的問題,就是廠商定製,在MUI的framework層,出於對「安全」的考慮,默認為用戶關閉了懸浮窗許可權,也就是是說設置了TYPE_SYSTEM_ALERT屬性的視圖默認是無法顯示的,需要用戶手動開啟許可權以後方可顯示。
雖然可以在用戶啟動的時候根據用戶機型選擇跳轉開啟許可權頁,但作為一個有情懷的開發這種不完美的體驗還是不能接受的。根據之前對安卓視圖層級的學習,我們有了第二套方案。應用視圖是存放於mContentParent他與Activity同屬TYPE_APPLICATION Window層級屬於最下層,常規Dialog的層級是TYPE_APPLICATION_ATTACHED_DIALOG,所以我們將常規Dialog作為最上層不可遮擋的提示框,下面只需考慮可遮擋的彈窗與語音控制兩層即可。因為語音控制層需要能夠遮擋提示彈窗,所以需要語音控制層在彈窗的上層,經過之前的學習,我們把彈窗加入到mContentParent,把語音控制層添加到DecorView層即可完美的解決問題。mContentParent為一個FrameLayout,應用視圖通過sentContentView率先添加到mContentParent中,作為提示彈窗,添加順序一定相對應用視圖置後,所以當提示彈窗再次向mContentParent添加的時候,即會添加到應用視圖之上。而DecorView是mContentParent的父容器,也是一個FrameLayout,添加語音提示框的時候mContentParent一定已經存在,所以添加的時候一定會在mContentParent之上。
就這樣,一個看似復雜的需求通過對安卓源碼的探究完美的解決了,很多時候當我們遇到難以解決的問題,不妨試試回到問題的原點,思考一下問題的本質,很多時候都會有不一樣的發現。
E. 關於android的購物車功能是怎麼實現的
1.頁面布局根布局用相對布局,其中有兩個子布局,有一個子布局null_layout來放空數據時需要展示的頁面visibility設為gone,另一個子布局就是你有數據顯示的樣子,請求伺服器購物車或者本地資料庫查詢時,若無則將null_layout的visibility設為visible,有則又設成gone就行2.這個邏輯不對啊,商品列表點一下不是應該去商品詳情,然後用戶自己再選擇加入購物車並選數量么,怎麼就直接加入購物車了...一般做購物車都會做本地資料庫,加入購物車按鈕被點擊就向本地插入一條數據到資料庫並發請求告訴後台也同步,商品數量是用戶選的,價格是自己算的(單價*數量),購物車顯示時查這個表就行
F. Android 中 怎麼動態修改布局xml文件中的根屬性值 比如如下圖的兩個屬性值 fitsSystemWindows 改為 false
給這個RelativeLayout設置一個id,比如 android:id="@+id/rl"
然後在class文件中獲取此控制項,再設置屬性即可
java">RelativeLayoutrelativeLayout=(RelativeLayout)findViewById(R.id.rl);
relativeLayout.setFitsSystemWindows(false);