Android N Multi-Window Mode Support
来源:互联网 发布:男装淘宝店铺运营外包 编辑:程序博客网 时间:2024/04/29 16:53
1. Multi-Window Mode
如何进入分屏模式?
长按Overview,App进入Split Screen Mode
单击Overview,点击App标题栏上的吕形按钮,进入Split Screen Mode
单击Overview,长按App标题栏拖入屏幕上的高亮区域,然后进入Freeform Screen Mode
如何配置App支持分屏模式?
项目首先需要满足的
targetSdkVersion >= Android N Preview
项目AndroidManifest.xml中需要给MainActivity配置:
android:resizeableActivity = true
这个属性值在Android N的手机系统默认true,也就是默认支持分屏,前提是满足条件1。
名词:
Full Screen Mode:全屏幕模式
Split Screen Mode: 上下屏幕模式
Freeform Screen Mode: 自由尺寸屏幕模式
2. 分屏模式下被禁用的特性
- 分屏模式下无法隐藏系统的状态栏
- 分屏模式下无法根据屏幕方向旋转App
3. 分屏模式下Activity的生命周期和回调方法
正常情况下,切换分屏模式的生命周期
长按overview进入分屏模式的Activity生命周期
MainActivity: onMultiWindowModeChanged
MainActivity: isInMultiWindowMode:true
MainActivity: onPause
MainActivity: onSaveInstanceState
MainActivity: onStop
MainActivity: onDestory
MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume
//注意这里,这里是因为切换到Split Mode的时候,Activity会先立即失去焦点,这点很关键
MainActivity: onPause退出分屏模式
退出分屏模式时,基于分屏模式下Activity的状态,不同的状态生命周期回调有差异
如果Activity处于 onPause状态,退出分配模式:
MainActivity: onSaveInstanceState
MainActivity: onStop
MainActivity: onDestory
MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceSate
MainActivity: onResume
MainActivity: onPause
MainActivity: onMultiWindowModeChanged
MainActivity: isInMultiWindowMode:false
MainActivity: onResume
//这个Log不属于退出分屏流程的回调如果Activity处于onResume状态,退出分屏模式:
MainActivity: onPause
MainActivity: onSaveInstanceState
MainActivity: onStop
MainActivity: onDestroy
MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume
MainActivity: D/MN: onMultiWindowModeChanged
MainActivity: isInMultiWindowMode= false上面的Log输出说明了,如果退出分屏模式之前为onPause状态,则退出之后Activty也必须切换到onPause状态,然后调用的onResume不属于退出分屏的回调流程,属于Activity展示在前台进程获取焦点时的回调。反之,如果退出之前是onResume状态,则退出之后也必须是onResume,则不必再onResume
总结就是:分屏模式下做一些操作,譬如退出分屏/改变分屏模式的尺寸,Activty在操作前的状态和操作后的状态要保持一致。至于Activity被放置到前台进程时触发的Activty生命周期回调方法,不属于分屏模式下操作的回调。
分屏模式下,从一个Activity切换到和它同处于多窗口的另外一个Activity
MainActivity: onPause
SecondActivity: onResume
分屏模式下,改变窗口的尺寸,也需要判断Activty的上一个状态 onPause/onResume
操作之前处于onPause状态:
MainActivity:D/MN: onSaveInstanceState
MainActivity:onStop
MainActivity:onDestroy
MainActivity:onCreate
MainActivity:onStart
MainActivity:onRestoreInstanceState
MainActivity:onResume
MainActivity:onPause操作之前处于onResume状态:
MainActivity:onPause
MainActivity:onSaveInstanceState
MainActivity:onStop
MainActivity:onDestroy
MainActivity: onCreate
MainActivity:onStart
MainActivity:onRestoreInstanceState
MainActivity:onResume
由上面的情况可以得知,当切换到多窗口模式或者改变多窗口模式下Activity 的尺寸时,Activity会被销毁然后重新加载。
特殊情况下,切换分屏模式的生命周期
为了解决上述的Activity销毁重建的问题,特做了一下配置:
在AndroidManifest.xml文件中,给Activity加上属性配置如下:
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
- 在Activity中复写方法
onConfigurationChanged
(不是必须,根据自身的情况来判断是否需要复写)
- 在Activity中复写方法
在这种情况下由Full ScreenMode切换为Split Screen Mode,其生命周期:
MainActivity:onPause
MainActivity:onSaveInstanceState
MainActivity:onStop
MainActivity:onConfigurationChanged
MainActivity:onMultiWindowModeChanged
MainActivity:isInMultiWindowMode= true
MainActivity:onRestart
MainActivity:onStart
MainActivity:onResume
MainActivity:onPause
但是上面的配置,由Full Screen Mode/Split Screen Mode切换为FreeForm Screen Mode并不适用:
Split Screen Mode —— >FreeForm Screen Mode
MainActivity:onPause
MainActivity:onSaveInstanceState
MainActivity:onStop
MainActivity:onDestroy
MainActivity:onCreate
MainActivity:onStart
MainActivity:onRestoreInstanceState
MainActivity:onResumeFull Screen Mode —— >FreeForm Screen Mode
MainActivity:onPause
MainActivity:onSaveInstanceState
MainActivity:onStop
MainActivity:onMultiWindowModeChanged
MainActivity:isInMultiWindowMode= true
MainActivity:onDestroy
MainActivity:onCreate
MainActivity:onStart
MainActivity:onRestoreInstanceState
MainActivity:onResume基于上面的配置,当处于Split/FreeForm Mode时,如果改变Activity的尺寸:
MainActivity:onConfigurationChanged
除此之外,FreeForm Mode切换为 Full Screen Mode,Activity也会销毁重建:
MainActivity:onPause
MainActivity:onSaveInstanceState
MainActivity:onStop
MainActivity:onDestroy
MainActivity: onCreate
MainActivity:onStart
MainActivity:onRestoreInstanceState
MainActivity:onResume
MainActivity:onMultiWindowModeChanged
MainActivity:isInMultiWindowMode = false当Activity A处于Split Screen Mode时,startActivityForResult Activity SECOND,然后切换为FreeForm Screen Mode,最后Activity SECOND Finish 回到Activity A:
=========startActivityForResult Activity SECOND=====
MN - A: onPause
MN - SECOND: onCreate
MN - SECOND: onStart
MN - SECOND: onResume
MN - A: onSaveInstanceState
MN - A: onStop
============Split Mode —> FreeForm Mode =======
MN-SECOND: onPause
MN-SECOND: onSaveInstanceState
MN-SECOND: onStop
MN-SECOND: onDestroy
MN-SECOND: onCreate
MN-SECOND: onStart
MN-SECOND: onRestoreInstanceState
MN-SECOND: onResume
=========B Finish return A ========
MN-SECOND: onPause
MN - A: onDestroy
MN - A: onCreate
MN - A: onStart
MN - A: onRestoreInstanceState
MN - A: onActivityResult
MN - A: onResume
MN-SECOND: onStop
MN-SECOND: onDestroy监测多窗口状态的回调方法
前提是Android N SDK
boolean Activity.isMultiWindowMode()
判断Activity是否处于Multi-Window(Split / FreeForm Screen Mode)
void Activity.onMultiWindowModeChanged(boolean isInMultiWindowMode)
Full Scree Mode <——> window-multi 时触发调用
4. 代码中如何判断App处于哪种模式?
首先在特殊情况下,Full Screen Mode 切换到Split Screen Mode不会销毁重建Activity,而切换到FreeForm Screen Mode会销毁重建Activity。
那么,判断multi-window mode 的条件就是:
1.isMultiWindowMode
方法返回值为false,则处于Full Screen Mode
2.isMultiWindowMode
方法返回值为true,并且调用了onRestoreInstanceState
方法,则处于FreeForm Screen Mode
,否则处于Split Screen Mode
5. Layout attributes
在Android N 中我们可以向AndroidManifest.xml
中给activity添加 layout节点
,并且设置一些属性,通过这些属性来设置分屏模式的一些行为,如最小尺寸等。
android:defaultWidth
android:defaultHeight
这2个属性是freeform模式下默认的宽度和高度android:gravity
这个是freeform模式下在手机窗口上默认的Gravityandroid:minWidth
android:minHeight
这2个属性freeform模式下最小宽度和高度。
下面是一个示例:
<activity android:name=".SecondActivity"> <layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="top|end" android:minHeight="450dp" android:minWidth="300dp"/></activity>
6. Luanch Activity with a defined bounds on Screen
In free-form mode, this activity is to be launched within a defined bounds on screen.
使用这个配置启动的Activity,在由非FreeForm切换为FreeForm模式,则以定义的bounds在屏幕上显示Activity的位置
// Define the bounds in which the Activity will be launched into.Rect bounds = new Rect(500, 300, 100, 0);// Set the bounds as an activity option.ActivityOptions options = ActivityOptions.makeBasic();options.setLaunchBounds(bounds);// Start the LaunchBoundsActivity with the specified optionsIntent intent = new Intent(this, LaunchBoundsActivity.class);startActivity(intent, options.toBundle());
7. 测试APP和Android N Multi-window 适配思路
保证APP可以顺利进入/退出分屏模式,且改变APP的尺寸时,UI依然可以非常顺滑,以及在分屏模式下,仍然可以保持性能的稳定性,不会Crash也不会OOM:
长按Overview之后,确保App能够进入Split Screen Mode分屏模式,且改变尺寸后仍然能正常工作。
长按Overview,拖动App标题栏进入FreeForm Screen Mode分屏模式,且改变尺寸后仍然能正常工作。
分屏模式下,在短时间内、多次、迅速的改变APP的尺寸,确保APP没有崩溃,且没有发生内存泄漏,且UI的刷新没有花费太多的时间。
进一步优化分屏模式:
减少不可滑动的页面和控件
在分屏过程中,屏幕的高度只有原来的一半,如果有太多的控件不响应滑动事件,那么用户无法上下滑动页面,甚至无法进行下一步操作。
这类页面属于最常见的Splash screen、登录注册页、弹窗等。尽量使用相对位置,以兼容分屏模式下多种窗口尺寸
尺寸变化时的处理,比如PopUpWindow自定义键盘
8. Talk is cheap , let us see case!
Activity中存在Fragment使用注意
在分屏模式下,如果Activity被销毁重建,FragmentManager中存在的Fragments会被自动保存,当再次show/ hide 时,需要注意操作的Fragment是否为同一个Fragment,即是否在FragmentManager中存在同一个Fragment,否则show/hide将失效。
举个栗子:
代码段一:初始化Fragment
fragmentArray = new Fragment[] { new xxxFragment(), ...};ArrayList<Fragment> fragmentList = (ArrayList<Fragment>) mFragmentManager.getFragments();if (fragmentList == null || fragmentList.size() == 0) { ... transaction.add(R.id.fl_info, fragmentArray[i], fragmentTagArray[i]); if (i == mCurrentIndex) { transaction.show(fragmentArray[i]); } else { transaction.hide(fragmentArray[i]); }}
代码段二:show/hide
Fragment fragment = fragmentArray[index];if (fragment != null) { transaction.show(fragmentArray[i]);} else { ...}...if (fragment != null) { transaction.hide(fragmentArray[i]);} else { ...}
上面的代码中,当Activity销毁重建之后,fragmentList 不为null,无法添加fragmentArray[i],然后show/hide操作时无效。
解决办法:
重写Activity 的
onSaveInstanceState
方法,并且不执行super.onSaveInstanceState(outState);
@Overrideprotected void onSaveInstanceState(Bundle outState) { //super.onSaveInstanceState(outState);}
当ViewPager配合Fragment使用时,也需要注意使用,否则会产生切换ViewPager空白。
根本原因就是操作的Fragment和FragmentManager中存在的不是同一个。
Toast 和 Dialog的使用注意
首先我们需要了解什么是Window,Window是一个抽象类,它的具体实现是PhoneWindow,其实现过程是在WindowManagerService中。
Window不能够直接访问,需要借助WindowManager。
在Android中,所有的视图都是通过Window来呈现,不管是Activity、Dialog还是Toast,他们的视图实际上都是附加在window上的,所以Window是View的实际管理者。
单击事件由Window传给DecorView,然后传递给我们的View。
Activity设置视图setContentView也是由Window完成的
WindowManager.addView(mView);
WindowManage.LayoutParamas有flags和type参数,其中Type参数表示window的类型,有3种类型,分别是应用window、子window、系统window
应用window对应一个Activity
子window不能单独存在,需要附属在特定的父Window之中,比如Dialog
系统window是需要生命权限才能创建的window,比如Toast和系统状态栏Window是分层的,每个window都有对应的z-ordered,层级大的会覆盖在层级小的window上面,在3类window中,应用window层级范围是1-99,子window层级是1000-1999,系统window层级是2000-2999,这些层级范围对应着windowManager.LayoutParamas的Type参数。
Window是一个抽象的概念,每一个window对应一个View,window和view通过ViewRootImpl联系,所以View是window存在的体现。
了解了这些,我们来看Dialog和Toast的window创建过程:
Dialog的window创建过程
创建window,通过PolicyManager的makeNewWindow方法完成
初始化DecorView并将Dialog的视图添加到DecorView中
WindowManager将DecorView添加到window中并显示
普通的Dialog有特殊之处,那就是必须采用Activity的Context,一般来说Dialog的window依附于Activity的windowToast的window创建过程:
首先Toast也是基于window实现的,但是和Dialog不同,具体的就不详述了
Toast属于系统window,属于整个屏幕范围的window那么,当手机分屏时,如果使用了Toast,则Toast的会保持原位置不变。
所以为了适配分屏模式,不建议使用Toast,可以使用Dialog代替
PopUpWindow自定义键盘在尺寸变化时如何适配
在分屏模式下,APP的尺寸产生了变化,PopUpWindow自定义键盘会遮盖掉整个EditText,这显然不是我们想要看到的,我们想要的是系统键盘那样将EditText放在键盘的上方。
思路很简单,在显示PopUpWindow之前,计算出EditText的bottom应该所在的准确位置,然后得到原位置和理想位置的distance,然后整体布局Move distance,当键盘隐藏,自动回到原位置。
首先我们需要了解一些东西:
如何获取屏幕尺寸?
如何准确获取EditText原位置?
如何计算Distance,然后Move布局到理想效果?
获取屏幕尺寸:
DisplayMetrics dm = null;activity.getWindowManager().getDefaultDisplay().getMetrics(dm);int screenHeight = dm.heightPixels;
准确获取EditText原位置
int editTextBottom = 0;Rect rect = new Rect();//获取view在视图屏幕范围内的坐标,如果view被遮挡,则返回false, rect(0,0,0,0)boolean globalVisibleRect = editText.getGlobalVisibleRect(rect);if (globalVisibleRect){ editTextBottom = rect.bottom;}
计算Distance并Move blockLayout:
if(editTextBottom!=0) { int scrollY = blockLayout.getScrollY(); int popUpWindowTop = screenHeight - popUpWindowHeight; distance = editTextBottom - (popUpWindowTop); if (scrollY + distance < 0) { distance = -scrollY;//防止多次向上移动 x * distance之后,无法复位 } if (blockLayout instanceof AutoPopLinearLayout) { ((AutoPopLinearLayout) blockLayout).startScroll(scrollY,distance,500); }}
blockLayout是EditText外层布局LinearLayout:
public class AutoPopLinearLayout extends LinearLayout { private final Context context; private final Scroller mScroller; private boolean isMove; public AutoPopLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; DecelerateInterpolator interpolator = new DecelerateInterpolator(); mScroller = new Scroller(context,interpolator); } /** * mScroller是一个封装位置和速度等信息的变量. * startScroll函数只是对它的一些成员变量做一些记录和计算. * 这个函数的结果就是导致mScroller.computeScrollOffset()返回true * 以及触发computeScroll方法的调用 * * 布局坐标体系是以左上方为(0,0) * * @param startY Y方向偏移量,作为开始位置 * @param dy 移动的Y方向上的距离,dy大于0,则布局往上移动,反之向下 * @param duration */ public void startScroll(int startY, int dy, int duration) { isMove = true; mScroller.startScroll(0,startY,0,dy,duration); invalidate(); } @Override public void computeScroll() { /* * 如果mScroller没有调用startScroll,这里将返回false。 * scrollTo方法依据封装在mScroller中的位置信息对blockLayout中的childView进行移动 */ if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); isMove = true; } else { isMove = false; } super.computeScroll(); } public boolean isMove() { return isMove; }}
这里主要需要了解的是: Scroller的概念
Scroller类是为了实现View平滑滚动的一个Helper类,通常是咋自定义View的时候使用。
如上代码中,mScroller记录和计算View滚动的位置,调用startScroll方法驱动,再重写View的computeScroll(),完成实际的滚动,实际的滚动通过scrollTo方法完成。
这里需要知道的一些API:
View.getGlobalVisibleRect(Rect rect)
顾名思义就是获取view的全局可视左上右下坐标,坐标原点时左上角(0,0),右下方向为正方向
View.getScrollY()
return the edge of top of the display part of you view返回view的可视部分的top边缘位置坐标
scrollTo(x,y)
scrolled to one postion(x,y)
Scroller.getCurrX()/getCurrY()
return current new offset X/Y in the scroll返回新的偏移位置 X/Y
如果某个Activity不需要支持分屏如何处理
使用下面代码启动Activity:
Intent intent = new Intent(this, XXXActivity.class);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);startActivity(intent);
并且Activity需要设置属性:
<activity android:resizeableActivity="false" android:excludeFromRecents="true" android:name=".ThirdActivity"></activity>
这样子在新的任务栈中开启Activity,就会以全屏方式打开,并且不支持分屏。其中
android:excludeFromRecents="true"
是为finish掉Activity之后,清除Recents中的Activity。分屏模式下Activity销毁重建时的数据缓存,Temp数据缓存
主要是Activity的重建缓存机制,涉及到2个方法:
@Overrideprotected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.d("N","onSaveInstanceState");}@Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); Log.d("N","onRestoreInstanceState");}
如何适配不可滑动的页面,以及解决滑动时产生的问题
分屏模式下由于尺寸的变化,一些不可滑动的页面只能显示部分内容,所以针对这些页面需要在布局外层嵌套ScrollView,并且设置
android:fillViewport="true"
<ScrollView android:fillViewport="true" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> ... </LinearLayout></ScrollView>
由于ScrollView嵌套之后,导致LinearLayout 的 match_parent不生效,会以wrap_content来计算,在大屏手机如三星手机上显示不全,所以需要设置 android:fillViewport=”true”属性。
值得注意的是,如果ScrollView嵌套ViewPager的时候,ViewPager无法正确显示高度的,导致内容显示不全,以及ViewPager左右滑动冲突的问题。
这个时候就需要自定义ViewPager,栗如:
public class ResetViewPager extends ViewPager { private int lastX = -1; private int lastY = -1; public ResetViewPager(Context context) { super(context); } public ResetViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { heightMeasureSpec = resetHeightMeasureSpec(widthMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 重新计算 heightMeasureSpec * @param widthMeasureSpec * @return */ private int resetHeightMeasureSpec(int widthMeasureSpec) { int heightMeasureSpec; int height = 0; for (int i = 0; i < getChildCount(); i++) { View childAt = getChildAt(i); childAt.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); int h = childAt.getMeasuredHeight(); if (h > height) { height = h; } } heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); return heightMeasureSpec; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int rawX = (int) ev.getRawX(); int rawY = (int) ev.getRawY(); int dealtX = 0; int dealtY = 0; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: dealtX = 0; dealtY = 0; //保证子ViewPager能收到ACTION_MOVE事件 getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: dealtX += Math.abs(rawX - lastX); dealtY += Math.abs(rawY - lastY); //这里的拦截判断依据是左右滑动 if(dealtX >= dealtY){ getParent().requestDisallowInterceptTouchEvent(true); }else{ getParent().requestDisallowInterceptTouchEvent(false); } lastX = rawX; lastY = rawY; break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_UP: break; } return super.dispatchTouchEvent(ev); }}
ListView在分屏模式时的使用注意
示栗:
代码段一
@Overridepublic int getItemViewType(int position) { ... return (rpipttyp.equals("00") || rpipttyp.equals("13")) ? R.layout.item_lv_select_condition2 : R.layout.item_lv_select_condition;}@Overridepublic int getViewTypeCount() { return 2;}
代码段二
int ViewType = getItemViewType(position);switch (ViewType) { case R.layout.item_lv_select_condition2: ... break; case R.layout.item_lv_select_condition: ... break;}
上面的代码是根据 position返回 Item的 Type,以及在
getView()
的时候判断Type但是上面的代码写法在分屏模式下会产生异常:
Exception: ArrayIndexOfBoundsException
原因是:
The ListView item view type you are returning from getItemViewType() is < getViewTypeCount()
也就是说:ListView使用Adapater时,
getViewTypeCount()
方法和getItemViewType()
方法返回值之间有一定的关系。如果
getViewTypeCount
返回值为2,那么getItemViewType(position)
方法的返回值应该为0,1,不能超过1,否则会出现ArrayIndexOfBoundsException
栗如:
@Overridepublic int getItemViewType(int position) { ... return (rpipttyp.equals("00") || rpipttyp.equals("13")) ? 0: 1;}
9. 附录
- Documents - Android developer multi-window doc
- Demo - Android MultiWindowPlayground-master
- Android N Multi-Window Mode Support
- Android N Multi window
- Multi-Window Support
- multi-window support
- Android n multi-window多窗口支持
- Android N New Features———Multi-Window
- 多窗口模式(multi-window mode)
- 适配Android N Multi-Window (多窗口)遇到的一些问题
- Does Android Support Dual-Cores or Multi-Cores Processor
- Multi-resolution support
- Qt multi screen Support
- 28.Multi-process Support
- Android 7.0 SystemUI(2)--Multi-Window多窗口模式
- from program multi language support
- Designing for Multi-Window
- 转移Window Xp Mode
- Android N新特性 : Direct Boot Mode[DBM]
- Android Wi-Fi AP/STA mode support 1x1 or 2x2
- [四校联训]切树游戏-树形DP-组合数学
- 数据结构导论
- 2007-2008 ACM-ICPC, NEERC, Southern Subregional Contest K. Extrasensory Perception(错排)
- matlab图像输入
- MyBatis总结(纯转载)
- Android N Multi-Window Mode Support
- 国庆训练总结
- 51nod 1127 最短的包含字符串
- 页面定时刷新或自动跳转
- 日记(周末)
- TensorFlow实现识别手写数字
- (C语言)关于位段空间的使用情况的经典面试题解析
- mapreduce框架设计思想,wordcount程序原理与实现
- nagios 监控程序安装