小工具知识网

当前位置: 首页 > 行业知识 > Android进阶:高仿抖音上下滑动分页视频,要求页面流畅

行业知识

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

2026-11-04 12:37:26 来源:互联网转载
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

    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移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

                                        由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

                                        如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)

                                        总结

                                        作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案!

                                        在最后我整理了一份资料,而且我们为了感谢很多支持的学者,资料是无偿分享的,需要的同学可以来学习学习

                                        领取方式:GitHub地址

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

                                        总结

                                        作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案!

                                        在最后我整理了一份资料,而且我们为了感谢很多支持的学者,资料是无偿分享的,需要的同学可以来学习学习

                                        领取方式:GitHub地址

                                        [外链图片转存中…(img-iM87nSFV-1711377209803)]

                                        [外链图片转存中…(img-4RYlyZb8-1711377209803)]

上一篇:两个 this 一起用?Kotlin 的成员扩展函数和 implicit receiver

下一篇:vps服务器怎么设置网络上网?