Android进阶:高仿抖音上下滑动分页视频,要求页面流畅

3.2 ViewPager和Fragment
  • 采用了ViewPager+FragmentStatePagerAdapter+Fragment来处理。为何选择使用FragmentStatePagerAdapter,主要是因为使用 FragmentStatePagerAdapter更省内存,但是销毁后新建也是需要时间的。一般情况下,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。关于PagerAdapter的深度解析,可以我这篇文章:PagerAdapter深度解析和实践优化
  • 在activity中的代码如下所示

private void initViewPager() {
List list = new ArrayList<>();
ArrayList fragments = new ArrayList<>();
for (int a = 0; a< DataProvider.VideoPlayerList.length ; a++){
Video video = new Video(DataProvider.VideoPlayerTitle[a],
10,“”,DataProvider.VideoPlayerList[a]);
list.add(video);
fragments.add(VideoFragment.newInstant(DataProvider.VideoPlayerList[a]));
}
vp.setOffscreenPageLimit(1);
vp.setCurrentItem(0);
vp.setOrientation(DirectionalViewPager.VERTICAL);
FragmentManager supportFragmentManager = getSupportFragmentManager();
MyPagerAdapter myPagerAdapter = new MyPagerAdapter(fragments, supportFragmentManager);
vp.setAdapter(myPagerAdapter);
}

class MyPagerAdapter extends FragmentStatePagerAdapter{

private ArrayList list;

public MyPagerAdapter(ArrayList list , FragmentManager fm){
super(fm);
this.list = list;
}

@Override
public Fragment getItem(int i) {
return list.get(i);
}

@Override
public int getCount() {
return list!=null ? list.size() : 0;
}
}

  • 那么在fragment中如何处理呢?关于视频播放器,这里可以看我封装的库,视频lib

public class VideoFragment extends Fragment{

public VideoPlayer videoPlayer;
private String url;
private int index;

@Override
public void onStop() {
super.onStop();
VideoPlayerManager.instance().releaseVideoPlayer();
}

public static Fragment newInstant(String url){
VideoFragment videoFragment = new VideoFragment();
Bundle bundle = new Bundle();
bundle.putString(“url”,url);
videoFragment.setArguments(bundle);
return videoFragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if (arguments != null) {
url = arguments.getString(“url”);
}
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_video, container, false);
return view;
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
videoPlayer = view.findViewById(R.id.video_player);
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.d(“初始化操作”,“------”+index++);
VideoPlayerController controller = new VideoPlayerController(getActivity());
videoPlayer.setUp(url,null);
videoPlayer.setPlayerType(ConstantKeys.IjkPlayerType.TYPE_IJK);
videoPlayer.setController(controller);
ImageUtils.loadImgByPicasso(getActivity(),“”,
R.drawable.image_default,controller.imageView());
}
}

3.3 修改滑动距离翻页
  • 需求要求必须手动触摸滑动超过1/2的时候松开可以滑动下一页,没有超过1/2返回原页,首先肯定是重写viewpager,只能从源码下手。经过分析,源码滑动的逻辑处理在此处,truncator的属性代表判断的比例值!
  • 这个方法会在切页的时候重定向Page,比如从第一个页面滑动,结果没有滑动到第二个页面,而是又返回到第一个页面,那个这个page会有重定向的功能

private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
int targetPage;
if (Math.abs(deltaX) > this.mFlingDistance && Math.abs(velocity) > this.mMinimumVelocity) {
targetPage = velocity > 0 ? currentPage : currentPage + 1;
} else {
float truncator = currentPage >= this.mCurItem ? 0.4F : 0.6F;
targetPage = currentPage + (int)(pageOffset + truncator);
}

if (this.mItems.size() > 0) {
ViewPager.ItemInfo firstItem = (ViewPager.ItemInfo)this.mItems.get(0);
ViewPager.ItemInfo lastItem = (ViewPager.ItemInfo)this.mItems.get(this.mItems.size() - 1);
targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
}

return targetPage;
}

  • determineTargetPage这个方法就是计算接下来要滑到哪一页。这个方法调用是在MotionEvent.ACTION_UP这个事件下,先说下参数意思:
  • currentPage:当前ViewPager显示的页面
  • pageOffset:用户滑动的页面偏移量
  • velocity: 滑动速率
  • deltaX: X方向移动的距离
  • 进行debug调试之后,发现问题就在0.4f和0.6f这个参数上。分析得出:0.6f表示用户滑动能够翻页的偏移量,所以不难理解,为啥要滑动半屏或者以上了。
  • 也可以修改Touch事件
  • 控制ViewPager的Touch事件,这个基本是万能的,毕竟是从根源上入手的。你可以在onTouchEvent和onInterceptTouchEvent中做逻辑的判断。但是比较麻烦。
