本文主要介紹采用RecyclerView配合PagerSnapHelper實現短視頻滑動播放內容。
1. 主頁內容構建
主頁布局文件定義RecyclerView,為RecyclerView建立對應適配器。
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_little_video"
android:layout_width="match_parent"
android:layout_height="match_parent" />
適配器條目中添加視頻播放容器FrameLayout及封面ImageVIew.
<FrameLayout
android:id="@+id/fl_content_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<ImageView
android:id="@+id/iv_thumb_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</FrameLayout>
2.定義RecyclerView滑動管理
PagerSnapHelper 結合 LinearLayoutManager 實現滑動管理,實現監聽任務。
PagerSnapHelper介紹
PagerSnapHelper can help achieve a similar behavior to
ViewPager. Set both RecyclerView and the items of the RecyclerView.Adapter to have android.view.ViewGroup.LayoutParams#MATCH_PARENT height and width and then attach PagerSnapHelper to the RecyclerView using #attachToRecyclerView(RecyclerView)}.
翻譯:PagerSnapHelper可以幫助實現與以下類似的行為
ViewPager。 將RecyclerView和RecyclerView.Adapter的項目都設置為具有android.view.ViewGroup.LayoutParams#MATCH_PARENT的高度和寬度,然后使用#attachToRecyclerView(RecyclerView)}將PagerSnapHelper附加到RecyclerView。
自定義RecyclerView管理器
RecyclerView管理器為LinearLayoutManager 時,默認為縱向滑動,如果想采用橫向滑動,就設置其滑動方向為RecyclerView.HORIZONTAL。同理,我們也可以這樣采用setOrientation(RecyclerView.HORIZONTAL) 方法去改變滑動方向。
public class PagerLayoutManager extends LinearLayoutManager implements RecyclerView.OnChildAttachStateChangeListener {
private OnPageChangedListener mOnPageChangedListener;
private PagerSnapHelper mSnapHelper;
/**
* 移動方向標記
*/
private int direction;
public PagerLayoutManager(Context context) {
super(context);
mSnapHelper = new PagerSnapHelper();
}
/**
* PagerSnapHelper綁定RecyclerView,同時為監聽RecyclerView子布局附着,脫離,進行滑動頁面內容控制
*
* @param view RecyclerView
*/
@Override
public void onAttachedToWindow(RecyclerView view) {
super.onAttachedToWindow(view);
mSnapHelper.attachToRecyclerView(view);
view.addOnChildAttachStateChangeListener(this);
}
/**
* 滑動狀態改變監聽,滑動完畢后進行播放控制
*
* @param state 滑動狀態
*/
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == RecyclerView.SCROLL_STATE_IDLE) {
View view = mSnapHelper.findSnapView(this);
if (view == null) {
return;
}
int position = getPosition(view);
if (mOnPageChangedListener != null && getChildCount() == 1) {
mOnPageChangedListener.onPageSelected(position, position == getItemCount() - 1);
}
}
}
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
direction = dx;
return super.scrollHorizontallyBy(dx, recycler, state);
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
direction = dy;
return super.scrollVerticallyBy(dy, recycler, state);
}
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
if (mOnPageChangedListener != null && getChildCount() == 1) {
mOnPageChangedListener.onPageInitComplete();
}
}
@Override
public void onChildViewDetachedFromWindow(@NonNull View view) {
if (mOnPageChangedListener != null) {
mOnPageChangedListener.onPageRelease(getPosition(view), direction >= 0);
}
}
public void setOnPageChangedListener(OnPageChangedListener mOnPageChangedListener) {
this.mOnPageChangedListener = mOnPageChangedListener;
}
public interface OnPageChangedListener {
/**
* 初始化子布局加載完成
*/
void onPageInitComplete();
/**
* 子布局脫離
*
* @param position 子布局在RecyclerView位置
* @param isNext 是否有下一個
*/
void onPageRelease(int position, boolean isNext);
/**
* 子布局附着
*
* @param position 子布局在RecyclerView位置
* @param isLast 是否最后一個
*/
void onPageSelected(int position, boolean isLast);
}
}
3. 視頻滑動實現
為RecyclerView 設置管理器PagerLayoutManager,設置其Adapter數據內容,進行封面展示,且此時會回調onPageInitComplete()方法,進行首個視頻播放。對RecyclerView進行滑動,當頁面滑動結束后,會先回調管理器中onPageRelease()方法,此時可對進行中播放器進行停止釋放;然后,回調onPageSelected()方法,對選中頁面內容進行展示播放。
當滑動后取消時,要進行判斷當前位置,避免當前頁視頻停止或重復播放。
mPagerLayoutManager = new PagerLayoutManager(this);
mPagerLayoutManager.setOnPageChangedListener(new PagerLayoutManager.OnPageChangedListener() {
@Override
public void onPageInitComplete() {
int position = mPagerLayoutManager.findFirstVisibleItemPosition();
if (position != -1) {
mCurrentPosition = position;
}
startPlay(mCurrentPosition);
}
@Override
public void onPageRelease(int position, boolean isNext) {
if (mCurrentPosition == position) {
stopPlay();
BaseViewHolder viewHolder = (BaseViewHolder) mRvLittleVideo.findViewHolderForLayoutPosition(mCurrentPosition);
if (viewHolder != null) {
ImageView mVideoThumb = viewHolder.getView(R.id.iv_thumb_item);
if (mVideoThumb != null) {
mVideoThumb.setVisibility(View.VISIBLE);
}
}
}
}
@Override
public void onPageSelected(int position, boolean isLast) {
if (mCurrentPosition == position) {
return;
}
startPlay(position);
mCurrentPosition = position;
}
});
mRvLittleVideo.setLayoutManager(mPagerLayoutManager);
mLittleVideoAdapter = new LittleVideoAdapter();
mRvLittleVideo.setAdapter(mLittleVideoAdapter);
3. 播放器初始化及停止、播放
初始化播放器內容
private void initVideo() {
mVideoView = new LittleVideoView(this);
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_FULL);
mGsySmallVideoHelperBuilder = new GSYVideoOptionBuilder();
mGsySmallVideoHelperBuilder
.setLooping(true)
.setCacheWithPlay(true)
.setIsTouchWiget(false)
.setVideoAllCallBack(new GSYSampleCallBack() {
@Override
public void onPrepared(String url, Object... objects) {
super.onPrepared(url, objects);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
BaseViewHolder viewHolder = (BaseViewHolder) mRvLittleVideo.findViewHolderForLayoutPosition(mCurrentPosition);
if (viewHolder != null) {
ImageView mVideoThumb = viewHolder.getView(R.id.iv_thumb_item);
if (mVideoThumb != null) {
mVideoThumb.setVisibility(View.INVISIBLE);
}
}
}
}, 100);
}
});
}
開始播放視頻內容,進行播放器視圖加載
private void startPlay(int position) {
if (position < 0 || position >= mLittleVideoAdapter.getData().size()) {
return;
}
BaseViewHolder holder = (BaseViewHolder) mRvLittleVideo.findViewHolderForLayoutPosition(position);
ViewParent parent = mVideoView.getParent();
if (parent instanceof FrameLayout) {
((ViewGroup) parent).removeView(mVideoView);
}
if (holder != null) {
FrameLayout mVideoContent = holder.getView(R.id.fl_content_item);
mVideoContent.addView(mVideoView, 0);
mGsySmallVideoHelperBuilder.setUrl(mLittleVideoAdapter.getData().get(position).getUrl());
mGsySmallVideoHelperBuilder.build(mVideoView);
mVideoView.startPlayLogic();
}
}
停止播放,移除視圖
private void stopPlay() {
mVideoView.release();
ViewParent parent = mVideoView.getParent();
if (parent instanceof FrameLayout) {
((FrameLayout) parent).removeView(mVideoView);
}
}
3. 播放器內容
例子中采用了自定義空布局的播放器繼承自GSY開源播放器,單純進行視頻播放,當然也可以采用其它的播放器餃子或者IjkPlayer等。
public class LittleVideoView extends StandardGSYVideoPlayer {
public LittleVideoView(Context context, Boolean fullFlag) {
super(context, fullFlag);
}
public LittleVideoView(Context context) {
super(context);
}
public LittleVideoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public int getLayoutId() {
return R.layout.empty_control_video;
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<FrameLayout
android:id="@+id/surface_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
</FrameLayout>
</RelativeLayout>
簡單的滑動播放這些就完成了,例子也只是僅僅提供了實現的方法和思路,供大家進行學習參考,實際使用中可以對其進一步地進行封裝及處理,接下來也會補充一些滑動播放適配器的數據加載處理以及多布局內容展示等內容。
Android短視頻滑動播放(二)
關注公眾號:幾圈年輪,查看更多有趣的技術、工具、閑言、資源。