Android N App分屏模式完全解析(下)
在上篇中,介紹了什麼是App分屏模式,以及如何設定我們的App來進入分屏模式。這次我們看一下,作為開發者,我們應該如何讓自己的App進入分屏模式,當App進入分屏模式時,我們注意哪些問題。
簡單地說,我認為除了保證分屏時App功能、效能正常以外,我們需要重點學習 如何在分屏模式下開啟新的Activity 以及 如何實現跨App/Activity的拖拽功能。
用分屏模式執行你的App
Android N中新增了一些方法來支援App的分屏模式。同時在分屏模式下,也禁用了App一些特性。
分屏模式下被禁用的特性
- 自定義系統UI,例如分屏模式下無法隱藏系統的狀態列。
- 無法根據螢幕方向來旋轉App的介面,也就是說
android:screenOrientation
分屏模式的通知回撥、查詢App是否處於分屏狀態
最新的Android N SDK中,Activity
類中增加了下面的方法。
- inMultiWindow():返回值為boolean,呼叫此方法可以知道App是否處於分屏模式。
- inPictureInPicture():返回值為boolean,呼叫此方法可以知道App是否處於畫中畫模式。
注意:
畫中畫模式
其實是一個特殊的分屏模式
,如果mActivity.inPictureInPicture()
返回true
,那麼mActivity.inMultiWindow()
一定也是返回true
。
- onMultiWindowChanged(boolean inMultiWindow):當Activity進入或者退出分屏模式時,系統會回撥這個方法來通知開發者。回撥的引數
inMultiWindow
inMultiWindow
為true,表示Activity進入分屏模式;如果inMultiWindow
為false,表示退出分屏模式。 - onPictureInPictureChanged(boolean inPictureInPicture):當Activity進入畫中畫模式時,系統會回撥這個方法。回撥引數
inPictureInPicture
為true
時,表示進入了畫中畫模式;inPictureInPicture
為false
時,表示退出了畫中畫模式。
Fragment
類中,同樣增加了以上支援分屏模式的方法,例如Fragment.inMultiWindow()
。
如何進入畫中畫模式
呼叫Activity
類的enterPictureInPicture()
方法,可以使得我們的App進入畫中畫模式。如果執行的裝置不支援畫中畫模式,呼叫這個方法將不會有任何效果。更多畫中畫模式的資料,請參考picture-in-picture。
在分屏模式下開啟新的Activity
當你開啟一個新的Activity時,只需要給Intent新增Intent.FLAG_ACTIVITY_LAUNCH_TO_ADJACENT
,系統將嘗試將它設定為與當前的Activity共同以分屏的模式顯示在螢幕上。
注意:這裡只是嘗試,但這不一定是100%生效的,前一篇部落格裡也說過,假如新開啟的Activity的android:resizeableActivity
屬性設定為false
,就會禁止分屏瀏覽這個Activity。所以系統只是嘗試去以分屏模式開啟一個新的Activity,如果條件不滿足,將不會生效!此外,我實際用Android
N Preview SDK
實踐的時候發現這個FLAG
實際得值是FLAG_ACTIVITY_LAUNCH_ADJACENT
,並非是FLAG_ACTIVITY_LAUNCH_TO_ADJACENT
。
當滿足下面的條件,系統會讓這兩個Activity進入分屏模式:
- 當前Activity已經進入到分屏模式。
- 新開啟的Activity支援分屏瀏覽(即android:resizeableActivity=true)。
此時,給新開啟的Activity,設定 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
| Intent.FLAG_ACTIVITY_NEW_TASK);
才會有效果。
那麼為何還需要新增FLAG_ACTIVITY_NEW_TASK
?看一下官方解釋:
注意:在同一個Activity返回棧中,開啟一個新的Activity時,這個Activity將會繼承上一個Activity所有和
分屏模式
有關的屬性。如果你想要在一個獨立的視窗以分屏模式開啟一個新的Activity,那麼必須新建一個Activity返回棧。
此外,如果你的裝置支援自由模式
(官方名字叫freeform,暫且就這麼翻譯它,其實我認為這算也是一種尺寸更自由的分屏模式,上一篇部落格裡提到過如果裝置廠商支援使用者可以自由改變Activity的尺寸,那麼就相當於支援自由模式
,這將比普通的分屏模式更加自由),開啟一個Activity時,還可通過ActivityOptions.setLaunchBounds()
來指定新的Activity的尺寸和在螢幕中的位置。同樣,這個方法也需要你的Activity已經處於分屏模式時,呼叫它才會生效。
支援拖拽
在上一篇部落格裡也提到過,現在我們可以實現在兩個分屏模式的Activity之間拖動內容了。Android
N Preview SDK中,View
已經增加支援Activity之間拖動的API。具體的類和方法,可以參考N
Preview SDK Reference,主要用到下面幾個新的介面:
- View.startDragAndDrop():View.startDrag() 的替代方法,需要傳遞
View.DRAG_FLAG_GLOBAL
來實現跨Activity拖拽。如果需要將URI許可權傳遞給接收方Activity,還可以根據需要設定View.DRAG_FLAG_GLOBAL_URI_READ
或者View.DRAG_FLAG_GLOBAL_URI_WRITE
。 - View.cancelDragAndDrop():由拖拽的發起方呼叫,取消當前進行中的拖拽。
- View.updateDragShadow():由拖拽的發起方呼叫,可以給當前進行的拖拽設定陰影。
- android.view.DropPermissions:接收方App所得到的許可權列表。
- Activity.requestDropPermissions():傳遞URI許可權時,需要呼叫這個方法。傳遞的內容儲存在DragEvent中的ClipData裡。返回值為前面的
android.view.DropPermissions
。
下面是我自己寫的一個demo,實現了在分屏模式下,把一個Activity中ImageView中儲存的內容到另外一個Activity中進行顯示。實際應用中,可以還可以傳遞圖片的url或者Bitmap物件。
上圖是一個最基本的例子,實現了把MainActivity中的圖片儲存的內容,拖拽到SecondActivity中。實現步驟如下:
在MainActivity中,發起拖拽。
// 1.首先我們在分屏模式下,開啟自己App中的SecondActivity
findViewById(R.id.launch_second_activity).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
// 2.然後我們在MainActivity中發出拖拽事件
imageView = (ImageView) findViewById(R.id.img);
/** 拖拽的傳送方Activity和ImageView */
imageView.setTag("I'm a ImageView from MainActivity");
imageView.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
/** 構造一個ClipData,將需要傳遞的資料放在裡面 */
ClipData.Item item = new ClipData.Item((CharSequence) view.getTag());
String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
ClipData dragData = new ClipData(view.getTag().toString(), mimeTypes, item);
View.DragShadowBuilder shadow = new View.DragShadowBuilder(imageView);
/** startDragAndDrop是Android N SDK中的新方法,替代了以前的startDrag,flag需要設定為DRAG_FLAG_GLOBAL */
view.startDragAndDrop(dragData, shadow, null, View.DRAG_FLAG_GLOBAL);
return true;
} else {
return false;
}
}
});
在SecondActivity
中,接收這個拖拽的結果,在ACTION_DROP
事件中,把結果顯示出來。
dropedText = (TextView) findViewById(R.id.text_drop);
dropedText.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View view, DragEvent dragEvent) {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
Log.d(TAG, "Action is DragEvent.ACTION_DRAG_STARTED");
break;
case DragEvent.ACTION_DRAG_ENTERED:
Log.d(TAG, "Action is DragEvent.ACTION_DRAG_ENTERED");
break;
case DragEvent.ACTION_DRAG_EXITED:
Log.d(TAG, "Action is DragEvent.ACTION_DRAG_EXITED");
break;
case DragEvent.ACTION_DRAG_LOCATION:
break;
case DragEvent.ACTION_DRAG_ENDED:
Log.d(TAG, "Action is DragEvent.ACTION_DRAG_ENDED");
break;
case DragEvent.ACTION_DROP:
Log.d(TAG, "ACTION_DROP event");
/** 3.在這裡顯示接收到的結果 */
dropedText.setText(dragEvent.getClipData().getItemAt(0).getText());
break;
default:
break;
}
return true;
}
});
這裡實現的關鍵在新增加的startDragAndDrop
方法,看下官方的API文件:
清楚地提到了,發出的DragEvent能夠被所有可見的View物件接收到
,所以在分屏模式下,SecondActivity可以監聽View的onDrag事件,於是我們監聽它!
接著,我們看下DragEvent.ACTION_DROP
事件發生的條件:
當被拖拽的View的陰影進入到接收方View的座標區域,如果此時使用者鬆手,那麼接收方View就可以接收到這個Drop事件。一目瞭然,我們通過拖拽ImageView到圖上的灰色區域,鬆手,便可以觸發DragEvent.ACTION_DROP
,把資料傳到SecondActivity中了。
其實還有更復雜的一些情況,需要呼叫requestDropPermissions
,後續我再進一步實踐一下。
這個demo的地址在這裡,先分享出來,後面我再接著完善它。
在分屏模式下測試你的App
無論你是否將自己的App適配到了Android N,或者是支援分屏模式,都應該找個Android N的裝置,來測試一下自己的App在分屏模式下會變成什麼樣。
設定你的測試裝置
如果你有一臺執行Android N的裝置,它是預設支援分屏模式的。
如果你的App不是用Android N Preview SDK打包的
如果你的App是用低於Android
N Preview SDK
打包的,且你的Activity支援橫豎屏切換
。那麼當用戶在嘗試使用分屏模式時,系統會強制將你的App進入分屏模式。(我在第一篇部落格裡提到過這個,Android
N Preview的介紹視訊中,很多Google家的App都可以進入分屏模式,但是開啟它們的xml一看,其實targetSDKVersion
= 23
)
因此,如果你的App/Activity支援橫豎屏切換,那麼你應該嘗試一下讓自己的App分屏,看看當系統強制改變你的App尺寸時,使用者是否還可以接受這種體驗。如果你的App/Activity不支援橫豎屏切換,那麼你可以確認一下,看看當嘗試進入分屏時,你的App是不是仍然能夠保持全屏模式。
如果你給App設定了支援分屏模式
如果你使用了Android
N Preview SDK
來開發自己的App,那麼應該按照下面的要點檢查一下自己的App。
- 啟動App,長按系統導航欄右下角的小方塊(Google官方把這個叫做Overview Button),確保你的App可以進入分屏模式,且尺寸改變後仍然能正常工作。
- 啟動工作管理員(即單擊右下角的小方塊),然後長按你App的標題欄,將它拖動到螢幕上的高亮區域。確保你的App可以進入分屏模式,且尺寸改變後仍然能正常工作。
這兩點在上一篇部落格中介紹過,讓自己的App進入分屏模式有三種方法。第三種方法,就是在開啟自己的App時,用手指從右下角的小方塊向上滑動,這樣也可以使得正在瀏覽的App進入分屏模式。這種方法目前屬於實驗性功能,正式版不一定保留。
- 當你的App進入分屏後,通過拖動兩個App中間的分欄上面的小白線,從而改變App的尺寸,觀察App中各個UI元素是否正常顯示。
- 如果你給自己的App/Activity設定了最小尺寸,可以嘗試在改變App尺寸時,低於這個最小尺寸,觀察App是不是會回到設定好的最小尺寸。
- 在進行上面幾項測試時,請同時驗證自己的App功能和效能是否正常,並注意一下自己的App在更新UI時是否花費了太長的時間。
這幾項測試,其實主要強調的是,我們的App可以順利的進入/退出分屏模式,且改變App的尺寸時,UI依然可以也非常順滑。
這裡我想多說一句,如果進入了分屏模式,要注意下App彈出的對話方塊,因為螢幕被兩個App分成兩塊之後,對話方塊也是可以彈出兩個的。這時對話方塊上的UI元素可能就會變得比較小了,如果我們的程式碼是寫死的大小,例如對話方塊是一個WebView,就需要特別注意了,搞不好顯示出來就缺了一塊了,這裡需要我們做好適配。
測試清單
關於功能、效能方面測試,還可以按照下面的操作來進行。
- 讓App進入,再退出分屏模式,確保此時App功能正常。
- 讓App進入分屏模式,啟用螢幕上的另外一個App,讓自己的App進入
可見、paused
狀態。舉了例子來講,如果你的App是一個視訊播放器,那麼當用戶點選了螢幕上另外一個App時,你的App不應該停止播放視訊,即使此時你的Activity/Fragment已經接到了onPaused()
回撥。 - 讓App進入分屏模式,拖動分欄上的小白線,改變App的尺寸。請在豎屏(兩個App一上一下佈局)和橫屏(兩個App一左一右佈局)模式下分別進行改變尺寸的操作。確保App不會崩潰,各項功能正常,且UI的重新整理沒有花費太多時間。
- 在短時間內、多次、迅速地改變App尺寸,確保App沒有崩潰,且沒有發生記憶體洩露。關於記憶體使用方面的更詳細注意事項,請參考Investigating Your RAM Usage。
- 在不同的視窗設定的情況下,正常使用App,確保App功能正常,文字仍然可讀,其他的UI元素也沒有變得太小,使用者仍然可以舒適地操作App。
這幾項測試,其實主要是說當App在分屏模式下執行時,仍然可以保持效能的穩定,不會Crash也不會OOM。
如果你給App設定了禁止分屏模式
如果你給App/Activity設定了android:resizableActivity="false"
,你應該試試當用戶在Android
N的裝置上,嘗試分屏瀏覽你的App時,它是否仍然能保持全屏模式。
以上就是參考Google最新的multi-window進行的實踐,總結下,我認為有3點比較重要:
- 如何讓自己的App/Activity順利的進入和退出分屏模式,可以參考處理執行時改變這一章。
- 如何實現跨App/Activity的拖拽功能,可以參考Drag and Drop這一章。
關於App分屏模式的學習就到這裡了,歡迎大家一起交流。我們還發揮更多的想象力,比如是否可以利用跨應用拖拽實現更方便操作,更好的使用者體驗。