Android N 分屏(多窗口支持)+ConstraintLayout简单使用
- 特点
- Android N 允许多个应用同时共享屏幕
- 用户可以在两个 Activity 共享屏幕的同时在这两个 Activity 之间拖放数据 (在此之前,用户只能在一个 Activity 内部拖放数据)。
- 多窗口生命周期
- 多窗口模式不会更改 Activity 生命周期。
- 在多窗口模式中,在指定时间只有最近与用户交互过的 Activity 为活动状态。 该 Activity 将被视为顶级 Activity(onResume)。 所有其他 Activity 虽然可见,但均处于暂停状态(onPause)。 但是,这些已暂停但可见的 Activity 在系统中享有比不可见 Activity 更高的优先级。 如果用户与其中一个暂停的 Activity 交互,该 Activity 将恢复,而之前的顶级 Activity 将暂停。
- 配置应用AndroidManifest.xml
android:resizeableActivity="true"
设置支持分屏,false为不支持,如果您的应用面向 Android N,但未对该属性指定值,则该属性的值默认设为 true。- 布局属性:
- android:defaultWidth
以自由形状模式启动时 Activity 的默认宽度。 - android:defaultHeight
以自由形状模式启动时 Activity 的默认高度。 - android:gravity
以自由形状模式启动时 Activity 的初始位置。请参阅 Gravity 参考资料,了解合适的值设置。 - android:minimalHeight、android:minimalWidth
分屏和自由形状模式中 Activity 的最小高度和最小宽度。 如果用户在分屏模式中移动分界线,使 Activity 尺寸低于指定的最小值,系统会将 Activity 裁剪为用户请求的尺寸。
- android:defaultWidth
- 示例配置代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tengda.android_70two_pager">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:resizeableActivity="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Main2Activity"
android:launchMode="singleInstance"
android:taskAffinity="">
<layout
android:defaultHeight="500dp"
android:defaultWidth="500dp"
android:gravity="top|end"
android:minWidth="500dp"
android:minHeight="500dp" />
</activity>
<activity android:name=".Main3Activity"
android:resizeableActivity="true"
android:supportsPictureInPicture="true">
</activity>
</application>
</manifest>
- MainActivity 代码:
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("test", "One:" + "onCreate");
setImageViewEvent();
}
//监听是不是进入了分屏模式
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
Log.i("test", "One:" + "onMultiWindowModeChanged" + isInMultiWindowMode);
}
//当Activity进入画中画模式时,系统会回调这个方法
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
Log.i("test", "One:" + "onPictureInPictureModeChanged" + isInPictureInPictureMode);
}
//如果用户重新调整窗口的大小,系统在必要的时候也可能触发
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.i("test", "One:" + "onConfigurationChanged"+newConfig.toString());
}
@Override
protected void onStart() {
super.onStart();
Log.i("test", "One:" + "onStart");
}
@Override
protected void onRestart() {
super.onRestart();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.i("test", "One:" + "onRestart" + ",判断是否处于分屏模式:" + this.isInMultiWindowMode() + ",判断是否处于画中画模式" + this.isInPictureInPictureMode());
} else {
Log.i("test", "One:" + "onRestart");
}
}
//分屏模式从失去焦点——》得到焦点,执行
@Override
protected void onResume() {
super.onResume();
Log.i("test", "One:" + "onResume");
}
//分屏模式下失去焦点,执行
@Override
protected void onPause() {
super.onPause();
Log.i("test", "One:" + "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.i("test", "One:" + "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i("test", "One:" + "onDestroy");
}
//打开第二个activity
public void openSencondActivity(View view) {
//1、当前MainActivity已经进入到分屏模式。
//2、新打开的Main2Activity支持分屏浏览(即android:resizeableActivity=true)。
//满足1和2,打开的Main2Activity将与MainActivity实现分屏
startActivity(new Intent(this, Main2Activity.class).addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK));
/**
设置FLAG_ACTIVITY_NEW_TASK的原因:
1、在同一个Activity返回栈中,打开一个新的Activity时,这个Activity将会继承上一个Activity所有和分屏模式有关的属性。
如果你想要在一个独立的窗口以分屏模式打开一个新的Activity,那么必须新建一个Activity返回栈。
*/
/**
此外,如果你的设备支持自由模式(官方名字叫freeform,暂且就这么翻译它,其实我认为这算也是一种尺寸更自由的分屏模式,
如果设备厂商支持用户可以自由改变Activity的尺寸,那么就相当于支持自由模式,这将比普通的分屏模式更加自由),
打开一个Activity时,还可通过ActivityOptions.setLaunchBounds()来指定新的Activity的尺寸和在屏幕中的位置。
同样,这个方法也需要你的Activity已经处于分屏模式时,调用它才会生效。
*/
}
/**
* 支持拖放
* 用户可以在两个 Activity 共享屏幕的同时在这两个 Activity 之间拖放数据 (在此之前,用户只能在一个 Activity 内部拖放数据)。
*/
public void setImageViewEvent() {
imageView = (ImageView) findViewById(R.id.imageView);
/** 拖拽的发送方Activity和ImageView */
imageView.setTag("I'm a ImageView from MainActivity");
imageView.setOnTouchListener(new View.OnTouchListener() {
@RequiresApi(api = Build.VERSION_CODES.N)
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);
MyDragShadowBuilder shadow = new MyDragShadowBuilder(imageView);//替换当前正在进行的拖动操作的拖动阴影。只能由发起拖动操作的应用调用
/** startDragAndDrop是Android N SDK中的新方法,替代了以前的startDrag(),
*flag需要设置为DRAG_FLAG_GLOBAL ( 需要传递View.DRAG_FLAG_GLOBAL来实现跨Activity拖拽。)
* 如果需要将URI权限传递给接收方Activity,还可以根据需要设置View.DRAG_FLAG_GLOBAL_URI_READ或者View.DRAG_FLAG_GLOBAL_URI_WRITE。
* */
view.startDragAndDrop(dragData, shadow, null, View.DRAG_FLAG_GLOBAL);
/**
* Activity.requestDropPermissions()
请求使用 DragEvent 中包含的 ClipData 传递的内容 URI 的权限。
*/
return true;
} else {
return false;
}
}
});
}
class MyDragShadowBuilder extends View.DragShadowBuilder {
// The drag shadow image, defined as a drawable thing
private Drawable shadow;
// Defines the constructor for myDragShadowBuilder
public MyDragShadowBuilder(View v) {
// Stores the View parameter passed to myDragShadowBuilder.
super(v);
// Creates a draggable image that will fill the Canvas provided by the system.
shadow = new ColorDrawable(Color.LTGRAY);
}
// Defines a callback that sends the drag shadow dimensions and touch point back to the
// system.
@Override
public void onProvideShadowMetrics(Point size, Point touch) {
// Defines local variables
int width, height;
// Sets the width of the shadow to half the width of the original View
width = getView().getWidth() / 2;
// Sets the height of the shadow to half the height of the original View
height = getView().getHeight() / 2;
// The drag shadow is a ColorDrawable. This sets its dimensions to be the same as the
// Canvas that the system will provide. As a result, the drag shadow will fill the
// Canvas.
shadow.setBounds(0, 0, width, height);
// Sets the size parameter's width and height values. These get back to the system
// through the size parameter.
size.set(width, height);
// Sets the touch point's position to be in the middle of the drag shadow
touch.set(width / 2, height / 2);
}
// Defines a callback that draws the drag shadow in a Canvas that the system constructs
// from the dimensions passed in onProvideShadowMetrics().
@Override
public void onDrawShadow(Canvas canvas) {
// Draws the ColorDrawable in the Canvas passed in from the system.
shadow.draw(canvas);
}
}
}
activity_main.xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.tengda.android_70two_pager.MainActivity"
tools:ignore="MissingConstraints">
<Button
android:text="在另一块分屏中打开secondActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
android:onClick="openSencondActivity"
app:layout_constraintTop_toTopOf="@+id/activity_main"
app:layout_constraintLeft_toLeftOf="@+id/activity_main"
tools:ignore="HardcodedText"
app:layout_constraintRight_toRightOf="@+id/activity_main"
app:layout_constraintBottom_toBottomOf="@+id/activity_main"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@mipmap/ic_launcher"
android:id="@+id/imageView"
app:layout_constraintBottom_toTopOf="@+id/button"
android:layout_marginBottom="24dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="@+id/activity_main"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="@+id/activity_main"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="@+id/activity_main"
tools:ignore="ContentDescription" />
<Button
android:text="打开画中画Activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button2"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/button"
app:layout_constraintBottom_toBottomOf="@+id/activity_main"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="@+id/activity_main"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="@+id/activity_main"
tools:ignore="HardcodedText"
android:onClick="openMain3Activity" />
</android.support.constraint.ConstraintLayout>
- Main2Activity
public class Main2Activity extends AppCompatActivity {
TextView dropedText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
getFromMainActivityMsg();
}
//接收从MainActivity拖过来的图片中的消息
public void getFromMainActivityMsg() {
dropedText = (TextView) findViewById(R.id.textView);
dropedText.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View view, DragEvent dragEvent) {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
Log.d("test", "Action is DragEvent.ACTION_DRAG_STARTED");
break;
case DragEvent.ACTION_DRAG_ENTERED:
Log.d("test", "Action is DragEvent.ACTION_DRAG_ENTERED");
break;
case DragEvent.ACTION_DRAG_EXITED:
Log.d("test", "Action is DragEvent.ACTION_DRAG_EXITED");
break;
case DragEvent.ACTION_DRAG_LOCATION:
break;
case DragEvent.ACTION_DRAG_ENDED:
Log.d("test", "Action is DragEvent.ACTION_DRAG_ENDED");
break;
case DragEvent.ACTION_DROP:
Log.d("test", "ACTION_DROP event");
/** 3.在这里显示接收到的结果 */
dropedText.setText(dragEvent.getClipData().getItemAt(0).getText());
break;
default:
break;
}
return true;
}
});
}
}
layout/activity_main2.xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.tengda.android_70two_pager.Main2Activity"
tools:ignore="MissingConstraints">
<TextView
android:text="第二个Activity"
android:layout_width="190dp"
android:layout_height="115dp"
android:id="@+id/textView"
android:gravity="center_horizontal|center_vertical"
app:layout_constraintTop_toTopOf="@+id/activity_main2"
app:layout_constraintLeft_toLeftOf="@+id/activity_main2"
app:layout_constraintRight_toRightOf="@+id/activity_main2"
app:layout_constraintBottom_toBottomOf="@+id/activity_main2"
tools:ignore="HardcodedText" />
</android.support.constraint.ConstraintLayout>
注:
1. 使用Android N 中的约束布局 android.support.constraint.ConstraintLayout需在build.gradle——》dependencies——》添加 compile ‘com.android.support.constraint:constraint-layout:1.0.0-alpha2’(使用起来还是比较简单的。。。)
2. 画中画现只支持Android_tv
附:
1. 官方文档之多窗口支持连接:https://developer.android.com/guide/topics/ui/multi-window.html?hl=zh-cn#overview
2. 官方文档之画中画:https://developer.android.com/training/tv/playback/picture-in-picture.html?hl=zh-cn#pip_button