前言
最近公司在做个短视频的项目,其中借鉴了很多抖音的设计,其中就有抖音的上下滑切换视频。
- 【Android 进阶】仿抖音系列之翻页上下滑切换视频(一)
- 【Android 进阶】仿抖音系列之列表播放视频(二)
- 【Android 进阶】仿抖音系列之列表播放视频(三)
- 【Android 进阶】仿抖音系列之翻页上下滑切换视频(四)
- 【Android 进阶】仿抖音系列之视频预览和录制(五)
思路
- ViewPager
这里用重写了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
,通过其生命周期来控制视频的播放与暂停。
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;
}
}
}
通过setUserVisibleHint
、onResume
、onPause
、onDestroy
4个方法处理视频的播放、暂停、重新播放、销毁逻辑
tip: 这里使用的是 JiaoZiVideoPlayer ,可以替换成其他播放器。
public class VideoFragment extends BaseFragment {
@BindView(R.id.txv_video)
MyVideoPlayer txvVideo;
@BindView(R.id.rl_back_right)
RelativeLayout rlBackRight;
@BindView(R.id.dl_back_play)
DrawerLayout dlBackPlay;
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);
Glide.with(context)
.load(url)
.into(txvVideo.thumbImageView);
txvVideo.rl_touch_help.setVisibility(View.GONE);
txvVideo.setUp(url, url);
}
@Override
protected void loadData() {
txvVideo.startVideo();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (txvVideo == null) {
return;
}
if (isVisibleToUser) {
txvVideo.goOnPlayOnResume();
} else {
txvVideo.goOnPlayOnPause();
}
}
@Override
public void onResume() {
super.onResume();
if (txvVideo != null) {
txvVideo.goOnPlayOnResume();
}
}
@Override
public void onPause() {
super.onPause();
if (txvVideo != null) {
txvVideo.goOnPlayOnPause();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (txvVideo != null) {
txvVideo.releaseAllVideos();
}
}
}
缺陷
自定义的ViewPager
滑动不太顺滑,推荐使用 VerticalViewPager
- RecyclerView + PagerSnapHelper
详情请移步另外一篇博客
【Android 进阶】仿抖音系列之翻页上下滑切换视频(四)
问题
- 1.如果要实现抖音那种可以无限上滑的,可以在
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) {
}
});
- 2.关于在大屏手机上滑动不灵敏的问题,可以尝试将
MIN_SCALE
设置为0.5,或者更低。 - 3.建议使用RecyclerView + PagerSnapHelper来实现。