Android仿抖音上下滑动切换视频
自从各大直播平台可以滑动切换直播间后,公司就出了一大波需求,还要配合各种收费,各种VIP,很是头疼(haha 主要是我这个人很懒,不想加班),后来研究了下 ,也查阅了一些别人写的demo和一些想法,也对此有了一些理解。
- 1 最开始是打算用RecyclerView来实现的,因为他的复用性很强,用起来也很方便,和SnapHelper相结合便可以实现滑动分页的功能。
什么是 SnapHelper
?
SnapHelper是一个抽象类,官方提供了一个LinearSnapHelper的子类,可以让RecyclerView滚动停止时相应的Item停留中间位置。在25.1.0版本中,官方又提供了一个PagerSnapHelper的子类,可以使RecyclerView像ViewPager一样的效果,一次只能滑一页,而且居中显示。详细源码解读可以看这里让你明明白白的使用RecyclerView——SnapHelper详解,这里我们用到的就是PagerSnapHelper。
如何使用
使用非常简单,只需要创建对象之后调用attachToRecyclerView()附着到对应的RecyclerView对象上就可以了。
snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(rvPage2);
设置Adapter
videoAdapter = new ListVideoAdapter(urlList);
layoutManager = new LinearLayoutManager(Page2Activity.this, LinearLayoutManager.VERTICAL, false);
rvPage2.setLayoutManager(layoutManager);
rvPage2.setAdapter(videoAdapter);
这样我们就只需要监听RecyclerView的滚动,然后就可以实现我们的逻辑了
rvPage2.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE://停止滚动
View view = snapHelper.findSnapView(layoutManager);
//TODO 销毁所有视频
RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(view);
if (viewHolder != null && viewHolder instanceof VideoViewHolder) {
//TODO 启动想要播放的视频
}
break;
case RecyclerView.SCROLL_STATE_DRAGGING://拖动
break;
case RecyclerView.SCROLL_STATE_SETTLING://惯性滑动
break;
}
}
});
- 2 不过RecyclerView有个问题,虽然可以达到切换到效果,但是如果我是上下切换,当我左右快速滑动的时候,也会造成上下切换,当然了,这个可以去监听他的触摸事件,只是每一个item里面还有很多事件要处理,冲突性和复杂性会增加很多,就将其设置为备选方案了。所以后来接触到上下切换的VerticalViewPager,就有了其他的方案。
- A 和V4包的ViewPager使用一样,适配FragmentPagerAdapter,加载多个Fragment,这样的方式其实很简单,很粗暴,不过性能也是很差的,不建议使用
- B 适配PagerAdapter,监听setPageTransformer,加载新的数据,通过消息传递到Fragment,刷新数据
class PagerAdapter extends android.support.v4.view.PagerAdapter {
private List list;
public PagerAdapter(List list) {
this.list = list;
}
@Override
public int getCount() {
if (list.size() > 1) {
return Integer.MAX_VALUE;
} else {
return 1;
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View view = LayoutInflater.from(container.getContext()).inflate(R.layout.live_activity_audience_mask_layout, null);
int pos = position % list.size();
HnLiveListModel.LiveListBean data = list.get(pos);
//遮罩层
FrescoImageView mFrescoImageView = (FrescoImageView) view.findViewById(R.id.fiv_mask);
mFrescoImageView.setVisibility(View.VISIBLE);
if (data != null) {
String avator = data.getAvator();
mFrescoImageView.setController(FrescoConfig.getController(avator));
}
view.setId(position);
container.addView(view);
return view;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(container.findViewById(position));
}
}
mVerticalViewPager.setPageTransformer(false, new ViewPager.PageTransformer() {
private float yPosition;
public float getPosition() {
return yPosition;
}
@Override
public void transformPage(View page, float position) {
page.setTranslationX(page.getWidth() * -position);
yPosition = position * page.getHeight();
page.setTranslationY(yPosition);
ViewGroup viewGroup = (ViewGroup) page;
HnLogUtils.i(TAG, "page.id == " + page.getId() + ", position == " + position);
if ((position < 0 && viewGroup.getId() != mCurrentItem)) {
View roomContainer = viewGroup.findViewById(R.id.room_container);
if (roomContainer != null && roomContainer.getParent() != null && roomContainer.getParent() instanceof ViewGroup) {
((ViewGroup) (roomContainer.getParent())).removeView(roomContainer);
}
}
// 满足此种条件,表明需要加载直播视频,以及聊天室了
if (viewGroup.getId() == mCurrentItem && position == 0 && mCurrentItem != mRoomUid) {
if (mRoomContainer.getParent() != null && mRoomContainer.getParent() instanceof ViewGroup) {
((ViewGroup) (mRoomContainer.getParent())).removeView(mRoomContainer);
}
EventBus.getDefault().post(new HnLiveEvent(0, HnLiveConstants.EventBus.Close_Dialog, 0));
EventBus.getDefault().post(new HnLiveEvent(0, HnLiveConstants.EventBus.Hide_Mask, 0));
loadVideoAndChatRoom(viewGroup, mCurrentItem);
}
}
});
/**
* 加载房间信息
*
* @param viewGroup
* @param mCurrentItem
*/
private void loadVideoAndChatRoom(ViewGroup viewGroup, int mCurrentItem) {
pos = mCurrentItem % list.size();
HnLogUtils.i(TAG, "当前加载的位置:" + pos + "--->" + mCurrentItem);
HnLiveListModel.LiveListBean bean = list.get(pos);
//聊天室的fragment只加载一次,以后复用
if (!mInit) {
mRoomFragment = HnAudienceRoomFragment.newInstance(bean);
mFragmentManager.beginTransaction().replace(R.id.fragment_container, mRoomFragment).commitAllowingStateLoss();
mInit = true;
} else {
if (mRoomFragment == null) {
mRoomFragment = HnAudienceRoomFragment.newInstance(bean);
mFragmentManager.beginTransaction().replace(R.id.fragment_container, mRoomFragment).commitAllowingStateLoss();
mInit = true;
}
EventBus.getDefault().post(new HnLiveEvent(0, HnLiveConstants.EventBus.Update_Room_Info, bean));
}
viewGroup.addView(mRoomContainer);
this.mRoomUid = mCurrentItem;
}
- C 适配PagerAdapter,初始化每个Item的View ,以View为数据源,适配到adapter中(不过直播中业务复杂,不推荐在直播中使用,小视频可以使用(直接一次性将数据传递过来)),这个方式主要是为了复用播放器,这样就不要添加多个播放器了
for (HnChatVideoSwitchEntity item : mList) {
if (mActivity == null) return;
View view = LayoutInflater.from(mActivity).inflate(R.layout.adapter_invite_chat, null);
//TODO 初始化控件
//TODO 设置点击事件
//TODO 设置数据
mViews.add(view);
}
adapter里面很简单
public class HnInviteChatAdapter extends PagerAdapter {
private static final String TAG = "DouYinAdapter";
private List mViews;
public HnInviteChatAdapter(List views) {
this.mViews = views;
}
public void setmViews(List mViews) {
this.mViews = mViews;
}
@Override
public int getCount() {
return mViews.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
Log.d(TAG, "instantiateItem: called");
container.addView(mViews.get(position));
return mViews.get(position);
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Log.d(TAG, "destroyItem: ");
container.removeView(mViews.get(position));
}
}
然后当滑动ViewPager时,重置数据就可以了
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mCurrentItem = position;
//TODO视频暂停播放
if (mIjkVideoView != null) {
mIjkVideoView.pause();
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (mPlayingPosition == mCurrentItem) return;
if (state == VerticalViewPager.SCROLL_STATE_IDLE) {
//TODO视频暂停播放
stopPlay();
//TODO 重置一些控件显示与否
releaseView();
ViewParent parent = mIjkVideoView.getParent();
//TODO移除上个页面的视频控件
if (parent != null && parent instanceof FrameLayout) {
((FrameLayout) parent).removeView(mIjkVideoView);
}
getAnchorData(mCurrentItem);
}
}
});
然后就是更新数据了
private void getAnchorData(int position) {
mUid = mList.get(position).getUser_id();
mDbean = mList.get(position);
mPlayUrl = mDbean.getUser_video();
mCurrentItem = position;
View view = mViews.get(mCurrentItem);
RelativeLayout mContainer = view.findViewById(R.id.mContainer);
initItemView(view);
//TODO 设置控件数据和状态
//添加播放器
ViewGroup parent = (ViewGroup) mIjkVideoView.getParent();
if (parent != null) {
parent.removeAllViews();
}
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
mIjkVideoView.setLayoutParams(params);
mContainer.addView(mIjkVideoView, 0, params);
startPlay();
mPlayingPosition = mCurrentItem;
}