实在是不好意思,楼下评论的兄弟久等了,这文章一直没写第一是没时间,第二是自己准备也不充足,最近才看了好几个Android视频播放器的开源项目,才对视频播放器的大小切换有点了解,就我目前的了解,视频播放器的大小屏切换基本有三个方案可选,下面我分别简单地讲一下,至于具体的实现我会给出github的链接,大家直接去看源代码。
所谓让视频播放器悬浮在activity中,就是播放器在内存里面,需要显示的时候就设置一个锚点view,当时小窗口的时候播放器就跟随这个锚点view滚动和重绘,我看到过这样的实现,可以说想法很好,看起来也很完美,下面看一下其大概的源代码:
/**
* 设置锚
*
* @param anchor 锚点view
* @param activity 所在的activity
*/
public void setAnchorView(View anchor, Activity activity) {
if (anchor == null || activity == null) {
return;
}
if (this.anchorView != null) {
ViewTreeObserver observerTmp = anchorView.getViewTreeObserver();
try {
observerTmp.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
observerTmp.removeOnScrollChangedListener(mOnScrollChangedListener);
} catch (Exception e) {
e.printStackTrace();
}
}
this.anchorView = anchor;
this.activity = activity;
final ViewTreeObserver observer = anchorView.getViewTreeObserver();
try {
/**注册滚动和绘制监听器**/
observer.addOnScrollChangedListener(mOnScrollChangedListener);
observer.addOnGlobalLayoutListener(mOnGlobalLayoutListener);
} catch (Exception e) {
e.printStackTrace();
}
Activity act = this.activity;
if (act != null) {
FrameLayout content = (FrameLayout) act.findViewById(android.R.id.content);
if (content.getTag() != null) {
return;
}
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(anchorView.getMeasuredWidth(), anchorView.getMeasuredHeight());
int[] location = new int[2];
anchorView.getLocationInWindow(location);
lp.leftMargin = location[0];
lp.topMargin = location[1];
/**在activity中添加播放器**/
content.addView(this, content.getChildCount(), lp);
content.setTag(true);
}
}
可以看到,这个方式很巧妙,但是,但是,这种方法也有致命的缺点,我们注册了滚动和绘制监听来控制我们的播放器,一旦我们把播放器放在scrollView或者recyclerView中,那就要教你做人了,播放器的晃动非常厉害,由于我们是注册的监听器来确定播放器的位置,这样我们在滚动控件中,视频播放器的位置跟滚动的位置总是慢一拍,导致在滚动控件在播放器有严重的果冻效果,体验很不好,如果可以优化器滚动效果那么这个方法还是很好的。我看到的是使用了surfaceview来播放视频,不知道使用textureview会不会减轻这个果冻效果,不放在滚动控件中只是放大和缩小这种方法还是不错的。
这第一种方法就目前来看不是很推荐!
这种方法小屏切换到大屏是创建了两个播放器,或者说是创建了两个TextureView的容器,从小到大的时候在Activity中创建了一个全屏的播放器容器,并把当前的视频渲染器(TextureView)和监听器都给赋值过去了,由于使用了TextureView,所以在滚动控件中使用也不会用果冻效果,可以说是正真意义的完美的播放器全屏方案。大小切换由于TextureView的容器换了,所以中间会有很短时间的黑屏,但是可以忽略:
/**
* 小屏窗口切换为全屏播放
*/
public void startWindowFullscreen() {
Log.i(TAG, "startWindowFullscreen " + " [" + this.hashCode() + "] ");
hideSupportActionBar(getContext());
/**activity的content layout**/
ViewGroup vp = (ViewGroup) (JCUtils.scanForActivity(getContext())).findViewById(Window.ID_ANDROID_CONTENT);
/**通过id找到播放器自己本身**/
View old = vp.findViewById(FULLSCREEN_ID);
/**如果在设置全屏之前activity的content layout中本来就存在播放器就先移除**/
if (old != null) {
vp.removeView(old);
}
if (textureViewContainer.getChildCount() > 0) {
textureViewContainer.removeAllViews();
}
try {
Constructor constructor = (Constructor) JCVideoPlayer.this.getClass().getConstructor(Context.class);
JCVideoPlayer jcVideoPlayer = constructor.newInstance(getContext());
jcVideoPlayer.setId(FULLSCREEN_ID);
/**获取屏幕的宽高**/
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
int w = wm.getDefaultDisplay().getWidth();
int h = wm.getDefaultDisplay().getHeight();
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(h, w);
lp.setMargins((w - h) / 2, -(w - h) / 2, 0, 0);
vp.addView(jcVideoPlayer, lp);
jcVideoPlayer.setUp(url, JCVideoPlayerStandard.SCREEN_WINDOW_FULLSCREEN, objects);
jcVideoPlayer.setUiWitStateAndScreen(currentState);
/**小窗口和全屏的切换是创建了两个播放器,之间的衔接全靠这个方法**/
jcVideoPlayer.addTextureView();
jcVideoPlayer.setRotation(90);
final Animation ra = AnimationUtils.loadAnimation(getContext(), R.anim.start_fullscreen);
jcVideoPlayer.setAnimation(ra);
/**监听器的替换**/
JCVideoPlayerManager.setLastListener(this);
JCVideoPlayerManager.setListener(jcVideoPlayer);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
这第二种方法比较推荐,但是你要很懂播放器,对源代码很熟悉,由于是直接对ijkMediaplayer进行的封装,中间还会对视频的播放监听器进行切换,所以还是有点复杂的,这方法可以做到真正的全屏,无视是否有ActionBar什么的,直接全屏。如果你觉得自己能力不错,那就赶紧把这个方法get去吧。
这方法需要在Activity中预留一个放置播放器的宽高都match_parent的ViewGroup,大小切换就是把播放器添加到本来的小容器和添加到全屏的ViewGroup中来回切换,对于播放器的监听器也不用过多干预,是比较通俗易懂简单的方法:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (player != null) {
/**
* 在activity中监听到横竖屏变化时调用播放器的监听方法来实现播放器大小切换
*/
player.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
fullScreen.setVisibility(View.GONE);
fullScreen.removeAllViews();
superRecyclerView.setVisibility(View.VISIBLE);
if (postion <= mLayoutManager.findLastVisibleItemPosition()
&& postion >= mLayoutManager.findFirstVisibleItemPosition()) {
View view = superRecyclerView.findViewHolderForAdapterPosition(postion).itemView;
FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.adapter_super_video);
frameLayout.removeAllViews();
ViewGroup last = (ViewGroup) player.getParent();//找到videoitemview的父类,然后remove
if (last != null) {
last.removeAllViews();
}
frameLayout.addView(player);
}
int mShowFlags =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
fullScreen.setSystemUiVisibility(mShowFlags);
} else {
ViewGroup viewGroup = (ViewGroup) player.getParent();
if (viewGroup == null)
return;
viewGroup.removeAllViews();
fullScreen.addView(player);
fullScreen.setVisibility(View.VISIBLE);
int mHideFlags =
View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
;
fullScreen.setSystemUiVisibility(mHideFlags);
}
} else {
fullScreen.setVisibility(View.GONE);
}
}
这个方法是主动来切换activity的横竖屏来通知播放器和activity来对大小屏的切换里做出处理,当然你也可以不用onConfigurationChanged方法来间接处理,自己写一个方法来处理,并旋转播放器90度也是可以的,但是这种方法要对activity的ActionBar做处理,要是有ActionBar就需要hide。
这是我自己参考第三种方法实现的,我增加了再Fragment、Activity和RecyclerView中放大缩小播放器,基本涵盖了我们遇到的所有场景了。有第三种方法是在Activity中预留容器,所以要是有ActionBar的Activity在放大播放器后,ActionBar并不会主动隐藏,需要我们主动hide和show ActionBar:
/**
* 隐藏ActionBar
*/
private void hideActionBar(){
if(mainActivity.getSupportActionBar() != null)
mainActivity.getSupportActionBar().hide();
}
/**
* 显示ActionBar
*/
private void showActionBar(){
if(mainActivity.getSupportActionBar() != null)
mainActivity.getSupportActionBar().show();
}
要是在Fragment中使用,就要在Fragment中找到Activity中预留的容器:
/***
* 全屏播放器放在MainActivity中
*/
fullScreen = (RelativeLayout) mainActivity.findViewById(R.id.full_screen);
在fragment和Activity中使用都要在方法中做出相应操作,下面是在Fragment中使用:
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mdPlayer != null) {
/**
* 在activity中监听到横竖屏变化时调用播放器的监听方法来实现播放器大小切换
*/
mdPlayer.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
showActionBar();
fullScreen.setVisibility(View.GONE);
fullScreen.removeAllViews();
videoRecyclerView.setVisibility(View.VISIBLE);
if (postion <= mLayoutManager.findLastVisibleItemPosition()
&& postion >= mLayoutManager.findFirstVisibleItemPosition()) {
View view = videoRecyclerView.findViewHolderForAdapterPosition(postion).itemView;
FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.adapter_super_video);
frameLayout.removeAllViews();
ViewGroup last = (ViewGroup) mdPlayer.getParent();//找到videoitemview的父类,然后remove
if (last != null) {
last.removeAllViews();
}
frameLayout.addView(mdPlayer);
}
int mShowFlags =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
fullScreen.setSystemUiVisibility(mShowFlags);
} else {
ViewGroup viewGroup = (ViewGroup) mdPlayer.getParent();
if (viewGroup == null)
return;
hideActionBar();
viewGroup.removeAllViews();
fullScreen.addView(mdPlayer);
fullScreen.setVisibility(View.VISIBLE);
int mHideFlags =
View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
;
fullScreen.setSystemUiVisibility(mHideFlags);
}
} else {
fullScreen.setVisibility(View.GONE);
}
super.onConfigurationChanged(newConfig);
}