Android Nougat - 有點好玩的Multi-Window

開始的開始

自從個人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

Multi-Window

在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

Multi-Window配置

要讓咱們的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>

Multi-Window API支持

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:MainActivityWebActivity。當處於分屏模式下,咱們期待從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了。效果以下:

clipboard.png

下面,咱們期待能夠用手指將上面這個EditText裏的URL拖動到下面的WebView,而且自動加載該URL頁面。

Drag and Drop

經過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數據的對象。暫時不用。

  • flagsDrag過程的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);

DragShadowBuilder

當觸發一個Drag過程時,系統會產生一個image,用來指示用戶手指的移動。這個image叫作drag shadow。爲了生成這個image,咱們須要本身實現一個DragShadowBuilderDragShadowBuilder的構造方法傳入了一個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時,咱們能夠看到有個陰影隨着咱們手指移動了!

DragListener

接下來,咱們須要處理整個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模式下跨窗口傳遞數據了!!!來看一個效果圖吧:

clipboard.png

參考

相關文章
相關標籤/搜索