图6 Android N设备上TextureView执行滑动在Android N的设备上,执行滑动和缩放操作时,SurfaceView有黑边,TextureView没有黑边。这里的滑动和缩放操作是通过修改SurfaceView的LayoutParam来实现的,而不是执行动画。
二、交互时无缝播放视频
在大屏和小窗之间切换时,因为重新创建了播放器,导致需要重新加载视频,不能平滑的过渡。通过单例播放器,将视频渲染到大屏和小窗视频控件,这样可以做到无缝播放视频,平滑加载视频,给用户平滑的过渡体验。
了解小窗播放视频原理后,那么有哪些方案可以实现小窗播放视频功能呢?以下对这些方案进行对比分析。
三、小窗播放视频的实现
1、视频播放控件内嵌到应用布局
如下代码所示,将TextureView内嵌到应用布局内,父容器是一个可以跟随手势缩放的控件——DragVideoView,同时还有一个View用来展示视频的描述,这样将视频播放页分为播放器(Player
)和描述(Desc
)。
<com.iamlarry.floatwindowdemo.drag.DragVideoView
android:id="@+id/drag_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/player"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ff000000" />
<View
android:id="@+id/desc"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent" />
</com.iamlarry.floatwindowdemo.drag.DragVideoView>
如下DragVideoView的代码所示,在onMeasure中,测量Player和Desc的宽高。在onLayout时,Desc的大小和位置会受到Player的大小和mTop的影响;Player也会受到mTop的影响。mTop就是手势滑动时距离屏幕最上方的距离,这样就做到了如图5、图6的视频跟随手指滑动的效果。
//先测量Player和Desc的宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measurePlayer(widthMeasureSpec, heightMeasureSpec);
measureDesc(widthMeasureSpec, heightMeasureSpec);
}
//将Desc放置到Player的下面
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mDragDirect != HORIZONTAL) {
mLeft = this.getWidth() - this.getPaddingRight() - this.getPaddingLeft() - mPlayer.getMeasuredWidth();
mDesc.layout(mLeft, mTop + mPlayer.getMeasuredHeight(), mLeft + mDesc.getMeasuredWidth(), mTop + mDesc.getMeasuredHeight());
}
mPlayer.layout(mLeft, mTop, mLeft + mPlayer.getMeasuredWidth(), mTop + mPlayer.getMeasuredHeight());
}
如下代码所示,DragVideoView的手势交给DragHelper管理。在slideVerticalTo方法中计算mPlayer的起始位置,控制Player滑动,从而带动Desc滑动。
//DragVideoView.java
//手势交给DragHelper管理
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return mDragHelper.shouldInterceptTouchEvent(event);
}
//滑动到垂直方向上某位置
private boolean slideVerticalTo(float slideOffset) {
int topBound = mMinTop;
int y = (int) (topBound + slideOffset * mVerticalRange);
if (mDragHelper.smoothSlideViewTo(mPlayer, mIsMinimum ?
(int) (mPlayerMaxWidth * (1 - PLAYER_RATIO)) : getPaddingLeft(), y)) {
ViewCompat.postInvalidateOnAnimation(this);
return true;
}
return false;
}
通过以上代码的实现,可以做到和 YouTube 效果一样的小窗播放视频功能。优点是交互好,交互时平滑播放视频;缺点是只能在应用内小窗播放。
2、WindowManager添加视频播放控件
WindowManagerService管理着多种窗口,如Activity中的PhoneWindow、壁纸窗口(Wallpaper Winodw)、弹出的子窗口(Sub Window),状态栏(Status Bar)以及输入法窗口(Input Method Window)等。应用程序添加窗口到WindowManagerService,是通过调用WindowManagerService的addWindow方法添加的。
public class WindowManagerService extends IWindowManager.Stub …… {
public int addWindow(Session session, IWindow client,……) {
if (attrs.type == TYPE_INPUT_METHOD) {
} else if (attrs.type == TYPE_INPUT_METHOD_DIALOG) {
} else {
if (attrs.type == TYPE_WALLPAPER) {
adjustWallpaperWindowsLocked();
} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
adjustWallpaperWindowsLocked();
}
}
}
}
如上源码所示,attrs.type是Window类型,用来决定Window的显示高度,可以理解为窗口位置的Z轴,Z轴越大,显示在越上层。通常有三种Window类型:
1)Application windows
取值范围从FIRST_APPLICATION_WINDOW
(0x00000001)到 LAST_APPLICATION_WINDOW
(0x00000063),这种Window是普通的顶层Window,应用的层级都在这个范围。
2)Sub windows
取值范围从FIRST_SUB_WINDOW
(0x000003e8)到 LAST_SUB_WINDOW
(0x000007cf) ,这种window一般都和其他顶层window关联在一起,所有的应用都在FIRST_SUB_WINDOW
之下。
3)System windows
取值范围为从 FIRST_SYSTEM_WINDOW
(0x000007d0) 到 LAST_SYSTEM_WINDOW
(0x00000bb7),这种window是特殊的window类型,使用它们必须拥有特别的权限。
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,0, 0,PixelFormat.TRANSPARENT);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
layoutParams.gravity = Gravity.CENTER;
WindowManager windowManager = getWindowManager();
windowManager.addView(floatingButton, layoutParams);
如上代码所示,是使用WindowManager添加视频播放控件的代码,设置type为TYPE_PHONE,可以浮在所有应用之上。通过WindowManager的addView添加的View,会创建新的Window。交互时的滑动手势不能从Acitvity转移到WindowManager,从而无法做到流畅的交互。所以使用这种方案的优点是可以在应用内外播放视频;缺点是需要权限,交互差。
3、Android8.0 的画中画
Android8.0 的画中画功能允许用户将播放视频缩小并显示到其他窗口上方。优点是实现简单,缺点是需要兼容8.0以前的设备。
4、Activity的Dialog模式
Dialog模式的Activity可以悬浮在其他Activity之上。Activity创建了PhoneWindow,通过PhoneWindow显示View,如下图所示表现了Activity
、Window
、View
的关系。