前言
最近公司在做個短視訊的專案,其中借鑑了很多抖音的設計,其中就有抖音的上下滑切換視訊。
- 【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: 由於公司專案中用到了騰訊雲直播相關的東西,所以播放器這裡用的是騰訊的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();
}
}
}
複製程式碼
-
- 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來實現。