谷歌于8月22日也就是北京时间23日发布了Android 7.0 Nougat牛轧糖的正式版,爱哥也及时将手中的Nexus5X刷到了正式版7.0,把玩了一段时间后爱哥觉得在普通应用的功能上7.0木有太大的改进,当然这是相比5.0而言,5.0爱哥觉得算是Android改动较大的一个版本,刚出来就给人各种惊喜,相对而言7.0则没那么多亮点,一个主要的原因是7.0中不少功能在国内各大各小的UI系统上都早有雏形甚至体验比7.0更好,就拿7.0让人期待的分屏来说,早在某米和某族等国产神机上就有了,某族上的一些应用分屏甚至做得更好,比如在某族上分屏你只需要两个步骤:
第一,先唤出Recent Task:
然后选择可以分屏的应用,可以分屏的应用在Recent Task里卡片右上角会有“分屏”二字,点击后就进入分屏界面:
在列出的可以分屏打开的应用里选择一个打开,这里爱哥挑了一个资讯,就可以看到如下效果:
然后你可以拖动两个应用窗口之间的那根白线上下调节窗口的显示大小:
这就是某族上的分屏功能,仅仅是分屏,还谈不上多窗口,而且这还是某族在很早的版本上就有的功能……这次Android7.0带来的分屏功能跟某族的极其相似,下面是Android 7.0原生的分屏效果:
可以看到原生的分屏跟某族的在效果上几乎没区别,唯一不同的是在功能上原生的分屏支持绝大部分应用,而且你得应用并不需要做什么特别的代码上的逻辑修改,是不是感觉挺蛋疼的,如果在小屏手机上,我5x是5.2的屏,开分屏简直就是个鸡肋……某族的分屏出了这么久爱哥表示也没真正意义上的用过……而且原生的分屏在爱哥测试的过程中还有几个BUG,其实也说不上是BUG,只是体验相对来说不好吧,第一个是调整分屏窗口大小的时候,原生的会在调整过程中将调节的部分显示为白色:
这点爱哥认为没有某族在视觉上做的体验好,某族是直接一个半透明的黑框透过去显示出下面的应用:
这点原生的感觉就有点不足。其次,原生在分屏界面按返回键退出后会直接回到桌面,回到桌面本身没啥问题,逻辑如此可以理解,但是回到桌面后状态栏的颜色居然还是分屏里应用的主题色:
这真是日了狗,还有呢如果我们在分屏的过程中出现应用的闪退之类,有可能会导致下面这样的显示错误:
爱哥虽然还没来得及看分屏部分的相关源码但是可以猜到原生分屏的实现应该是用了一个全局的容器来装载两个分屏的应用,也就是说在应用窗口之上套了一个分屏窗口容器,验证一下,按返回键从分屏退到桌面,再点开其他应用,原生的果然还是跳回了分屏界面……当然啦这只是猜想,具体实现等爱哥同步完7.0的源码再细细研究。
Android7.0的多窗口分为两种,一种是上面我们所提到的分屏,分屏只能上下或左右分;另一种是画中画模式,画中画模式就是我们Windows这类多窗口类似的窗口模式,不过画中画模式只能在Nexus palyer里应用,爱哥饶有兴致地用TV模拟器强制开启画中画模式试了下……
1 2 3 4 5 6 7 8 9 10 | @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// 确保当前Activity处于画中画模式下 enterPictureInPictureMode(); setContentView(R.layout.ac_base);
...... } |
对应地在AndroidManifest.xml中让Activity支持画中画模式:
1 2 3 4 | <activity android:supportsPictureInPicture="true"> ...... </activity> |
发现没有Player的遥控器根本不能玩……我们的手持设备上就更不行啦,不过大家可以通过某族的画中画模式来间接感受下Android7.0的原生画中画模式:
Android 7.0为多窗口提供了一系列的新的API,其中包含两个比较重要的Activity调用方法:onMultiWindowModeChanged和onPictureInPictureModeChanged,官方文档对这两个方法的解释是当Activity进入或退出多窗口模式时将会调用onMultiWindowModeChanged方法,当Activity进入多窗口模式时,系统会向该方法传递true值,当Activity退出多窗口模式时,则传递false值。onPictureInPictureModeChanged也与之类似。不过在实际验证过程中爱哥发现这两个方法跟文档描述的有点不一样,我们先来看看我们的小DEMO,这个例子很简单,两个Activity分别全屏填充两种不同的颜色,我们将两个Activity放置于不同的Task中以便能在Recent Task中同时显示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.aigestudio.multiwindows">
<application> <activity android:name="com.aigestudio.multiwindows.TopActivity" android:label="Top" android:taskAffinity=""> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.aigestudio.multiwindows.BottomActivity" android:label="Bottom" android:taskAffinity=""> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
我们清一下Recent Task让我们的Recent Task里只显示我们应用中的两个Activity,Top和Bottom:
我们先让TopActivity位于前台,然后长按导航栏的菜单按钮进入分屏:
这时候,TopActivity的onMultiWindowModeChanged方法会被调用,但是,注意,BottomActivity的onMultiWindowModeChanged方法此时是不会被调用的,为什么呐?原因很简单,此时的BottomActivity并没有被分屏对吧,它只是隐藏在了后台而已,因此onMultiWindowModeChanged方法不触发是正常的,然后我们在底部的分屏窗口中选择BottomActivity,此时BottomActivity被分屏:
但是!!!!!正常逻辑下此刻BottomActivity的onMultiWindowModeChanged方法应该被调用对吧!!但是此刻该方法并没有被调用!!!真是日了狗,爱哥表示不知道这 是分屏本身的设计如此还是系统BUG,真是有点醉啊……但是如果我们长按菜单按钮退出分屏,那么TopActivity和BottomActivity的onMultiWindowModeChanged方法均会被触发………………一万只草泥马裸奔…………不得不服……除了这两个系统主动会调用的Activity方法外,还有另外两个用于判断多窗口模式的方法isInMultiWindowMode和isInPictureInPictureMode,具体含义就不说了,但是需要注意的是如果isInPictureInPictureMode画中画模式返回true那么isInMultiWindowMode必定也会返回true。对了,顺便说一下,上述方法均有Fragment版本哦。
另一个需要注意的是在分屏的情况下如果我们启动Activity,可以为Intent设置一个FLAG_ACTIVITY_LAUNCH_TO_ADJACENT标识,该标识会告诉系统,如果有可能的话就把新启动的Activity置于当前Activity旁边,也就是说让新启动的Activity与当前Activity并列显示,不过需要注意的是新Activity的launchMode得是singleInstance才行哦,不过呢该操作也仅仅是告诉系统,而非强制,至于是不是会让两个Activity并列显示,最终还是得看系统心情……是不是有点醉……
Android 7.0默认是支持所有Activity可以分屏显示的,但是你也可以在开发中指定Activity不允许其被分屏,你只需要在AndroidManifest.xml中简单地指定Activity的resizeableActivity为false即可:
1 2 3 4 | <activity android:resizeableActivity="false"> ...... </activity> |
运行后如果我们再长按导航栏的菜单键就会出现“该应用不支持分屏”的提示……其实爱哥觉得提示改为“该界面不支持分屏”对用户来说体验更好:
如果我们想在分屏中查看该Activity,会看到在该Activity卡片的上方显示“该应用不支持分屏”:
除此之外在多窗口模式下你还可以为Activity指定一个layout子节点,用于告诉系统该Activity在分屏模式下的宽高位置等信息:
1 2 3 4 5 6 7 8 9 10 11 12 | <activity android:name=".SplitActivity" android:label="Split" android:launchMode="singleInstance" android:taskAffinity=""> <layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="top|end" android:minHeight="500dp" android:minWidth="700dp" /> </activity> |
这里先吐槽一下官网文档的代码有问题,官网给出的layout示例是这样的:
minWidth和minHeight写成了minimalWidth和minimalHeight……还好我机制!!!一眼看穿,这段参数很好理解,defaultWidth和defaultHeight表示分屏模式下该Activity的默认宽高,gravity则表示位置,实质上这三个参数在我们普通的手持设备上根本没卵用……对于手持设备而言不管你defaultWidth和defaultHeight设置为啥,默认都是屏幕对半分的,而gravity呢其实也是针对画中画模式的,因为我们的分屏没有gravity可言。另外两个参数minWidth和minHeight就很有意思了,他们表示分屏后分屏窗口的最小宽高,可能有朋友不解,对分屏来说最小宽高不就是屏幕宽度和高度一半么?其实不然,这里的最小宽高指的是分屏后窗口的实际内容区域,默认情况下实际内容区域应该是和分屏后的窗口一样的,但是你可以通过这两个参数来强制更改这个大小,看看效果你就懂了:
绿色的界面就是上面我们指定了minWidth和minHeight的界面,可以看到我们的Button已然不在窗口的中心位置了,因为窗口的大小我们强制更改了,中心位置自然也变了,这里还要注意的是,调整分屏窗口大小的Activity其launchMode也必须为singleInstance。
在多窗口模式下,原来只能在Activity内进行的数据拖放也被升级到了跨Activity拖放,实现方式与原来并无太大出入,只是跨Activity拖放多了一个DRAG_FLAG_GLOBAL_XXXX的标识值,总体来说还算比较简单:
1 2 3 4 5 6 7 8 9 | mBtnClick.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { ClipData data = ClipData.newPlainText(view.getClass().getName(), "AigeStudio"); View.DragShadowBuilder builder = new View.DragShadowBuilder(view); view.startDragAndDrop(data, builder, view, View.DRAG_FLAG_GLOBAL); return true; } }); |
这里我们先给Button一个长按事件,让我们长按Button后包裹一个带字符串“AigeStudio”的剪切数据,并让我们的Button可以被拖拽。然后我们在跟布局里监听拖放获取拖放数据:
1 2 3 4 5 6 7 8 9 10 | llRoot.setOnDragListener(new View.OnDragListener() { @Override public boolean onDrag(View view, DragEvent dragEvent) { if (dragEvent.getAction() == DragEvent.ACTION_DROP) { ClipData.Item item = dragEvent.getClipData().getItemAt(0); mTVContent.setText(item.getText()); } return true; } }); |
接下来我们就从TopActivity拖拽Button到BottomActivity放手:
然后呢我们的松开手,这样从TopActivity附带的剪切数据就会被传递到BottomActivity中并设置到BottomActivity的TextView上:
除此之外再多窗口模式下许多功能会受到限制,比如在非全屏模式下应用无法隐藏状态栏而且系统将忽略android:screenOrientation属性的设置,因此如果你的应用想对多窗口做一些额外的适配,就应该多注意这些参数属性的变更。
另一个比较重要的是多窗口模式下Activity的生命周期,Android声称多窗口模式下Activity的生命周期不会更改,事实确实如此,生命周期的方法名字都没变,调用顺序呢也没变,比如原来的onCreate方法被调用后就是onStart方法调用,现在在多窗口模式下也一样……然而如果你从普通模式切换到多窗口模式下,生命周期的调用将会发生一定的改变,Activity会先被销毁而后重新创建,我们知道当我们新启动一个Activity时会走下述的生命周期:
1 2 3 4 5 | onCreate onStart onPostCreate onResume onPostResume |
那么如果此时我们长按导航栏菜单按钮分屏会如何呢?此刻Activity的会销毁然后重新创建:
1 2 3 4 5 6 7 8 9 | onPause onStop onDestroy onCreate onStart onPostCreate onResume onPostResume onPause |
上述的生命周期中最后一个被调用的是onPause方法,那么为什么这里我们的onPause方法会在最后调用呢?因为这时候我们的TopActivity被分屏了对吧,此刻系统就会将TopActivity置为暂停状态,而将下面的分屏窗口置为激活状态,如果下方的分屏窗口中有Activity,那么该Activity将会被置为“显示”状态,也就是说即便分屏是同时显示在屏幕上,但是也有主次之分,当前与用户交互的那个窗口为活动窗口,而没有与用户交互的窗口则为非活动窗口,两个窗口中的Activity生命周期会相应地切换,如上所示,当一个分屏窗口被置为活动窗口时,另一个分屏窗口将会标识为非活动状态,此刻非活动窗口中Activity的onPause方法会被调用,而活动窗口中Activity的onResume相关方法会被调用,因此你需要注意的是,在一些应用场景下,比如你想让视频分屏播放,那么就不能在onPause或onResume中处理暂停/播放的逻辑,因为这会使你在交互另一个分屏窗口的时候让你的视频暂停,你应该将相关逻辑适配移植到onStop和onStart方法中。
好了,以上就是Android 7.0多窗口的内容,其实在实际应用中爱哥觉得多窗口,特别是手持设备的分屏窗口意义不是很大,不过再鸡肋的功能也有它可用武之地对吧,多了解下也没啥,况且从应用上来说,7.0的多窗口并不需要你特意为其适配什么,最后至于是不是要在你项目里用上多窗口,那就见仁见智了……