精华内容
下载资源
问答
  • ViewPager2+Fragment+tablayout ... ViewPager2 是 ViewPager 库的改进版本,可提供增强型功能并解决使用 ViewPager 时遇到的一些问题 ViewPager有两个弊端:1....而目前ViewPager2对Fragment支持只能

    ViewPager2+Fragment+tablayout

    https://www.jianshu.com/p/351e695015d4

    ViewPager2 是 ViewPager 库的改进版本,可提供增强型功能并解决使用 ViewPager 时遇到的一些问题

    ViewPager有两个弊端:1.不能关闭预加载;2.更新adapter不生效
    我们在加载数据的时候,viewpager默认会帮我们预加载前后两个页面的数据,并且这2个view是不可见的。而目前ViewPager2对Fragment支持只能用FragmentStateAdapter,FragmentStateAdapter在遇到预加载时,只会创建Fragment对象,不会把Fragment真正的加入到布局中,所以自带懒加载效果

     

    https://mp.weixin.qq.com/s/MOWdbI5IREjQP1Px-WJY1Q

     

    Android ViewPager2的特性

    • 从右到左的布局支持
    • 垂直方向
    • RecyclerView.Adapter取代PagerAdapter
    • registerOnPageChangeCallback 取代 addPageChangeListener
    • 更高效的notifyDataSetChanged

          支持Fragment切换的动画
     

     

    ViewPager2与RecycleView的结合使用

     

    public class MainActivity extends AppCompatActivity {
        ViewPager2 mviewPager2;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mviewPager2 = findViewById(R.id.viewpager2);
            List<String> list = new ArrayList<>();
            list.add("页面一");
            list.add("页面二");
            list.add("页面三");
            list.add("页面四");
    
    
            mviewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
    
            mviewPager2.setAdapter(new ViewPagerAdapter(this, list, mviewPager2));
    
            mviewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                }
            });
    
        }
    }
    

     

     

     

    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.Button;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    
    import java.util.List;
    
    import androidx.annotation.NonNull;
    import androidx.recyclerview.widget.RecyclerView;
    import androidx.viewpager2.widget.ViewPager2;
    
    /**
     * @Author: wuchaowen
     * @Description:
     * @Time:
     **/
    public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewHolder> {
        private List<String> mData;
        private LayoutInflater mInflater;
        private ViewPager2 viewPager2;
    
    
        private int[] colorArray = new int[]{android.R.color.black, android.R.color.holo_blue_dark, android.R.color.holo_green_dark, android.R.color.holo_red_dark};
    
    
        public ViewPagerAdapter(Context context, List<String> data, ViewPager2 viewPager2) {
            this.mInflater = LayoutInflater.from(context);
            this.mData = data;
            this.viewPager2 = viewPager2;
        }
    
        @NonNull
        @Override
        public ViewPagerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = mInflater.inflate(R.layout.recycleview_item, parent, false);
            return new ViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(@NonNull ViewPagerAdapter.ViewHolder holder, int position) {
            String animal = mData.get(position);
            holder.myTextView.setText(animal);
            holder.relativeLayout.setBackgroundResource(colorArray[position]);
    
        }
    
        @Override
        public int getItemCount() {
            return mData.size();
        }
    
        public class ViewHolder extends RecyclerView.ViewHolder {
            TextView myTextView;
            RelativeLayout relativeLayout;
    
            ViewHolder(View itemView) {
                super(itemView);
                myTextView = itemView.findViewById(R.id.tvTitle);
                relativeLayout = itemView.findViewById(R.id.container);
    
            }
        }
    }
    展开全文
  • 仿抖音上下滑动分页视频 目录介绍 01.先来看一下需求 02.有几种实现方式 2.1 使用ViewPager 2.2 使用RecyclerView 03.用ViewPager实现 3.1 自定义ViewPager 3.2 ViewPager和Fragment 3.3 修改滑动距离...

    仿抖音上下滑动分页视频

    目录介绍

    • 01.先来看一下需求
    • 02.有几种实现方式
      • 2.1 使用ViewPager
      • 2.2 使用RecyclerView
    • 03.用ViewPager实现
      • 3.1 自定义ViewPager
      • 3.2 ViewPager和Fragment
      • 3.3 修改滑动距离翻页
      • 3.4 修改滑动速度
    • 04.用RecyclerView实现
      • 4.1 自定义LayoutManager
      • 4.2 添加滑动监听
      • 4.3 监听页面是否滚动
      • 4.4 attach和Detached
    • 05.优化点详谈
      • 5.1 ViewPager改变滑动速率
      • 5.2 PagerSnapHelper注意点
      • 5.3 自定义LayoutManager注意点
      • 5.4 视频播放逻辑优化
      • 5.5 视频逻辑充分解藕
      • 5.6 翻页卡顿优化分析
      • 5.7 上拉很快翻页黑屏

    00.先看一下效果图

    01.先来看一下需求

    • 项目中的视频播放,要求实现抖音那种竖直方向一次滑动一页的效果。滑动要流畅不卡顿,并且手动触摸滑动超过1/2的时候松开可以滑动下一页,没有超过1/2返回原页。
    • 手指拖动页面滑动,只要没有切换到其他的页面,视频都是在播放的。切换了页面,上一个视频销毁,该页面则开始初始化播放。
    • 切换页面的时候过渡效果要自然,避免出现闪屏。具体的滑动效果,可以直接参考抖音……
    • 开源库地址:github.com/yangchong21…

    02.有几种实现方式

    2.1 使用ViewPager

    • 使用ViewPager实现竖直方法上下切换视频分析
      • 1.最近项目需求中有用到需要在ViewPager中播放视频,就是竖直方法上下滑动切换视频,视频是网络视频,最开始的实现思路是ViewPager中根据当前item位置去初始化SurfaceView,同时销毁时根据item的位置移除SurfaceView。
      • 2.上面那种方式确实是可以实现的,但是存在2个问题,第一,MediaPlayer的生命周期不容易控制并且存在内存泄漏问题。第二,连续三个item都是视频时,来回滑动的过程中发现会出现上个视频的最后一帧画面的bug。
      • 3.未提升用户体验,视频播放器初始化完成前上面会覆盖有该视频的第一帧图片,但是发现存在第一帧图片与视频第一帧信息不符的情况,后面会通过代码给出解决方案。
    • 大概的实现思路是这样
      • 1.需要自定义一个竖直方向滑动的ViewPager,注意这个特别重要。
      • 2.一次滑动一页,建议采用ViewPager+FragmentStatePagerAdapter+Fragment方式来做,后面会详细说。
      • 3.在fragment中处理视频的初始化,播放和销毁逻辑等逻辑。
      • 4.由于一个页面需要创建一个fragment,注意性能和滑动流畅度这块需要分析和探讨。
    • 不太建议使用ViewPager
      • 1.ViewPager 自带的滑动效果完全满足场景,而且支持Fragment和View等UI绑定,只要对布局和触摸事件部分作一些修改,就可以把横向的 ViewPager 改成竖向。
      • 2.但是没有复用是个最致命的问题。在onLayout方法中,所有子View会实例化并一字排开在布局上。当Item数量很大时,将会是很大的性能浪费。
      • 3.其次是可见性判断的问题。很多人会以为 Fragment 在 onResume 的时候就是可见的,而 ViewPager 中的 Fragment 就是个反例,尤其是多个 ViewPager 嵌套时,会同时有多个父 Fragment 多个子 Fragment 处于 onResume 的状态,却只有其中一个是可见的。除非放弃 ViewPager 的预加载机制。在页面内容曝光等重要的数据上报时,就需要判断很多条件:onResumed 、 setUserVisibleHint 、 setOnPageChangeListener 等。

    2.2 使用RecyclerView

    • 使用RecyclerView实现树枝方向上下切换视频分析
      • 1.首先RecyclerView它设置竖直方向滑动是十分简单的,同时关于item的四级缓存也做好了处理,而且滑动的效果相比ViewPager要好一些。
      • 2.滑动事件处理比viewPager好,即使你外层嵌套了下拉刷新上拉加载的布局,也不影响后期事件冲突处理,详细可以看demo案例。
    • 大概的实现思路是这样
      • 1.自定义一个LinearLayoutManager,重写onScrollStateChanged方法,注意是拿到滑动状态。
      • 2.一次滑动切换一个页面,可以使用PagerSnapHelper来实现,十分方便简单。
      • 3.在recyclerView对应的adapter中,在onCreateViewHolder初始化视频操作,同时当onViewRecycled时,销毁视频资源。
      • 4.添加自定义回调接口,在滚动页面和attch,detach的时候,定义初始化,页面销毁等方法,暴露给开发者。

    03.用ViewPager实现

    3.1 自定义ViewPager

    • 代码如下所示,这里省略了不少的代码,具体可以看项目中的代码。

      /**
       * <pre>
       *     @author 杨充
       *     blog  : https://github.com/yangchong211
       *     time  : 2019/6/20
       *     desc  : 自定义ViewPager,主要是处理边界极端情况
       *     revise:
       * </pre>
       */
      public class VerticalViewPager extends ViewPager {
      
          private boolean isVertical = false;
          private long mRecentTouchTime;
      
          public VerticalViewPager(@NonNull Context context) {
              super(context);
          }
      
          public VerticalViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
              super(context, attrs);
          }
      
          private void init() {
              setPageTransformer(true, new HorizontalVerticalPageTransformer());
              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.25f;
      
              @Override
              public void transformPage(@NonNull 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);
                      }
                  }
              }
          }
      
          /**
           * 交换x轴和y轴的移动距离
           * @param event 获取事件类型的封装类MotionEvent
           */
          private MotionEvent swapXY(MotionEvent event) {
              //获取宽高
              float width = getWidth();
              float height = getHeight();
              //将Y轴的移动距离转变成X轴的移动距离
              float swappedX = (event.getY() / height) * width;
              //将X轴的移动距离转变成Y轴的移动距离
              float swappedY = (event.getX() / width) * height;
              //重设event的位置
              event.setLocation(swappedX, swappedY);
              return event;
          }
      
          @Override
          public boolean onInterceptTouchEvent(MotionEvent ev) {
              mRecentTouchTime = System.currentTimeMillis();
              if (getCurrentItem() == 0 && getChildCount() == 0) {
                  return false;
              }
              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 (getCurrentItem() == 0 && getChildCount() == 0) {
                  return false;
              }
              if (isVertical) {
                  return super.onTouchEvent(swapXY(ev));
              } else {
                  return super.onTouchEvent(ev);
              }
          }
      }
      
      
      

    3.2 ViewPager和Fragment

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

    • 在activity中的代码如下所示

      private void initViewPager() {
          List<Video> list = new ArrayList<>();
          ArrayList<Fragment> 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<Fragment> list;
      
          public MyPagerAdapter(ArrayList<Fragment> 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
      public void onAttachedToWindow(RecyclerView recyclerView) {
          //这里省略部分代码
          mRecyclerView.addOnChildAttachStateChangeListener(mChildAttachStateChangeListener);
      }
      
      /**
       * 销毁的时候调用该方法,需要移除监听事件
       * @param view                                  view
       * @param recycler                              recycler
       */
      @Override
      public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
          super.onDetachedFromWindow(view, recycler);
          if (mRecyclerView!=null){
              mRecyclerView.removeOnChildAttachStateChangeListener(mChildAttachStateChangeListener);
          }
      }
      
      
      

    05.优化点详谈

    5.1 ViewPager改变滑动速率

    • 可以通过反射修改属性,注意,使用反射的时候,建议手动try-catch,避免异常导致崩溃。代码如下所示:

      /**
       * 修改滑动灵敏度
       * @param flingDistance                     滑动惯性,默认是75
       * @param minimumVelocity                   最小滑动值,默认是1200
       */
      public void setScrollFling(int flingDistance , int minimumVelocity){
          try {
              Field mFlingDistance = ViewPager.class.getDeclaredField("mFlingDistance");
              mFlingDistance.setAccessible(true);
              Object o = mFlingDistance.get(this);
              Log.d("setScrollFling",o.toString());
              //默认值75
              mFlingDistance.set(this, flingDistance);
      
              Field mMinimumVelocity = ViewPager.class.getDeclaredField("mMinimumVelocity");
              mMinimumVelocity.setAccessible(true);
              Object o1 = mMinimumVelocity.get(this);
              Log.d("setScrollFling",o1.toString());
              //默认值1200
              mMinimumVelocity.set(this,minimumVelocity);
          } catch (Exception e){
              e.printStackTrace();
          }
      }
      
      
      

    5.2 PagerSnapHelper注意点

    • 好多时候会抛出一个异常"illegalstateexception an instance of onflinglistener already set".

    • 看SnapHelper源码attachToRecyclerView(xxx)方法时,可以看到如果recyclerView不为null,则先destoryCallback(),它作用在于取消之前的RecyclerView的监听接口。然后通过setupCallbacks()设置监听器,如果当前RecyclerView已经设置了OnFlingListener,会抛出一个状态异常。那么这个如何复现了,很容易,你初始化多次就可以看到这个bug。

    • 建议手动捕获一下该异常,代码设置如下所示。源码中判断了,如果onFlingListener已经存在的话,再次设置就直接抛出异常,那么这里可以增强一下逻辑判断,ok,那么问题便解决呢!

      try {
          //attachToRecyclerView源码上的方法可能会抛出IllegalStateException异常,这里手动捕获一下
          RecyclerView.OnFlingListener onFlingListener = mRecyclerView.getOnFlingListener();
          //源码中判断了,如果onFlingListener已经存在的话,再次设置就直接抛出异常,那么这里可以判断一下
          if (onFlingListener==null){
              mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
          }
      } catch (IllegalStateException e){
          e.printStackTrace();
      }
      
      
      

    5.3 自定义LayoutManager注意点

    • 网上有人已经写了一篇自定义LayoutManager实现抖音的效果的博客,我自己也很仔细看了这篇文章。不过我觉得有几个注意要点,因为要用到线上app,则一定要尽可能减少崩溃率……
    • 通过SnapHelper调用findSnapView方法,得到的view,一定要增加非空判断逻辑,否则很容易造成崩溃。
    • 在监听滚动位移scrollVerticallyBy的时候,注意要增加判断,就是getChildCount()如果为0时,则需要返回0。
    • 在onDetachedFromWindow调用的时候,可以把listener监听事件给remove掉。

    5.4 视频播放逻辑优化

    • 从前台切到后台,当视频正在播放或者正在缓冲时,调用方法可以设置暂停视频。销毁页面,释放,内部的播放器被释放掉,同时如果在全屏、小窗口模式下都会退出。从后台切换到前台,当视频暂停时或者缓冲暂停时,调用该方法重新开启视频播放。具体视频播放代码设置如下,具体更加详细内容可以看我封装的视频播放器lib

      @Override
      protected void onStop() {
          super.onStop();
          //从前台切到后台,当视频正在播放或者正在缓冲时,调用该方法暂停视频
          VideoPlayerManager.instance().suspendVideoPlayer();
      }
      
      @Override
      protected void onDestroy() {
          super.onDestroy();
          //销毁页面,释放,内部的播放器被释放掉,同时如果在全屏、小窗口模式下都会退出
          VideoPlayerManager.instance().releaseVideoPlayer();
      }
      
      @Override
      public void onBackPressed() {
          //处理返回键逻辑;如果是全屏,则退出全屏;如果是小窗口,则退出小窗口
          if (VideoPlayerManager.instance().onBackPressed()){
              return;
          }else {
              //销毁页面
              VideoPlayerManager.instance().releaseVideoPlayer();
          }
          super.onBackPressed();
      }
      
      @Override
      protected void onRestart() {
          super.onRestart();
          //从后台切换到前台,当视频暂停时或者缓冲暂停时,调用该方法重新开启视频播放
          VideoPlayerManager.instance().resumeVideoPlayer();
      }
      
      
      

    5.5 视频逻辑充分解藕

    • 实际开发中,翻页肯定会涉及到视频的初始化和销毁的逻辑。首先要保证视频只有唯一一个播放,滑动到分页一半,总不可能让两个页面都播放视频吧,所以需要保证视频VideoPlayer是一个单利对象,这样就可以保证唯一性呢!接着,不管是在recyclerView还是ViewPager中,当页面处于不可见被销毁或者view被回收的阶段,这个时候需要把视频资源销毁,尽量视频播放功能封装起来,然后在页面不同状态调用方法即可。
    • 当然,实际app中,视频播放页面,还有一些点赞,评论,分享,查看作者等等很多其他功能。那么这些都是要请求接口的,还有滑动分页的功能,当滑动到最后某一页时候拉取下一个视频集合数据等业务逻辑。视频播放功能这块,因为功能比较复杂,因此封装一下比较好。尽量做到视频功能解藕!关于视频封装库,可以看我之前写的一个库,视频播放器

    5.6 翻页卡顿优化分析

    • 如果是使用recyclerView实现滑动翻页效果,那么为了提高使用体验效果。则可以注意:1.在onBindViewHolder中不要做耗时操作,2.视频滑动翻页的布局固定高度,避免重复计算高度RecyclerView.setHasFixedSize(true),3.关于分页拉取数据注意,建议一次拉下10条数据(这个也可以和服务端协定自定义数量),而不要滑动一页加载下一页的数据。

    5.7 上拉很快翻页黑屏

    • 因为设置视频的背景颜色为黑色,我看了好多播放器初始化的时候,都是这样的。因为最简单的解决办法,就是给它加个封面,设置封面的背景即可。
    展开全文
  • 不少朋友对短视频,上下滑动播放视频效果比较比较感兴趣,今天看看这个案例。1、效果图:讲下大概思路,使用Recycleview配合自定义LinearLayoutManager来实现这个功能...

    不少朋友对短视频,上下滑动播放视频效果比较比较感兴趣,今天看看这个案例。

    1、效果图:

    讲下大概思路,使用Recycleview配合自定义LinearLayoutManager来实现这个功能,这里着重说下自定义LinearLayoutManager的实现可以看到每当下一个item滑入屏幕时,上面的item会继续播放视频,而滑入的item只有当全部进入屏幕才会播放,而且当手指抬起时,当前item会根据滑动的距离相应的自动滑入滑出,针对这种情形,就会想到使用SnapHelper

    RecyclerView在24.2.0版本中新增了SnapHelper这个辅助类,用于辅助RecyclerView在滚动结束时将Item对齐到某个位置。特别是列表横向滑动时,很多时候不会让列表滑到任意位置,而是会有一定的规则限制,这时候就可以通过SnapHelper来定义对齐规则了。

    SnapHelper是一个抽象类,官方提供了一个LinearSnapHelper的子类,可以让RecyclerView滚动停止时相应的Item停留中间位置。25.1.0版本中官方又提供了一个PagerSnapHelper的子类,可以使RecyclerView像ViewPager一样的效果,一次只能滑一页,而且居中显示,也就是说使用SnapHelper可以帮助RecyclerView滑动完成后进行对齐操作,让item的侧边对齐或者居中对齐,这样实现上下滑动进行视频切换。这里有SnapHelper的详解

    2、正式撸代码:
    1.首先定义一个接口,用来执行item的相关操作

    public interface OnViewPagerListener {
        /*初始化完成*/
        void onInitComplete();
        /*释放的监听*/
        void onPageRelease(boolean isNext, int position);
        /*选中的监听以及判断是否滑动到底部*/
        void onPageSelected(int position, boolean isBottom);
    }
    
    

    2.继承LinearLayoutManager ,对滑入滑出的item回调1中接口里面的方法

    import android.content.Context;
    import android.support.annotation.NonNull;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.PagerSnapHelper;
    import android.support.v7.widget.RecyclerView;
    import android.view.View;
    
    public class MyLayoutManager extends LinearLayoutManager implements RecyclerView.OnChildAttachStateChangeListener {
        private int mDrift;//位移,用来判断移动方向
    
        private PagerSnapHelper mPagerSnapHelper;
        private OnViewPagerListener mOnViewPagerListener;
    
        public MyLayoutManager(Context context) {
            super(context);
        }
    
        public MyLayoutManager(Context context, int orientation, boolean reverseLayout) {
            super(context, orientation, reverseLayout);
            mPagerSnapHelper = new PagerSnapHelper();
        }
    
        @Override
        public void onAttachedToWindow(RecyclerView view) {
    
            view.addOnChildAttachStateChangeListener(this);
            mPagerSnapHelper.attachToRecyclerView(view);
            super.onAttachedToWindow(view);
        }
    //当Item添加进来了  调用这个方法
    
        //
        @Override
        public void onChildViewAttachedToWindow(@NonNull View view) {
    //        播放视频操作 即将要播放的是上一个视频 还是下一个视频
            int position = getPosition(view);
            if (0 == position) {
                if (mOnViewPagerListener != null) {
                    mOnViewPagerListener.onPageSelected(getPosition(view), false);
                }
    
            }
        }
    
        public void setOnViewPagerListener(OnViewPagerListener mOnViewPagerListener) {
            this.mOnViewPagerListener = mOnViewPagerListener;
        }
    
        @Override
        public void onScrollStateChanged(int state) {
            switch (state) {
                case RecyclerView.SCROLL_STATE_IDLE:
                    View view = mPagerSnapHelper.findSnapView(this);
                    int position = getPosition(view);
                    if (mOnViewPagerListener != null) {
                        mOnViewPagerListener.onPageSelected(position, position == getItemCount() - 1);
    
                    }
    //                postion  ---回调 ----》播放
    
    
                    break;
            }
            super.onScrollStateChanged(state);
        }
    
        @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));
            }
        }
    
    
        @Override
        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
            this.mDrift = dy;
            return super.scrollVerticallyBy(dy, recycler, state);
        }
    
        @Override
        public boolean canScrollVertically() {
            return true;
        }
    }
    
    

    3.接下来就是正常使用recycleview了 配合原生VideoView 播放视频,切换时先用一张截图盖住视频,视频渲染成功再隐藏截图,感觉上是无缝切换(这里是原生播放器初始加载视频会黑屏,如果用更高级的播放器可能不会有这个问题)

    import android.annotation.TargetApi;
    import android.content.Context;
    import android.media.MediaPlayer;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.widget.OrientationHelper;
    import android.support.v7.widget.RecyclerView;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.RelativeLayout;
    import android.widget.VideoView;
    
    
    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "douyin";
        private RecyclerView mRecyclerView;
        private MyAdapter mAdapter;
        MyLayoutManager2 myLayoutManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
            initListener();
        }
    
        private void initView() {
            mRecyclerView = findViewById(R.id.recycler);
            myLayoutManager = new MyLayoutManager2(this, OrientationHelper.VERTICAL, false);
    
            mAdapter = new MyAdapter(this);
            mRecyclerView.setLayoutManager(myLayoutManager);
            mRecyclerView.setAdapter(mAdapter);
    
        }
    
        private void initListener() {
            myLayoutManager.setOnViewPagerListener(new OnViewPagerListener() {
                @Override
                public void onInitComplete() {
    
                }
    
                @Override
                public void onPageRelease(boolean isNext, int position) {
                    Log.e(TAG, "释放位置:" + position + " 下一页:" + isNext);
                    int index = 0;
                    if (isNext) {
                        index = 0;
                    } else {
                        index = 1;
                    }
                    releaseVideo(index);
                }
    
                @Override
                public void onPageSelected(int position, boolean bottom) {
                    Log.e(TAG, "选择位置:" + position + " 下一页:" + bottom);
    
                    playVideo(0);
                }
            });
        }
    
        class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
            private int[] imgs = {R.mipmap.img_video_1, R.mipmap.img_video_2, R.mipmap.img_video_3, R.mipmap.img_video_4, R.mipmap.img_video_5, R.mipmap.img_video_6, R.mipmap.img_video_7, R.mipmap.img_video_8};
            private int[] videos = {R.raw.video_1, R.raw.video_2, R.raw.video_3, R.raw.video_4, R.raw.video_5, R.raw.video_6, R.raw.video_7, R.raw.video_8};
            private int index = 0;
            private Context mContext;
    
            public MyAdapter(Context context) {
                this.mContext = context;
            }
    
    
            @Override
            public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view_pager, parent, false);
                return new ViewHolder(view);
            }
    
            @Override
            public void onBindViewHolder(ViewHolder holder, int position) {
                holder.img_thumb.setImageResource(imgs[index]);
                holder.videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + videos[index]));
                index++;
                if (index >= 7) {
                    index = 0;
                }
            }
    
            @Override
            public int getItemCount() {
                return 88;
            }
    
            public class ViewHolder extends RecyclerView.ViewHolder {
                ImageView img_thumb;
                VideoView videoView;
                ImageView img_play;
                RelativeLayout rootView;
    
                public ViewHolder(View itemView) {
                    super(itemView);
                    img_thumb = itemView.findViewById(R.id.img_thumb);
                    videoView = itemView.findViewById(R.id.video_view);
                    img_play = itemView.findViewById(R.id.img_play);
                    rootView = itemView.findViewById(R.id.root_view);
                }
            }
        }
    
        private void releaseVideo(int index) {
            View itemView = mRecyclerView.getChildAt(index);
            final VideoView videoView = itemView.findViewById(R.id.video_view);
            final ImageView imgThumb = itemView.findViewById(R.id.img_thumb);
            final ImageView imgPlay = itemView.findViewById(R.id.img_play);
            videoView.stopPlayback();
            imgThumb.animate().alpha(1).start();
            imgPlay.animate().alpha(0f).start();
        }
    
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        private void playVideo(int position) {
            View itemView = mRecyclerView.getChildAt(position);
            final FullWindowVideoView videoView = itemView.findViewById(R.id.video_view);
            final ImageView imgPlay = itemView.findViewById(R.id.img_play);
            final ImageView imgThumb = itemView.findViewById(R.id.img_thumb);
            final RelativeLayout rootView = itemView.findViewById(R.id.root_view);
            final MediaPlayer[] mediaPlayer = new MediaPlayer[1];
            videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
    
                }
            });
            videoView.setOnInfoListener(new MediaPlayer.OnInfoListener() {
                @Override
                public boolean onInfo(MediaPlayer mp, int what, int extra) {
                    mediaPlayer[0] = mp;
                    mp.setLooping(true);
                    imgThumb.animate().alpha(0).setDuration(200).start();
                    return false;
                }
            });
    
            videoView.start();
    
            imgPlay.setOnClickListener(new View.OnClickListener() {
                boolean isPlaying = true;
    
                @Override
                public void onClick(View v) {
                    if (videoView.isPlaying()) {
                        imgPlay.animate().alpha(0.7f).start();
                        videoView.pause();
                        isPlaying = false;
                    } else {
                        imgPlay.animate().alpha(0f).start();
                        videoView.start();
                        isPlaying = true;
                    }
                }
            });
        }
    
    }
    
    

    最后附上github地址,点击《阅读原文

    原创作者:庞哈哈哈12138,原文:https://www.jianshu.com/p/3a043cd4eb1f

    展开全文
  • Android仿抖音上下滑动切换视频https://www.jianshu.com/p/af9c0e46725d  自从各大直播平台可以滑动切换直播间后,公司就出了一大波需求,还要配合各种收费,各种VIP,很是头疼(haha 主要是我这个人很懒,不想...

    Android仿抖音上下滑动切换视频
    https://www.jianshu.com/p/af9c0e46725d

     自从各大直播平台可以滑动切换直播间后,公司就出了一大波需求,还要配合各种收费,各种VIP,很是头疼(haha 主要是我这个人很懒,不想加班),后来研究了下 ,也查阅了一些别人写的demo和一些想法,也对此有了一些理解。

    • 1 最开始是打算用RecyclerView来实现的,因为他的复用性很强,用起来也很方便,和SnapHelper相结合便可以实现滑动分页的功能。
      什么是 SnapHelper
      SnapHelper是一个抽象类,官方提供了一个LinearSnapHelper的子类,可以让RecyclerView滚动停止时相应的Item停留中间位置。在25.1.0版本中,官方又提供了一个PagerSnapHelper的子类,可以使RecyclerView像ViewPager一样的效果,一次只能滑一页,而且居中显示。详细源码解读可以看这里让你明明白白的使用RecyclerView——SnapHelper详解,这里我们用到的就是PagerSnapHelper。
      如何使用
      使用非常简单,只需要创建对象之后调用attachToRecyclerView()附着到对应的RecyclerView对象上就可以了。

     

            snapHelper = new PagerSnapHelper();
            snapHelper.attachToRecyclerView(rvPage2);
    

     设置Adapter

     

            videoAdapter = new ListVideoAdapter(urlList);
            layoutManager = new LinearLayoutManager(Page2Activity.this, LinearLayoutManager.VERTICAL, false);
            rvPage2.setLayoutManager(layoutManager);
            rvPage2.setAdapter(videoAdapter);
    

     这样我们就只需要监听RecyclerView的滚动,然后就可以实现我们的逻辑了

     

      rvPage2.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    
    
                }
    
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    switch (newState) {
                        case RecyclerView.SCROLL_STATE_IDLE://停止滚动
                            View view = snapHelper.findSnapView(layoutManager);
                           //TODO 销毁所有视频
                            RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(view);
                            if (viewHolder != null && viewHolder instanceof VideoViewHolder) {
                                  //TODO  启动想要播放的视频
                            }
                            break;
                        case RecyclerView.SCROLL_STATE_DRAGGING://拖动
                            break;
                        case RecyclerView.SCROLL_STATE_SETTLING://惯性滑动
                            break;
                    }
    
                }
            });
    
    • 2 不过RecyclerView有个问题,虽然可以达到切换到效果,但是如果我是上下切换,当我左右快速滑动的时候,也会造成上下切换,当然了,这个可以去监听他的触摸事件,只是每一个item里面还有很多事件要处理,冲突性和复杂性会增加很多,就将其设置为备选方案了。所以后来接触到上下切换的VerticalViewPager,就有了其他的方案。
      • A 和V4包的ViewPager使用一样,适配FragmentPagerAdapter,加载多个Fragment,这样的方式其实很简单,很粗暴,不过性能也是很差的,不建议使用
      • B 适配PagerAdapter,监听setPageTransformer,加载新的数据,通过消息传递到Fragment,刷新数据

     

      class PagerAdapter extends android.support.v4.view.PagerAdapter {
    
    
            private List<HnLiveListModel.LiveListBean> list;
    
            public PagerAdapter(List<HnLiveListModel.LiveListBean> list) {
                this.list = list;
            }
    
            @Override
            public int getCount() {
                if (list.size() > 1) {
                    return Integer.MAX_VALUE;
                } else {
                    return 1;
                }
            }
    
            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }
    
            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                View view = LayoutInflater.from(container.getContext()).inflate(R.layout.live_activity_audience_mask_layout, null);
                int pos = position % list.size();
                HnLiveListModel.LiveListBean data = list.get(pos);
                //遮罩层
                FrescoImageView mFrescoImageView = (FrescoImageView) view.findViewById(R.id.fiv_mask);
                mFrescoImageView.setVisibility(View.VISIBLE);
                if (data != null) {
                    String avator = data.getAvator();
                    mFrescoImageView.setController(FrescoConfig.getController(avator));
                }
                view.setId(position);
                container.addView(view);
                return view;
            }
    
            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView(container.findViewById(position));
            }
        }
    

     

      mVerticalViewPager.setPageTransformer(false, new ViewPager.PageTransformer() {
                private float yPosition;
    
                public float getPosition() {
                    return yPosition;
                }
    
                @Override
                public void transformPage(View page, float position) {
                    page.setTranslationX(page.getWidth() * -position);
                    yPosition = position * page.getHeight();
                    page.setTranslationY(yPosition);
    
    
                    ViewGroup viewGroup = (ViewGroup) page;
                    HnLogUtils.i(TAG, "page.id == " + page.getId() + ", position == " + position);
    
                    if ((position < 0 && viewGroup.getId() != mCurrentItem)) {
                        View roomContainer = viewGroup.findViewById(R.id.room_container);
                        if (roomContainer != null && roomContainer.getParent() != null && roomContainer.getParent() instanceof ViewGroup) {
                            ((ViewGroup) (roomContainer.getParent())).removeView(roomContainer);
                        }
                    }
                    // 满足此种条件,表明需要加载直播视频,以及聊天室了
                    if (viewGroup.getId() == mCurrentItem && position == 0 && mCurrentItem != mRoomUid) {
                        if (mRoomContainer.getParent() != null && mRoomContainer.getParent() instanceof ViewGroup) {
                            ((ViewGroup) (mRoomContainer.getParent())).removeView(mRoomContainer);
                        }
                        EventBus.getDefault().post(new HnLiveEvent(0, HnLiveConstants.EventBus.Close_Dialog, 0));
                        EventBus.getDefault().post(new HnLiveEvent(0, HnLiveConstants.EventBus.Hide_Mask, 0));
                        loadVideoAndChatRoom(viewGroup, mCurrentItem);
                    }
                }
            });
    
     /**
         * 加载房间信息
         *
         * @param viewGroup
         * @param mCurrentItem
         */
        private void loadVideoAndChatRoom(ViewGroup viewGroup, int mCurrentItem) {
            pos = mCurrentItem % list.size();
            HnLogUtils.i(TAG, "当前加载的位置:" + pos + "--->" + mCurrentItem);
            HnLiveListModel.LiveListBean bean = list.get(pos);
    
            //聊天室的fragment只加载一次,以后复用
            if (!mInit) {
                mRoomFragment = HnAudienceRoomFragment.newInstance(bean);
                mFragmentManager.beginTransaction().replace(R.id.fragment_container, mRoomFragment).commitAllowingStateLoss();
                mInit = true;
            } else {
                if (mRoomFragment == null) {
                    mRoomFragment = HnAudienceRoomFragment.newInstance(bean);
                    mFragmentManager.beginTransaction().replace(R.id.fragment_container, mRoomFragment).commitAllowingStateLoss();
                    mInit = true;
                }
                EventBus.getDefault().post(new HnLiveEvent(0, HnLiveConstants.EventBus.Update_Room_Info, bean));
            }
            viewGroup.addView(mRoomContainer);
            this.mRoomUid = mCurrentItem;
        }
    
    
    • C 适配PagerAdapter,初始化每个Item的View ,以View为数据源,适配到adapter中(不过直播中业务复杂,不推荐在直播中使用,小视频可以使用(直接一次性将数据传递过来)),这个方式主要是为了复用播放器,这样就不要添加多个播放器了

     

       for (HnChatVideoSwitchEntity item : mList) {
                if (mActivity == null) return;
                View view = LayoutInflater.from(mActivity).inflate(R.layout.adapter_invite_chat, null);
                //TODO  初始化控件
              
                //TODO  设置点击事件
           
                //TODO  设置数据
               
                mViews.add(view);
            }
    

    adapter里面很简单

     

    public class HnInviteChatAdapter extends PagerAdapter {
    
        private static final String TAG = "DouYinAdapter";
    
        private List<View> mViews;
    
        public HnInviteChatAdapter(List<View> views) {
            this.mViews = views;
        }
    
    
        public void setmViews(List<View> mViews) {
            this.mViews = mViews;
        }
    
        @Override
        public int getCount() {
            return mViews.size();
        }
    
        @Override
        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
            return view == object;
        }
    
        @NonNull
        @Override
        public Object instantiateItem(@NonNull ViewGroup container, int position) {
            Log.d(TAG, "instantiateItem: called");
            container.addView(mViews.get(position));
            return mViews.get(position);
        }
    
        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            Log.d(TAG, "destroyItem: ");
            container.removeView(mViews.get(position));
        }
    
    }
    

    然后当滑动ViewPager时,重置数据就可以了

     

      mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
                @Override
                public void onPageSelected(int position) {
                    mCurrentItem = position;
                    //TODO视频暂停播放
                    if (mIjkVideoView != null) {
                        mIjkVideoView.pause();
                    }
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
                    if (mPlayingPosition == mCurrentItem) return;
                    if (state == VerticalViewPager.SCROLL_STATE_IDLE) {
                        //TODO视频暂停播放
                        stopPlay();
                        //TODO 重置一些控件显示与否
                        releaseView();
                        ViewParent parent = mIjkVideoView.getParent();
                        //TODO移除上个页面的视频控件
                        if (parent != null && parent instanceof FrameLayout) {
                            ((FrameLayout) parent).removeView(mIjkVideoView);
                        }
                        getAnchorData(mCurrentItem);
                    }
                }
            });
    
    

    然后就是更新数据了

     

     private void getAnchorData(int position) {
            mUid = mList.get(position).getUser_id();
            mDbean = mList.get(position);
            mPlayUrl = mDbean.getUser_video();
            
            mCurrentItem = position;
        
            View view = mViews.get(mCurrentItem);
            RelativeLayout mContainer = view.findViewById(R.id.mContainer);
            initItemView(view);
            //TODO 设置控件数据和状态
    
             //添加播放器
            ViewGroup parent = (ViewGroup) mIjkVideoView.getParent();
            if (parent != null) {
                parent.removeAllViews();
            }
            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
            params.addRule(RelativeLayout.CENTER_IN_PARENT);
            mIjkVideoView.setLayoutParams(params);
            mContainer.addView(mIjkVideoView, 0, params);
    
    
            startPlay();
            mPlayingPosition = mCurrentItem;
        }
    



    作者:TXswim
    链接:https://www.jianshu.com/p/af9c0e46725d
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • 一,网上也有很多,看了各位大佬的文章。在此基础上写的。 看效果图吧。 使用到的框架 饺子播放器(重写方便监听播放器生命周期) VideoCaChe(改造播放当前,缓存下一个) 重写LayoutManager 图片glide 万能适配器...
  • 时下最火的莫过抖音了,实现这个效果应该很简单嘛,用ViewPager就可以了。但是等你通过ViewPager来实现的时候,手机内存不够用的情况就会显现出来。有没有更好的方式呢???自然是有,每个人都会用RecyclerView吧,...
  • 1.实现原理 利用小程序内置组件 swiper+video 来制定逻辑的方向 实现方式 设定swiepr-item数量为4,也就是在swiper里面同时... [new, old, now, new] // 向下滑动第二次,也就是在swiper滑动到第三个的时候,启用轮循
  • 小视频及直播页面均实现了类似抖音上下滑动切换效果,还有点赞、评论、商品等功能展示。运行效果:技术实现:编辑器+技术:HBuilderX + vue/NVue/uniapp/vuexiconfont图标:阿里字体图标库自定义导航栏 + 底部Tabbar...
  • 学习笔记-抖音分析报告

    千次阅读 2020-05-28 17:41:25
    学习笔记-产品分析报告-抖音
  • 本文期望通过对比抖音微视的核心功能,体会功能设计和产品定位以及商业模式之间的关系,并结合个人体验提出若干改进建议。 体验版本:抖音11.3.0;微视 6.9.0.588 02 竞品背景分析 2.1 产品发展历史 我们可以...
  • 之前做过一些短视频和直播项目,...在实现短视频的过程中,我根据市面流行的快手和抖音,实现了这两种都有的短视频实现方式。下面我会先介绍抖音的短视频实现方式。 推荐页面 抖音样式推荐页面整体实现的Gif效果...
  • 抖音小程序填坑指南

    千次阅读 2020-06-24 16:49:13
    最近抖音小程序比较火,公司部门也想尝试获取一些流量,于是开始吭哧吭哧不断徘徊于踩坑与填坑之间,下面记录一些最近小程序开发中碰到的各种问题~ 1.https://microapp.bytedance.com/首先是小程序开发平台;这里...
  • iOS抖音 内部方法 名称

    千次阅读 2020-04-28 20:22:17
    "点击重试": "comment_status_retry", "转发[视频]": "im_session_cell_content_aweme", "转发[图片]": "im_session_cell_content_picture", "[图片]": "im_session_cell_content_only_picture", "抖音小助手": ...
  • 去年求职的时候,作为一个产品新人,每次体验产品更多的是...滑动的时候是上下滑动还是左右滑动,是在屏幕中央滑动还是从边缘滑动; 这样的交互是否费劲,是否合理; 是否容易误操作; 点击/滑动之后界面内容出现什么样
  • 【Android 进阶】仿抖音系列之翻页上下滑切换视频(一) 【Android 进阶】仿抖音系列之列表播放视频(二) 【Android 进阶】仿抖音系列之列表播放视频(三) 这一篇,要说实现的是第一篇中的翻页切换视频。 在第一篇...
  • 字节跳动抖音 测试工程师 视频面试

    千次阅读 多人点赞 2020-07-08 06:12:12
    字节跳动2021提前批测试开发 ---------------------------------------...200 - 请求成功 301 - 资源被永久转移大其他URL 404 - 请求的资源不存在 500 - 内部服务器错误 抖音同城的测试 能否准确定位,试试更新定位,...
  • RecyclerView系列之滑动菜单

    千次阅读 2019-06-29 15:06:49
    前面已经实现了 RecyclerView 的下拉刷新和上拉加载更多,给 RecyclerView 添加 header,这两个用的比较多,这次实现的是滑动菜单,实现这个是因为产品经理通常会告诉你,我们要做一个和某某应用一样的效果。...
  • 前一篇已经开发了大部分框架,包含视频上下滑动播放,这次将上次未完成的数据显示友好显示,以及底部音乐走马灯特效,另外优化了加载数据的bug,在dart语言里 & 会自动变成&amp; 也不知道这个bug啥时候修复...
  • 小程序滑动屏幕切换视频

    千次阅读 2019-01-22 11:15:02
    项目需求:滑动屏幕,切换短视频,实现类似抖音等短视频平台的视频切换效果 最终效果图:滑动屏幕自动加载,切换视频和产品图片 分析:video是原生标签,层级太高,在全屏的情况下,其他组件均会被遮挡,所幸小...
  • 安卓View—滑动冲突 ...滑动冲突在开发过程中遇到的情况还挺多的,我以前开发 码助 等项目的时候遇到过,当然在使用抖音的时候也遇到过(当场给学姐提交BUG,结果修复的还挺快),我以前解决滑动冲...
  • AppStore转场pop手势的上下滑动抖音评论的效果非常类似,但与之对比,AppStore页面还增加了左滑pop手势。 我开始是想用苹果自带边缘手势UIScreenEdgePanGestureRecognizer来进行处理,但发现这样只能解决横向...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 145
精华内容 58
关键字:

抖音只能上下滑动吗