3.4 修改滑动速度
  • 使用viewPager进行滑动时,如果通过手指滑动来进行的话,可以根据手指滑动的距离来实现,但是如果通过setCurrentItem函数来实现的话,则会发现直接闪过去的,会出现一下刷屏。想要通过使用setCurrentItem函数来进行viewpager的滑动,并且需要有过度滑动的动画,那么,该如何做呢?
  • 具体可以分析setCurrentItem源码的逻辑,然后会看到scrollToItem方法,这个特别重要,主要是处理滚动过程中的逻辑。最主要关心的也是smoothScrollTo函数,这个函数中,可以看到具体执行滑动的其实就一句话,就是mScroller.startScroll(sx,sy,dx,dy,duration),则可以看到,是mScroller这个对象进行滑动的。那么想要改变它的属性,则可以通过反射来实现。
  • 代码如下所示,如果是手指触摸滑动,则可以加快一点滑动速率,当然滑动持续时间你可以自己设置。通过自己自定义滑动的时间,就可以控制滑动的速度。

@TargetApi(Build.VERSION_CODES.KITKAT)
public void setAnimationDuration(final int during){
try {
// viewPager平移动画事件
Field mField = ViewPager.class.getDeclaredField(“mScroller”);
mField.setAccessible(true);
// 动画效果与ViewPager的一致
Interpolator interpolator = new Interpolator() {
@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
Scroller mScroller = new Scroller(getContext(),interpolator){
final int time = 2000;
@Override
public void startScroll(int x, int y, int dx, int dy, int duration) {
// 如果手工滚动,则加速滚动
if (System.currentTimeMillis() - mRecentTouchTime > time) {
duration = during;
} else {
duration /= 2;
}
super.startScroll(x, y, dx, dy, duration);
}

@Override
public void startScroll(int x, int y, int dx, int dy) {
super.startScroll(x, y, dx, dy,during);
}
};
mField.set(this, mScroller);
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
e.printStackTrace();
}
}

04.用RecyclerView实现

4.1 自定义LayoutManager
  • 自定义LayoutManager,并且继承LinearLayoutManager,这样就得到一个可以水平排向或者竖向排向的布局策略。如果你接触过SnapHelper应该了解一下LinearSnapHelper和PagerSnapHelper这两个子类类,LinearSnapHelper可以实现让列表的Item居中显示的效果,PagerSnapHelper就可以做到一次滚动一个item显示的效果。
  • 重写onChildViewAttachedToWindow方法,在RecyclerView中,当Item添加进来了调用这个方法。这个方法相当于是把view添加到window时候调用的,也就是说它比draw方法先执行,可以做一些初始化相关的操作。

/**

  • 该方法必须调用
  • @param recyclerView recyclerView
    */
    @Override
    public void onAttachedToWindow(RecyclerView recyclerView) {
    if (recyclerView == null) {
    throw new IllegalArgumentException(“The attach RecycleView must not null!!”);
    }
    super.onAttachedToWindow(recyclerView);
    this.mRecyclerView = recyclerView;
    if (mPagerSnapHelper==null){
    init();
    }
    mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
    mRecyclerView.addOnChildAttachStateChangeListener(mChildAttachStateChangeListener);
    }
4.2 添加滑动监听
  • 涉及到一次滑动一页视频,那么肯定会有视频初始化和释放的功能。那么思考一下哪里来开始播放视频和在哪里释放视频?不要着急,要监听滑动到哪页,需要我们重写onScrollStateChanged()函数,这里面有三种状态:SCROLL_STATE_IDLE(空闲),SCROLL_STATE_DRAGGING(拖动),SCROLL_STATE_SETTLING(要移动到最后位置时)。
  • 我们需要的就是RecyclerView停止时的状态,我们就可以拿到这个View的Position,注意这里还有一个问题,当你通过这个position去拿Item会报错,这里涉及到RecyclerView的缓存机制,自己去脑补~~。打印Log,你会发现RecyclerView.getChildCount()一直为1或者会出现为2的情况。来实现一个接口然后通过接口把状态传递出去。
  • 自定义监听listener事件

public interface OnPagerListener {

/**

  • 初始化完成
    */
    void onInitComplete();

/**

  • 释放的监听
  • @param isNext 是否下一个
  • @param position 索引
    */
    void onPageRelease(boolean isNext,int position);

/***

  • 选中的监听以及判断是否滑动到底部

  • @param position 索引

  • @param isBottom 是否到了底部
    */
    void onPageSelected(int position,boolean isBottom);
    }

  • 获取到RecyclerView空闲时选中的Item,重写LinearLayoutManager的onScrollStateChanged方法

/**

  • 滑动状态的改变
  • 缓慢拖拽-> SCROLL_STATE_DRAGGING
  • 快速滚动-> SCROLL_STATE_SETTLING
  • 空闲状态-> SCROLL_STATE_IDLE
  • @param state 状态
    */
    @Override
    public void onScrollStateChanged(int state) {
    switch (state) {
    case RecyclerView.SCROLL_STATE_IDLE:
    View viewIdle = mPagerSnapHelper.findSnapView(this);
    int positionIdle = 0;
    if (viewIdle != null) {
    positionIdle = getPosition(viewIdle);
    }
    if (mOnViewPagerListener != null && getChildCount() == 1) {
    mOnViewPagerListener.onPageSelected(positionIdle,
    positionIdle == getItemCount() - 1);
    }
    break;
    case RecyclerView.SCROLL_STATE_DRAGGING:
    View viewDrag = mPagerSnapHelper.findSnapView(this);
    if (viewDrag != null) {
    int positionDrag = getPosition(viewDrag);
    }
    break;
    case RecyclerView.SCROLL_STATE_SETTLING:
    View viewSettling = mPagerSnapHelper.findSnapView(this);
    if (viewSettling != null) {
    int positionSettling = getPosition(viewSettling);
    }
    break;
    default:
    break;
    }
    }
4.3 监听页面是否滚动
  • 这里有两个方法scrollHorizontallyBy()和scrollVerticallyBy()可以拿到滑动偏移量,可以判断滑动方向。

/**

  • 监听竖直方向的相对偏移量
  • @param dy y轴滚动值
  • @param recycler recycler
  • @param state state滚动状态
  • @return int值
    */
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() == 0 || dy == 0) {
    return 0;
    }
    this.mDrift = dy;
    return super.scrollVerticallyBy(dy, recycler, state);
    }

