自從個人Nexus 6加入了Android beta計劃以來,我便在很早的時候就體驗上了Android Nougat的一些新特性,天然也體驗到了比較重要的Multi-Window
新特性。不過,在當前Android Nougat普及度特別低(尤爲是個人Nexus 6尚未收到官方ota)的狀況下,Multi-Window
的體驗也大打了很多折扣。html
根據民間的說法,Multi-Window
能夠同時在屏幕上打開兩個應用,你能夠在用其中一個App的同時看到另外一個App的內容。想象一下,你正在玩遊戲或者看電影,這時你可能同時正在和朋友聊天,這在以往,你可能須要整屏切來切去,如今,你能夠在同一個屏幕上完成這兩件事情,並且互不耽誤。再想象一下,你正在和朋友探討知識,忽然遇到了一個你講不清楚的原理,這時你開了一個分屏打開了Google,這邊Google一下答案,複製一下,而後立馬在那邊繼續大家的探討,這大大加快了你獲取知識的效率,同時也爲裝逼提供了可能。java
通過初步的探索,我發現Multi-Window
的分屏並非以App爲基本單位,確切地說,是以Activity
做爲基本單位。這就意味着,同一個App的不一樣的Activity,也能夠共享屏幕。再精確地說,咱們能夠開發出一款App,這個App能夠打開兩個窗口分享整個屏幕,每一個窗口加載一個不一樣的Activity。想象一下,若是有一個郵件App,能夠左邊窗口查看收件箱,右邊窗口寫新郵件,這可能會方便不少。再想象一下,若是有一個象棋遊戲,你能夠左邊窗口扮演玩家A,右邊窗口扮演玩家B,本身和本身下棋。哦剩下的腦洞交給大家了。android
其實當我首次看到Multi-Window
的介紹時,我不認爲這個特性會帶來多少方便,由於我感受手機屏幕原本就比較小,再分屏就有點施展不開了;這個特性相對地對於平板更好一些。直到我看到Android裏面還有一個叫作Drag and Drop
的東西,這讓我感受Multi-Window
能多多少少施展一些做用了。git
Drag and Drop
讓咱們能夠把數據從一個View拖到另外一個View。比方說,咱們有兩個EditText,其中在第一個EditText輸入了某項內容,而後咱們用手指從第一個EditText拖動到第二個EditText,而後第二個EditText就自動填充了第一個EditText的內容,是否是很好玩?這裏傳遞的數據能夠是任何咱們須要的數據,而View是任何咱們能夠操做的控件,這就爲咱們的交互設計提供了更多的想象空間。更重要的,Drag and Drop
很好地支持了Multi-Window
,這爲跨窗口數據分享提供了可能。github
這裏我實現了這樣一個App:這個App啓動了兩個Activity進入分屏模式,第一個Activity有一個輸入URL的EditText,第二個Activity有一個WebView。咱們在第一個Activity中輸入了URL以後,用手從EditText跨窗口拖動到第二個Activity的WebView上,就直接在WebView中打開咱們填寫的URL。canvas
下面咱們來一步一步來探索一下這些具體是怎麼完成的。這個工程我放在了Github-MultiWindowGiraffe,以供參考。app
在Android Nougat上進入Multi-Window
模式的方法能夠參照Multi-Window Support上的說明,這裏只從開發層面加以描述。ide
當App進入Multi-Window
模式時,系統會向Activity發送一個configuration change的通知,其對Activity生命週期的影響和屏幕旋轉是同樣的。當處於Multi-Window
模式時,咱們能夠看到兩個Activity分享屏幕,其中用戶正在操做的Activity處於Active
狀態,另外一個處於Paused
狀態。用戶的操做從其中一個Activity轉到另外一個Activity時,會調用原來Activity的onPause()
方法,同時會觸發新操做的Activity的onResume()
方法。ui
所以,這裏出現了一個處於Paused
狀態但同時又對用戶可見的Activity。咱們知道,當Activity對用戶不可見時,會調用生命週期的onPause()
和onStop()
方法,咱們能夠在這兩個方法裏面作一些相似中止視頻播放的操做。可是在這裏,當分屏的兩個Activity之間切換時,只會觸發onPause()
方法,若是咱們想要用戶在另外一個窗口操做的時候視頻扔繼續播放,就只能將中止播放的動做放在onStop()
方法裏,不能放到onPause()
方法裏了。this
要讓咱們的App增長對Multi-Window
的支持,咱們須要在<activity>
或者<application>
標籤裏增長以下配置:
android:resizeableActivity=["true" | "false"]
若是target API level是24,則該項默認爲true。
在Android 7.0,咱們能夠在manifest中爲<activity>
增長<layout>
元素,用來規定Activity在Multi-Window
下的行爲。這裏能夠對尺寸、位置作一些配置。好比能夠這樣配置:
<activity android:name=".MyActivity"> <layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="top|end" android:minHeight="450dp" android:minWidth="300dp" /> </activity>
Activity對Multi-Window
提供了以下方法:
isInMultiWindowMode()
:查看Activity是否正處於Multi-Window
模式。
onMultiWindowModeChanged()
:當Activity進入或者退出Multi-Window
模式時的回調。
咱們能夠在Activity中根據本身的業務邏輯靈活使用這兩個方法。
除此以外,咱們還能夠以Multi-Window
模式啓動一個Activity。想要實現這一點,咱們須要在Intent中增長FLAG_ACTIVITY_LAUNCH_ADJACENT
。當啓動Activity的Intent包含該Flag時,系統會執行以下動做:
若是設備當前正處於Multi-Window
模式,則新啓動的Activity會佔用另一個窗口,與當前Activity分屏佔用整個屏幕。這裏須要注意,新的Activity須要以NEW_TASK
的模式啓動。
若是設備沒有處於Multi-Window
模式,則該Flag無效。
在MultiWindowGiraffe中,我建立了兩個Activity:MainActivity
和WebActivity
。當處於分屏模式下,咱們期待從MainActivity
啓動WebActivity
,使得兩個Activity分享整個屏幕,這裏能夠這樣實現:
Intent intent = new Intent(MainActivity.this, WebActivity.class); intent.putExtra(WebActivity.FIELD_URL, url); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (isInMultiWindowMode()) { // launch this activity in another split window next to the current one intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT); } startActivity(intent);
這樣一來,咱們已經能夠在同一個屏幕上同時顯示兩個Activity了。效果以下:
下面,咱們期待能夠用手指將上面這個EditText
裏的URL拖動到下面的WebView
,而且自動加載該URL頁面。
經過Drag and Drop
,咱們能夠將數據從一個View傳遞到另外一個View。當用戶作出一些咱們能夠識別的手勢操做時(好比長按),咱們須要告知系統開始一個Drag
過程。當一個Drag
過程開始後,咱們能夠爲拖動的View生成一個虛擬的陰影,這個陰影能夠隨着用戶的手指進行移動。在移動過程當中,系統不斷給咱們先前設置的Drag Listener
發送一系列事件,咱們能夠經過不一樣的事件進行不一樣的處理。當用戶手指離開屏幕時(咱們稱爲Drop
操做),整個Drag and Drop
過程結束。
咱們能夠經過View.startDragAndDrop方法開始一個Drag過程。該方法的定義爲:
boolean startDragAndDrop (ClipData data, View.DragShadowBuilder shadowBuilder, Object myLocalState, int flags)
這四個參數意義分別爲:
data
:這次drag包含的數據,能夠看到,這裏採用了ClipData數據類型。
shadowBuilder
:一個DragShadowBuilder
,用來生成drag時指示控件移動的陰影。
myLocalState
:一個包含local數據的對象。暫時不用。
flags
:Drag
過程的flag配置。
在這裏,咱們將URL包裝成一個ClipData
對象,而且爲了支持跨窗口Drag
,咱們須要將flag置成View.DRAG_FLAG_GLOBAL
:
// create ClipData Object ClipData.Item item = new ClipData.Item(mUrlEditText.getText().toString()); ClipData data = new ClipData("LABEL", new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item); // start drag view.startDragAndDrop(data, new GiraffeDragShadowBuilder(view), null, View.DRAG_FLAG_GLOBAL);
當觸發一個Drag
過程時,系統會產生一個image,用來指示用戶手指的移動。這個image叫作drag shadow。爲了生成這個image,咱們須要本身實現一個DragShadowBuilder
。DragShadowBuilder
的構造方法傳入了一個View類型的參數,用來表示發起Drag
的View。
在DragShadowBuilder
裏,咱們須要實現兩個方法:
onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint)
當咱們調用startDragAndDrop()
方法後,系統會當即調用onProvideShadowMetrics()
方法。這個方法有兩個入參,第一個入參outShadowSize
表示drag shadow顯示的尺寸,第二個入參outShadowTouchPoint
表示drag shadow移動時,與手指的接觸位置。
onDrawShadow(Canvas canvas)
系統調用onProvideShadowMetrics()
方法後,會當即調用onDrawShadow()
方法,經過Canvas
來繪製drag shadow。
在這裏,咱們設置drag shadow的尺寸與EditText
同樣大,設置手指接觸點爲drag shadow的中心點,示例代碼爲:
int width = getView().getWidth(); int height = getView().getHeight(); mShadow.setBounds(0, 0, width, height); outShadowSize.set(width, height); outShadowTouchPoint.set(width / 2, height / 2);
到目前爲止,當咱們移動EditText
時,咱們能夠看到有個陰影隨着咱們手指移動了!
接下來,咱們須要處理整個Drag
過程,而且在合適的時候從WebView
接收數據了。爲了獲取Drag過程的數據和狀態,咱們能夠爲接收該Drag
的目標控件設置一個OnDragListener
,實現其中的onDrag()
方法。onDrag()
方法的定義是這樣的:
public boolean onDrag(View view, DragEvent dragEvent) {}
其中,第一個參數view
表示設置了Drag監聽器的目標控件,第二個參數dragEvent
表示Drag過程當中系統發送的一系列事件。
在Drag過程當中,咱們能夠在DragListener
裏收到以下一系列事件:
ACTION_DRAG_STARTED
:表示一個Drag過程的開始。這裏咱們能夠作一些初始化的工做,好比,能夠判斷這次Drag包含的數據的類型,若是目標控件支持處理該種類型數據,將目標控件加亮,以給用戶視覺上的提示。
ACTION_DRAG_ENTERED
:表示該過程的drag shadow進入到了目標控件的邊界。
ACTION_DRAG_EXITED
:表示該過程的drag shadow離開目標控件的邊界。
ACTION_DROP
:表示用戶在目標控件上釋放了這次drag過程。咱們能夠在這個事件發生時獲取到這次傳遞的數據,而且作相應的處理。
ACTION_DRAG_ENDED
:表示一次drag過程的結束。
這裏須要注意的是,onDrag()
方法返回了一個boolean
類型的返回值,該返回值指定了咱們是否能夠繼續收到當前drag過程的事件。若是返回true
,說明咱們對這次drag比較感興趣,系統還會將後續一系列的drag事件傳遞給咱們的DragListener
;若是返回false
,則咱們不會再收到這次drag後續的任何事件。
在MultiWindowGiraffe中,我作了以下處理:
在drag過程當中,支持當前數據類型的目標控件置爲藍色;
若是drag shadow移動到了某個控件邊界內,若是這個控件支持該數據類型,則將該控件置爲綠色。
當drag完成時,在WebView內加載URL。
這裏能夠參考GiraffeDragEventListener的實現。
至此,咱們就能夠實現我以前說過的,在Multi-Window
模式下跨窗口傳遞數據了!!!來看一個效果圖吧: