项目中视频使用的是 GSYVideoPlayer 这个开源库。
既然要实现类似微博那种滚动列表时,处于当前屏幕的项自动播放,那么肯定得监听列表的滚动事件,好在 Recyclerview 中给我们提供了接口:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case SCROLL_STATE_IDLE: //滚动停止
break;
case SCROLL_STATE_DRAGGING: //手指拖动
break;
case SCROLL_STATE_SETTLING: //惯性滚动
break;
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
如上 onScrollStateChanged 方法 给我们返回了三种状态: SCROLL_STATE_DRAGGING: 手指按住屏幕拖动 SCROLL_STATE_SETTLING: 手指快速在屏幕滑一下后的惯性滑动 SCROLL_STATE_IDLE: 屏幕处于禁止状态
而 onScrolled 方法给我们返回了 dx:水平滚动距离、dy:垂直滚动距离。这两个值都是用手指开始触摸的位置减去移动后的位置,所以: dx > 0 时为手指向左滑动,列表滚动显示右面的内容 dx < 0 时为手指向右滑动,列表滚动显示左面的内容 dy > 0 时为手指向上滑动,列表滚动显示下面的内容 dy < 0 时为手指向下滑动,列表滚动显示上面的内容 项目中暂时没用到这些,但是这些值很有用。
在这个方法中我们需要获取三个值: (1) 在屏幕可见区域的第一项位置 : 通过 findFirstVisibleItemPosition()
方法获取 (2) 在屏幕可见区域的最后一项位置 : 通过 findLastVisibleItemPosition()
方法获取 (3)屏幕可见项的数目 : 用(2)减去(1)即可
为什么需要以上值,这里说下整体思路: (1)获取当前处于屏幕可见的列表 (2)滑出屏幕的视频我们需要回收掉 (3)当屏幕处于静止状态时我们才开始播放视频
第一点:获取可见列表;上面获取的三个值已经解决了,直接看代码:
public int firstVisibleItem, lastVisibleItem, visibleCount;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
lastVisibleItem = layoutManager.findLastVisibleItemPosition();
visibleCount = lastVisibleItem - firstVisibleItem;
}
第二点:回收滑出屏幕的视频,获取到当前播放的位置(开源库中有相关方法)判断是否在屏幕可见,不可见就回收;具体看代码
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//大于0说明有播放
if (GSYVideoManager.instance().getPlayPosition() >= 0) {
//当前播放的位置
int position = GSYVideoManager.instance().getPlayPosition();
//对应的播放列表TAG
if (GSYVideoManager.instance().getPlayTag().equals(HomeAdapter.TAG) && (position lastVisibleItem)) {
GSYVideoManager.releaseAllVideos();
}
}
}
第三点:屏幕处于静止时才开始播放,只要播放的逻辑写在 onScrollStateChanged
的 SCROLL_STATE_IDLE
状态下即可;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case SCROLL_STATE_IDLE: //滚动停止
autoPlayVideo(recyclerView);
break;
}
}
private void autoPlayVideo(RecyclerView view) {
RecyclerView.LayoutManager layoutManager = view.getLayoutManager();
for (int i = 0; i
list_video_item.xml
video_layout_cover.xml
4. 自定义控件 SampleCoverVideo.java
/**
* Created on 2022/6/1 14:07
*
* @author Gong Youqiang
*/
public class SampleCoverVideo extends StandardGSYVideoPlayer {
ImageView mCoverImage;
String mCoverOriginUrl;
int mCoverOriginId = 0;
int mDefaultRes;
public SampleCoverVideo(Context context, Boolean fullFlag) {
super(context, fullFlag);
}
public SampleCoverVideo(Context context) {
super(context);
}
public SampleCoverVideo(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void init(Context context) {
super.init(context);
mCoverImage = (ImageView) findViewById(R.id.thumb_image);
if (mThumbImageViewLayout != null &&
(mCurrentState == -1 || mCurrentState == CURRENT_STATE_NORMAL || mCurrentState == CURRENT_STATE_ERROR)) {
mThumbImageViewLayout.setVisibility(VISIBLE);
}
}
@Override
public int getLayoutId() {
return R.layout.video_layout_cover;
}
public void loadCoverImage(String url, int res) {
mCoverOriginUrl = url;
mDefaultRes = res;
Glide.with(getContext().getApplicationContext())
.setDefaultRequestOptions(
new RequestOptions()
.frame(1000000)
.centerCrop()
.error(res)
.placeholder(res))
.load(url)
.into(mCoverImage);
}
public void loadCoverImageBy(int id, int res) {
mCoverOriginId = id;
mDefaultRes = res;
mCoverImage.setImageResource(id);
}
@Override
public GSYBaseVideoPlayer startWindowFullscreen(Context context, boolean actionBar, boolean statusBar) {
GSYBaseVideoPlayer gsyBaseVideoPlayer = super.startWindowFullscreen(context, actionBar, statusBar);
SampleCoverVideo sampleCoverVideo = (SampleCoverVideo) gsyBaseVideoPlayer;
if(mCoverOriginUrl != null) {
sampleCoverVideo.loadCoverImage(mCoverOriginUrl, mDefaultRes);
} else if(mCoverOriginId != 0) {
sampleCoverVideo.loadCoverImageBy(mCoverOriginId, mDefaultRes);
}
return gsyBaseVideoPlayer;
}
@Override
public GSYBaseVideoPlayer showSmallVideo(Point size, boolean actionBar, boolean statusBar) {
//下面这里替换成你自己的强制转化
SampleCoverVideo sampleCoverVideo = (SampleCoverVideo) super.showSmallVideo(size, actionBar, statusBar);
sampleCoverVideo.mStartButton.setVisibility(GONE);
sampleCoverVideo.mStartButton = null;
return sampleCoverVideo;
}
@Override
protected void cloneParams(GSYBaseVideoPlayer from, GSYBaseVideoPlayer to) {
super.cloneParams(from, to);
SampleCoverVideo sf = (SampleCoverVideo) from;
SampleCoverVideo st = (SampleCoverVideo) to;
st.mShowFullAnimation = sf.mShowFullAnimation;
}
/**
* 退出window层播放全屏效果
*/
@SuppressWarnings("ResourceType")
@Override
protected void clearFullscreenLayout() {
if (!mFullAnimEnd) {
return;
}
mIfCurrentIsFullscreen = false;
int delay = 0;
// ------- !!!如果不需要旋转屏幕,可以不调用!!!-------
// 不需要屏幕旋转,还需要设置 setNeedOrientationUtils(false)
if (mOrientationUtils != null) {
delay = mOrientationUtils.backToProtVideo();
mOrientationUtils.setEnable(false);
if (mOrientationUtils != null) {
mOrientationUtils.releaseListener();
mOrientationUtils = null;
}
}
if (!mShowFullAnimation) {
delay = 0;
}
final ViewGroup vp = (CommonUtil.scanForActivity(getContext())).findViewById(Window.ID_ANDROID_CONTENT);
final View oldF = vp.findViewById(getFullId());
if (oldF != null) {
//此处fix bug#265,推出全屏的时候,虚拟按键问题
SampleCoverVideo gsyVideoPlayer = (SampleCoverVideo) oldF;
gsyVideoPlayer.mIfCurrentIsFullscreen = false;
}
if (delay == 0) {
backToNormal();
} else {
postDelayed(new Runnable() {
@Override
public void run() {
backToNormal();
}
}, delay);
}
}
/******************* 下方两个重载方法,在播放开始前不屏蔽封面,不需要可屏蔽 ********************/
@Override
public void onSurfaceUpdated(Surface surface) {
super.onSurfaceUpdated(surface);
if (mThumbImageViewLayout != null && mThumbImageViewLayout.getVisibility() == VISIBLE) {
mThumbImageViewLayout.setVisibility(INVISIBLE);
}
}
@Override
protected void setViewShowState(View view, int visibility) {
if (view == mThumbImageViewLayout && visibility != VISIBLE) {
return;
}
super.setViewShowState(view, visibility);
}
@Override
public void onSurfaceAvailable(Surface surface) {
super.onSurfaceAvailable(surface);
if (GSYVideoType.getRenderType() != GSYVideoType.TEXTURE) {
if (mThumbImageViewLayout != null && mThumbImageViewLayout.getVisibility() == VISIBLE) {
mThumbImageViewLayout.setVisibility(INVISIBLE);
}
}
}
/******************* 下方重载方法,在播放开始不显示底部进度和按键,不需要可屏蔽 ********************/
protected boolean byStartedClick;
@Override
protected void onClickUiToggle(MotionEvent e) {
if (mIfCurrentIsFullscreen && mLockCurScreen && mNeedLockFull) {
setViewShowState(mLockScreen, VISIBLE);
return;
}
byStartedClick = true;
super.onClickUiToggle(e);
}
@Override
protected void changeUiToNormal() {
super.changeUiToNormal();
byStartedClick = false;
}
@Override
protected void changeUiToPreparingShow() {
super.changeUiToPreparingShow();
Debuger.printfLog("Sample changeUiToPreparingShow");
setViewShowState(mBottomContainer, INVISIBLE);
setViewShowState(mStartButton, INVISIBLE);
}
@Override
protected void changeUiToPlayingBufferingShow() {
super.changeUiToPlayingBufferingShow();
Debuger.printfLog("Sample changeUiToPlayingBufferingShow");
if (!byStartedClick) {
setViewShowState(mBottomContainer, INVISIBLE);
setViewShowState(mStartButton, INVISIBLE);
}
}
@Override
protected void changeUiToPlayingShow() {
super.changeUiToPlayingShow();
Debuger.printfLog("Sample changeUiToPlayingShow");
if (!byStartedClick) {
setViewShowState(mBottomContainer, INVISIBLE);
setViewShowState(mStartButton, INVISIBLE);
}
}
@Override
public void startAfterPrepared() {
super.startAfterPrepared();
Debuger.printfLog("Sample startAfterPrepared");
setViewShowState(mBottomContainer, INVISIBLE);
setViewShowState(mStartButton, INVISIBLE);
setViewShowState(mBottomProgressBar, VISIBLE);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
byStartedClick = true;
super.onStartTrackingTouch(seekBar);
}
}
5. 工具类 ScrollCalculatorHelper.java
/**
* Created on 2022/6/1 14:32
*
* @author Gong Youqiang
*/
public class ScrollCalculatorHelper {
private int firstVisible = 0;
private int lastVisible = 0;
private int visibleCount = 0;
private int playId;
private int rangeTop;
private int rangeBottom;
private PlayRunnable runnable;
private Handler playHandler = new Handler();
public ScrollCalculatorHelper(int playId, int rangeTop, int rangeBottom) {
this.playId = playId;
this.rangeTop = rangeTop;
this.rangeBottom = rangeBottom;
}
public void onScrollStateChanged(RecyclerView view, int scrollState) {
switch (scrollState) {
case RecyclerView.SCROLL_STATE_IDLE:
playVideo(view);
break;
}
}
public void onScroll(RecyclerView view, int firstVisibleItem, int lastVisibleItem, int visibleItemCount) {
if (firstVisible == firstVisibleItem) {
return;
}
firstVisible = firstVisibleItem;
lastVisible = lastVisibleItem;
visibleCount = visibleItemCount;
}
void playVideo(RecyclerView view) {
if (view == null) {
return;
}
RecyclerView.LayoutManager layoutManager = view.getLayoutManager();
GSYBaseVideoPlayer gsyBaseVideoPlayer = null;
boolean needPlay = false;
for (int i = 0; i = rangeTop && rangePosition = Build.VERSION_CODES.LOLLIPOP) {
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
getWindow().setEnterTransition(new Explode());
getWindow().setExitTransition(new Explode());
}
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
resolveData();
mTopBar.setTitle("视频号");
//限定范围为屏幕一半的上下偏移180
int playTop = CommonUtil.getScreenHeight(this) / 2 - CommonUtil.dip2px(this, 180);
int playBottom = CommonUtil.getScreenHeight(this) / 2 + CommonUtil.dip2px(this, 180);
//自定播放帮助类
scrollCalculatorHelper = new ScrollCalculatorHelper(R.id.video_item_player, playTop, playBottom);
final RecyclerNormalAdapter recyclerNormalAdapter = new RecyclerNormalAdapter(this, dataList);
linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
mRecyclerView.addItemDecoration(new MyItemDecoration());
mRecyclerView.setAdapter(recyclerNormalAdapter);
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
int firstVisibleItem, lastVisibleItem;
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
scrollCalculatorHelper.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
//这是滑动自动播放的代码
if (!mFull) {
scrollCalculatorHelper.onScroll(recyclerView, firstVisibleItem, lastVisibleItem, lastVisibleItem - firstVisibleItem);
}
}
});
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//如果旋转了就全屏
mFull = newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_USER;
}
@Override
public void onBackPressed() {
if (GSYVideoManager.backFromWindowFull(this)) {
return;
}
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
GSYVideoManager.onPause();
}
@Override
protected void onResume() {
super.onResume();
GSYVideoManager.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
GSYVideoManager.releaseAllVideos();
}
private void resolveData() {
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?