/**

  • 监听水平方向的相对偏移量
  • @param dx x轴滚动值
  • @param recycler recycler
  • @param state state滚动状态
  • @return int值
    */
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() == 0 || dx == 0) {
    return 0;
    }
    this.mDrift = dx;
    return super.scrollHorizontallyBy(dx, recycler, state);
    }
4.4 attach和Detached
  • 列表的选中监听好了,我们就看看什么时候释放视频的资源,第二步中的三种状态,去打印getChildCount()的日志,你会发现getChildCount()在SCROLL_STATE_DRAGGING会为1,SCROLL_STATE_SETTLING为2,SCROLL_STATE_IDLE有时为1,有时为2,还是RecyclerView的缓存机制O(∩∩)O,这里不会去赘述缓存机制,要做的是要知道在什么时候去做释放视频的操作,还要分清是释放上一页还是下一页。

private RecyclerView.OnChildAttachStateChangeListener mChildAttachStateChangeListener =
new RecyclerView.OnChildAttachStateChangeListener() {
/**

  • 第一次进入界面的监听,可以做初始化方面的操作
  • @param view view
    */
    @Override
    public void onChildViewAttachedToWindow(@NonNull View view) {
    if (mOnViewPagerListener != null && getChildCount() == 1) {
    mOnViewPagerListener.onInitComplete();
    }
    }

/**

  • 页面销毁的时候调用该方法,可以做销毁方面的操作

  • @param view view
    */
    @Override
    public void onChildViewDetachedFromWindow(@NonNull View view) {
    if (mDrift >= 0){
    if (mOnViewPagerListener != null) {
    mOnViewPagerListener.onPageRelease(true , getPosition(view));
    }
    }else {
    if (mOnViewPagerListener != null) {
    mOnViewPagerListener.onPageRelease(false , getPosition(view));
    }
    }
    }
    };

  • 哪里添加该listener监听事件,如下所示。这里注意需要在页面销毁的时候移除listener监听事件。

/**

  • attach到window窗口时,该方法必须调用
  • @param recyclerView recyclerView
    */
    @Override

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案!
在最后我整理了一份资料,而且我们为了感谢很多支持的学者,资料是无偿分享的,需要的同学可以来学习学习
领取方式:GitHub地址
资料.png
资料图.jpg

片转存中…(img-QOs6ToG2-1711377209803)]

总结

作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案!
在最后我整理了一份资料,而且我们为了感谢很多支持的学者,资料是无偿分享的,需要的同学可以来学习学习
领取方式:GitHub地址
[外链图片转存中…(img-iM87nSFV-1711377209803)]
[外链图片转存中…(img-4RYlyZb8-1711377209803)]

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>