最近公司在做个短视频的项目,其中借鉴了很多抖音的设计,其中就有抖音的上下滑切换视频。
先说下思路,这里用重写了ViewPager的onInterceptTouchEvent和onTouchEvent方法,使其可以上下滑动切换视图。
代码如下
/**
* 作者: ch
* 时间: 2018/7/30 0030-下午 2:53
* 描述:
* 来源:
*/
public class VerticalViewPager extends ViewPager {
private boolean isVertical = false;
public VerticalViewPager(@NonNull Context context) {
super(context);
}
public VerticalViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
private void init() {
// The majority of the magic happens here
setPageTransformer(true, new HorizontalVerticalPageTransformer());
// The easiest way to get rid of the over scroll drawing that happens on the left and right
setOverScrollMode(OVER_SCROLL_NEVER);
}
public boolean isVertical() {
return isVertical;
}
public void setVertical(boolean vertical) {
isVertical = vertical;
init();
}
private class HorizontalVerticalPageTransformer implements PageTransformer {
private static final float MIN_SCALE = 0.75f;
@Override
public void transformPage(View page, float position) {
if (isVertical) {
if (position < -1) {
page.setAlpha(0);
} else if (position <= 1) {
page.setAlpha(1);
// Counteract the default slide transition
float xPosition = page.getWidth() * -position;
page.setTranslationX(xPosition);
//set Y position to swipe in from top
float yPosition = position * page.getHeight();
page.setTranslationY(yPosition);
} else {
page.setAlpha(0);
}
} else {
int pageWidth = page.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
page.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
page.setAlpha(1);
page.setTranslationX(0);
page.setScaleX(1);
page.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
page.setAlpha(1 - position);
// Counteract the default slide transition
page.setTranslationX(pageWidth * -position);
page.setTranslationY(0);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
page.setScaleX(scaleFactor);
page.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
page.setAlpha(0);
}
}
}
}
/**
* Swaps the X and Y coordinates of your touch event.
*/
private MotionEvent swapXY(MotionEvent ev) {
float width = getWidth();
float height = getHeight();
float newX = (ev.getY() / height) * width;
float newY = (ev.getX() / width) * height;
ev.setLocation(newX, newY);
return ev;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isVertical) {
boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
swapXY(ev); // return touch coordinates to original reference frame for any child views
return intercepted;
} else {
return super.onInterceptTouchEvent(ev);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (isVertical) {
return super.onTouchEvent(swapXY(ev));
} else {
return super.onTouchEvent(ev);
}
}
}
在上下滑的时候,不可见时,要暂停视频,可见时重新播放,这里使用的是fragment,通过其生命周期来控制视频的播放与暂停。
通过setUserVisibleHint、onResume、onPause、onDestroy 4个方法处理视频的播放、暂停、重新播放、销毁逻辑
private void initView() {
VerticalViewPagerAdapter pagerAdapter = new VerticalViewPagerAdapter(getSupportFragmentManager());
vvpBackPlay.setVertical(true);
//设置viewpager 缓存数,可以根据需要调整
vvpBackPlay.setOffscreenPageLimit(10);
pagerAdapter.setUrlList(urlList);
vvpBackPlay.setAdapter(pagerAdapter);
}
public class VerticalViewPagerAdapter extends PagerAdapter {
private FragmentManager fragmentManager;
private FragmentTransaction mCurTransaction;
private Fragment mCurrentPrimaryItem = null;
private List<String> urlList;
public void setUrlList(List<String> urlList) {
this.urlList = urlList;
}
public VerticalViewPagerAdapter(FragmentManager fm) {
this.fragmentManager = fm;
}
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = fragmentManager.beginTransaction();
}
VideoFragment fragment = new VideoFragment();
if (urlList != null && urlList.size() > 0) {
Bundle bundle = new Bundle();
if (position >= urlList.size()) {
bundle.putString(VideoFragment.URL, urlList.get(position % urlList.size()));
} else {
bundle.putString(VideoFragment.URL, urlList.get(position));
}
fragment.setArguments(bundle);
}
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), position));
fragment.setUserVisibleHint(false);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = fragmentManager.beginTransaction();
}
mCurTransaction.detach((Fragment) object);
mCurTransaction.remove((Fragment) object);
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return ((Fragment) object).getView() == view;
}
private String makeFragmentName(int viewId, int position) {
return "android:switcher:" + viewId + position;
}
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
}
由于公司项目中用到了腾讯云直播相关的东西,所以播放器这里用的是腾讯的TXCloudVideoView播放器,可以替换成其他播放器。
public class VideoFragment extends BaseFragment {
@BindView(R.id.txv_video)
TXCloudVideoView txvVideo;
@BindView(R.id.rl_back_right)
RelativeLayout rlBackRight;
@BindView(R.id.dl_back_play)
DrawerLayout dlBackPlay;
@BindView(R.id.iv_play_thun)
ImageView ivPlayThun;
private TXVodPlayer mVodPlayer;
private String url;
public static final String URL = "URL";
@Override
protected int getLayoutId() {
return R.layout.fm_video;
}
@Override
protected void initView() {
url = getArguments().getString(URL);
//创建player对象
mVodPlayer = new TXVodPlayer(context);
//关键player对象与界面view
mVodPlayer.setPlayerView(txvVideo);
// url = "http://v.cctv.com/flash/mp4video6/TMS/2011/01/05/cf752b1c12ce452b3040cab2f90bc265_h264818000nero_aac32-1.mp4";
mVodPlayer.setLoop(true);
Glide.with(context)
.load(url)
.into(ivPlayThun);
}
@Override
protected void loadData() {
mVodPlayer.startPlay(url);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (mVodPlayer == null) {
return;
}
if (isVisibleToUser) {
mVodPlayer.resume();
} else {
mVodPlayer.pause();
}
}
@Override
public void onResume() {
super.onResume();
if (mVodPlayer != null) {
mVodPlayer.resume();
}
}
@Override
public void onPause() {
super.onPause();
if (mVodPlayer != null) {
mVodPlayer.pause();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mVodPlayer != null) {
// true代表清除最后一帧画面
mVodPlayer.stopPlay(true);
}
if (txvVideo != null) {
txvVideo.onDestroy();
}
}
}
要实现抖音那种可以无限上滑的,可以在ViewPager 的onPageSelected 中判断position
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position == list.size() - 2) {
//倒数第2个 加载数据
page++;
addData();
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});