继续创造,加快生长!这是我参加「日新计划 10 月更文挑战」的第12天,点击检查活动概况

前语

之前的文章咱们讲到了WX盆友圈动态列表的作用,九宫格控件的完成【传送门】。 而且讲到了发布动态中论题的处理【传送门】。 现已在动态列表中展示超长的文本与特殊文办的处理【传送门】。

能够看到其实咱们的动态列表并不是九宫格和一些图片文本的信息流展示,咱们还包含和和WX盆友圈相同的视频功能。其中有一个快捷入口能够播映视频列表,相似抖音的作用。

信任相似的作用咱们都看的多了,网上也有许多的Demo了,这儿记载一下我的想法和完成思路,仅供咱们参阅。

完成的几种思路

其实这种上下切换视频的作用,市面上大致分为几种思路:

1. 直接运用RecyclerView + SpanHelper。

Activity的布局便是一个RecyclerView, Adapter内部的Item的布局便是一个封面图和文本之类的,都没有播映器。

播映器是在 Activity 独自实例出来的一个,然后添加到Item的布局中,每次翻滚到方位之后先尝试移除 VideoView 然后再加载VideoView。保证页面只要只要一个VideoView然后保证播映作用和内存优化。

核心伪代码如下:


   private void initRecyclerView() {
        mRecyclerView = findViewById(R.id.rv);
        mTikTokAdapter = new TikTokAdapter(mVideoList);
        ViewPagerLayoutManager layoutManager = new ViewPagerLayoutManager(this, OrientationHelper.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setAdapter(mTikTokAdapter);
        layoutManager.setOnViewPagerListener(new OnViewPagerListener() {
            @Override
            public void onInitComplete() {
                //自动播映第index条
                startPlay(mIndex);
            }
            @Override
            public void onPageRelease(boolean isNext, int position) {
                if (mCurPos == position) {
                    mVideoView.release();
                }
            }
            @Override
            public void onPageSelected(int position, boolean isBottom) {
                if (mCurPos == position) return;
                startPlay(position);
            }
        });
    }
    private void startPlay(int position) {
        View itemView = mRecyclerView.getChildAt(0);
        TikTokAdapter.VideoHolder viewHolder = (TikTokAdapter.VideoHolder) itemView.getTag();
        mVideoView.release();
        Utils.removeViewFormParent(mVideoView);
        TiktokBean item = mVideoList.get(position);
        String playUrl = PreloadManager.getInstance(this).getPlayUrl(item.videoDownloadUrl);
        L.i("startPlay: " + "position: " + position + "  url: " + playUrl);
        mVideoView.setUrl(playUrl);
        mController.addControlComponent(viewHolder.mTikTokView, true);
        viewHolder.mPlayerContainer.addView(mVideoView, 0);
        mVideoView.start();
        mCurPos = position;
    }

2. 自定义笔直的 ViewPager

github上面许多笔直的ViewPager自定义类,相似如下

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;
        }
    }
}

运用的办法和RV的办法相似,仅仅假如用ViewPager的话,能够设置预加载的数量,这儿设置的是前后4个,也是经过一个VideoView先移除,然后再添加到Item布局里边。

核心伪代码如下:

  private void initVideoView() {
        mVideoView = new VideoView(this);
        mVideoView.setLooping(true);
        mVideoView.setScreenScaleType(VideoView.SCREEN_SCALE_CENTER_CROP);
        mController = new TikTokController(this);
        mVideoView.setVideoController(mController);
    }
    private void initViewPager() {
        mViewPager = findViewById(R.id.vvp);
        mViewPager.setOffscreenPageLimit(4);
        mTiktok2Adapter = new Tiktok2Adapter(mVideoList);
        mViewPager.setAdapter(mTiktok2Adapter);
        mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            private int mCurItem;
            private boolean mIsReverseScroll;
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                if (position == mCurItem) {
                    return;
                }
                mIsReverseScroll = position < mCurItem;
            }
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                if (position == mCurPos) return;
                startPlay(position);
            }
            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                if (state == VerticalViewPager.SCROLL_STATE_DRAGGING) {
                    mCurItem = mViewPager.getCurrentItem();
                }
                if (state == VerticalViewPager.SCROLL_STATE_IDLE) {
                    mPreloadManager.resumePreload(mCurPos, mIsReverseScroll);
                } else {
                    mPreloadManager.pausePreload(mCurPos, mIsReverseScroll);
                }
            }
        });
    }
    private void startPlay(int position) {
        int count = mViewPager.getChildCount();
        for (int i = 0; i < count; i ++) {
            View itemView = mViewPager.getChildAt(i);
            Tiktok2Adapter.ViewHolder viewHolder = (Tiktok2Adapter.ViewHolder) itemView.getTag();
            if (viewHolder.mPosition == position) {
                mVideoView.release();
                Utils.removeViewFormParent(mVideoView);
                TiktokBean tiktokBean = mVideoList.get(position);
                String playUrl = mPreloadManager.getPlayUrl(tiktokBean.videoDownloadUrl);
                L.i("startPlay: " + "position: " + position + "  url: " + playUrl);
                mVideoView.setUrl(playUrl);
                mController.addControlComponent(viewHolder.mTikTokView, true);
                viewHolder.mPlayerContainer.addView(mVideoView, 0);
                mVideoView.start();
                mCurPos = position;
                break;
            }
        }
    }

因为内部并非是RV完成,所以关于自定义的缓存池咱们需求额外的处理一下,特别是假如参加了视频缓存计划。

public class TiktokAdapter extends PagerAdapter {
    /**
     * View缓存池,从ViewPager中移除的item将会存到这儿面,用来复用
     */
    private List<View> mViewPool = new ArrayList<>();
    private List<TiktokBean> mVideoBeans;
    public TiktokAdapter(List<TiktokBean> videoBeans) {
        this.mVideoBeans = videoBeans;
    }
    @Override
    public int getCount() {
        return mVideoBeans == null ? 0 : mVideoBeans.size();
    }
    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
        return view == o;
    }
    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        Context context = container.getContext();
        View view = null;
        if (mViewPool.size() > 0) {//取第一个进行复用
            view = mViewPool.get(0);
            mViewPool.remove(0);
        }
        ViewHolder viewHolder;
        if (view == null) {
            view = LayoutInflater.from(context).inflate(R.layout.item_tik_tok, container, false);
            viewHolder = new ViewHolder(view);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }
        TiktokBean item = mVideoBeans.get(position);
        //开端预加载
        PreloadManager.getInstance(context).addPreloadTask(item.videoDownloadUrl, position);
        Glide.with(context)
                .load(item.coverImgUrl)
                .placeholder(android.R.color.white)
                .into(viewHolder.mThumb);
        viewHolder.mTitle.setText(item.title);
        viewHolder.mTitle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, "点击了标题", Toast.LENGTH_SHORT).show();
            }
        });
        viewHolder.mPosition = position;
        container.addView(view);
        return view;
    }
    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        View itemView = (View) object;
        container.removeView(itemView);
        TiktokBean item = mVideoBeans.get(position);
        //撤销预加载
        PreloadManager.getInstance(container.getContext()).removePreloadTask(item.videoDownloadUrl);
        //保存起来用来复用
        mViewPool.add(itemView);
    }
    public static class ViewHolder {
        public int mPosition;
        public TextView mTitle;//标题
        public ImageView mThumb;//封面图
        public TikTokView mTikTokView;
        public FrameLayout mPlayerContainer;
        ViewHolder(View itemView) {
            mTikTokView = itemView.findViewById(R.id.tiktok_View);
            mTitle = mTikTokView.findViewById(R.id.tv_title);
            mThumb = mTikTokView.findViewById(R.id.iv_thumb);
            mPlayerContainer = itemView.findViewById(R.id.container);
            itemView.setTag(this);
        }
    }
}

3. ViewPagr2

已然咱们能够用自定义的笔直ViewPager来完成,那么天然支撑笔直翻滚的ViewPager2当然或许完成了。

ViewPager2的机制是根据RV完成的,尽管运用办法和ViewPager的办法差不多,可是不需求咱们自己完成缓存和复用了。

核心的伪代码如下:

 private void initVideoView() {
        mVideoView = new VideoView(this);
        mVideoView.setLooping(true);
        mVideoView.setScreenScaleType(VideoView.SCREEN_SCALE_CENTER_CROP);
        mController = new TikTokController(this);
        mVideoView.setVideoController(mController);
    }
    private void initViewPager() {
        mViewPager = findViewById(R.id.vp2);
        mViewPager.setOffscreenPageLimit(4);
        mTiktok3Adapter = new Tiktok3Adapter(mVideoList);
        mViewPager.setAdapter(mTiktok3Adapter);
        mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
        mViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            private int mCurItem;
            private boolean mIsReverseScroll;
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                if (position == mCurItem) {
                    return;
                }
                mIsReverseScroll = position < mCurItem;
            }
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                if (position == mCurPos) return;
                mViewPager.post(new Runnable() {
                    @Override
                    public void run() {
                        startPlay(position);
                    }
                });
            }
            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                if (state == VerticalViewPager.SCROLL_STATE_DRAGGING) {
                    mCurItem = mViewPager.getCurrentItem();
                }
                if (state == ViewPager2.SCROLL_STATE_IDLE) {
                    mPreloadManager.resumePreload(mCurPos, mIsReverseScroll);
                } else {
                    mPreloadManager.pausePreload(mCurPos, mIsReverseScroll);
                }
            }
        });
        mViewPagerImpl = (RecyclerView) mViewPager.getChildAt(0);
    }
    private void startPlay(int position) {
        int count = mViewPagerImpl.getChildCount();
        for (int i = 0; i < count; i++) {
            View itemView = mViewPagerImpl.getChildAt(i);
            Tiktok3Adapter.ViewHolder viewHolder = (Tiktok3Adapter.ViewHolder) itemView.getTag();
            if (viewHolder.mPosition == position) {
                mVideoView.release();
                Utils.removeViewFormParent(mVideoView);
                TiktokBean tiktokBean = mVideoList.get(position);
                String playUrl = mPreloadManager.getPlayUrl(tiktokBean.videoDownloadUrl);
                L.i("startPlay: " + "position: " + position + "  url: " + playUrl);
                mVideoView.setUrl(playUrl);
                mController.addControlComponent(viewHolder.mTikTokView, true);
                viewHolder.mPlayerContainer.addView(mVideoView, 0);
                mVideoView.start();
                mCurPos = position;
                break;
            }
        }
    }

其实三种计划各有利弊,都能够完成同样的作用,我运用的哪一种计划?

我运用的是ViewPager2计划,为什么?

ViewPager在部分设备上会呈现滑动不跟手的状况,而且不方便完成RV那样加载的作用和预加载的作用。

而运用RV的话,尽管是能够像普通列表相同来完成这个视频滑动作用,可是感觉预加载的逻辑不太好精准的操控,只能在 onBindViewHolder 中开启预加载,在onViewDetachedFromWindow 中移除预加载。

假如是运用ViewPager2的话,因为内部也是RV完成,缓存什么的也不需求咱们管,也能经过RecyclerView.Adapter中完成LoadMore的功能,完成预加载数据的逻辑,还能经过 setOffscreenPageLimit 来精准操控视频预加载的数量。

所以我挑选的是ViewPager2计划。

参加视频缓存

视频的预加载与缓存作用,估计咱们都是运用的开源计划 VideoCache 这种计划了。

它经过创立一个设备的本地代理服务 CacheService,在将视频资源的 url 交给播映器之前,先进行本地的一次转化,并将初始的url作为参数,拼接在本地代理的url上。如:http://127.0.0.1:8090/https://1234.mp4

当咱们把到新的 url 并交给恣意播映器后,播映器的加载都指向本地服务的新地址——即经过 Socket 连接树立的本地服务 CacheService,后者经过解分出恳求中真正的 1234.mp4 地址,创立对应的下载使命,并从下载的文件缓存中,读取 buffer 回来给播映器。

咱们无需联系视频文件是否现已下载,当文件现已下载,此时直接读取本地文件,将数据经过Socket不断传回给播映器。当文件没有下载,此时会新建一个本地文件,并开启长途下载使命,下载过程中,数据流不断涌入本地文件,本地文件巨细、下载进展的改变都会呼应式通知上层;除此之外,新的音视频流数据会经过Socket不断传回给播映器。

详细使用在咱们计划中,就需求在ViewPager2设置 setOffscreenPageLimit 之后,比方预加载4个Item,那么咱们就需求在 Adapter 的 onBindViewHolder 参加预加载资源,在 onViewDetachedFromWindow 移除预加载。

public class TiktokAdapter extends RecyclerView.Adapter<Tiktok3Adapter.ViewHolder> {
    /**
     * 数据源
     */
    private List<TiktokBean> mVideoBeans;
    public TiktokAdapter(List<TiktokBean> videoBeans) {
        this.mVideoBeans = videoBeans;
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tik_tok, parent, false);
        return new ViewHolder(itemView);
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Context context = holder.itemView.getContext();
        TiktokBean item = mVideoBeans.get(position);
        //开端预加载
        PreloadManager.getInstance(context).addPreloadTask(item.videoDownloadUrl, position);
        Glide.with(context)
                .load(item.coverImgUrl)
                .placeholder(android.R.color.white)
                .into(holder.mThumb);
        holder.mTitle.setText(item.title);
        holder.mPosition = position;
    }
    @Override
    public void onViewDetachedFromWindow(@NonNull ViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        TiktokBean item = mVideoBeans.get(holder.mPosition);
        //撤销预加载
        PreloadManager.getInstance(holder.itemView.getContext()).removePreloadTask(item.videoDownloadUrl);
    }
    @Override
    public int getItemCount() {
        return mVideoBeans != null ? mVideoBeans.size() : 0;
    }
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public int mPosition;
        public TextView mTitle;//标题
        public ImageView mThumb;//封面图
        public TikTokView mTikTokView;
        public FrameLayout mPlayerContainer;
        ViewHolder(View itemView) {
            super(itemView);
            mTikTokView = itemView.findViewById(R.id.tiktok_View);
            mTitle = mTikTokView.findViewById(R.id.tv_title);
            mThumb = mTikTokView.findViewById(R.id.iv_thumb);
            mPlayerContainer = itemView.findViewById(R.id.container);
            itemView.setTag(this);
        }
    }
}

VideoCache的问题与优化思路:

1.不支撑直播流 2.不支撑Seek 3.不支撑优先级

比方5分钟的视频,咱们缓存了前1分钟,此时Seek到第3分钟,那么此时仍是会当场长途加载视频并不会写入缓存,下次再滑回这个视频,仍是只缓存了1分钟的视频,再Seek到第3分钟面仍是会当场长途加载视频数据。

主要是Seek对视频进行切片,视频碎片的问题,假如要解决还要考虑视频的拼接,直播流也是同样的逻辑。假如考虑这个就比较复杂。

性咱们的体系限制是只能发MP4格式,而且咱们的体系是不支撑1分钟以上长视频,所以咱们的视频都是不支撑Seek的。就没有考虑这些问题。

而预加载的优先级,因为咱们设置的是 setOffscreenPageLimit(4) ,预加载4个视频文件,此时按照道理应该是优先加载下一个视频,而不是第二个第三个视频。

不知道咱们有没有更好的计划呢?

详细计划完成

这儿咱们ViewPager的Adapter运用的是BRVAH的计划,视频播映UI与操控运用的是 JzvdStd 。视频播映内核运用的是 ExoPlayer 。

初始化ViewPager2

 /**
     * ViewPager2和内部的RV初始化和监听
     */
    private void initPager() {
        mViewPager.setOffscreenPageLimit(4);
        mViewPager.setAdapter(mPresenter.mTiktokAdapter);
        mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
        mViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            private int mCurItem;
            private boolean mIsReverseScroll;
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                if (position == mCurItem) {
                    return;
                }
                mIsReverseScroll = position < mCurItem;
            }
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                if (position == mCurPos) return;
                mViewPager.post(() -> startPlay(position));
            }
            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                if (state == ViewPager2.SCROLL_STATE_DRAGGING) {
                    mCurItem = mViewPager.getCurrentItem();
                }
                if (state == ViewPager2.SCROLL_STATE_IDLE) {
                    mPreloadManager.resumePreload(mCurPos, mIsReverseScroll);
                } else {
                    mPreloadManager.pausePreload(mCurPos, mIsReverseScroll);
                }
            }
        });
        //ViewPage2内部是经过RecyclerView去完成的,它位于ViewPager2的第0个方位
        mViewPagerImpl = (RecyclerView) mViewPager.getChildAt(0);
    }

加载更多,以及Item的点击监听:

        //内部Adapter的滑动监听,监听加载更多
        mPresenter.mTiktokAdapter.setEnableLoadMore(false);
        mPresenter.mTiktokAdapter.setPreLoadNumber(2);
        mPresenter.mTiktokAdapter.setOnLoadMoreListener(() -> {
            //调用接口获取预加载的数据
            mPresenter.getNextVideos();
        }, mViewPagerImpl);
        //Item的点击事件
        mPresenter.mTiktokAdapter.setOnItemChildClickListener((adapter, view, position) -> {
        // 。。。
        });
    }
    //滑动之后开端播映视频的逻辑
    private void startPlay(int position) {
        int count = mViewPagerImpl.getChildCount();
        for (int i = 0; i < count; i++) {
            //直接像TikTokActivity那样直接获取到索引的Tag拿到ViewHolder直接调用,这样能够省去遍历
            View itemView = mViewPagerImpl.getChildAt(i);
            TiktokAdapter.TiktokViewHolder viewHolder = (TiktokAdapter.TiktokViewHolder) itemView.getTag();
            if (viewHolder.mPosition == position) {
                Jzvd.releaseAllVideos();
                NewsFeedAdatperBean item = mPresenter.mDatas.get(position);
                ImageInfo videoUrlInfo = item.myNewsResources.get(1);
                mCurPlayVideoView = viewHolder.mVideoView;
                //运用预加载的缓存路径
                String proxyUrl = mPreloadManager.getPlayUrl(videoUrlInfo.bigImageUrl);
                YYLogUtils.w("视频播映proxyUrl:" + proxyUrl);
                mCurPlayVideoView.setUp(proxyUrl, "", Jzvd.SCREEN_NORMAL, JZMediaExo.class);
                //自定义的播映办法-强制性指定播映的裁剪形式为默认的形式
                mCurPlayVideoView.startVideo();
                YYLogUtils.w("视频播映:" + "startVideo");
                mCurPos = position;
                //长按事件-下载视频
                viewHolder.mVideoView.textureViewContainer.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        showSavePopup(videoUrlInfo.bigImageUrl);
                        return false;
                    }
                });
                break;
            }
        }
    }   

Adapter中因为是BRVAH完成的,反倒是简略一点,需求留意的是长视频与宽视频的裁剪。

public class TiktokAdapter extends BaseQuickAdapter<NewsFeedAdatperBean, TiktokAdapter.TiktokViewHolder> {
    private String mMyMemberId = "";
    private final int mScreenHeight;
    private final int mScreenWidth;
    public TiktokAdapter(@Nullable List<NewsFeedAdatperBean> data) {
        super(R.layout.item_tiktok, data);
        mScreenHeight = ScreenUtils.getScreenHeight(CommUtils.getContext());
        mScreenWidth = ScreenUtils.getScreenWidth(CommUtils.getContext());
    }
    @Override
    protected void convert(TiktokViewHolder helper, NewsFeedAdatperBean item) {
        ImageInfo thumImgInfo = item.myNewsResources.get(0);
        ImageInfo videoUrlInfo = item.myNewsResources.get(1);
        //开端预加载
        VideoCachePreloadManager.getInstance(mContext).addPreloadTask(videoUrlInfo.bigImageUrl, helper.getAdapterPosition());
        //设置封面
        int videoWidth = videoUrlInfo.getImageViewWidth();
        int videoHeight = videoUrlInfo.getImageViewHeight();
        //判别是横视频仍是竖视频,依据宽高份额切换裁剪形式
        if (videoWidth >= videoHeight) {
            //横视频,宽度填满,高度按份额核算
            helper.mVideoView.thumbImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        } else {
            //竖视频
            float videoRatio = (float) videoWidth / (float) videoHeight;
            float parentRatio = (float) mScreenWidth / (float) mScreenHeight;
            if (videoRatio > parentRatio) {
                //无需裁剪
                helper.mVideoView.thumbImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
            } else {
                //视频太高了-裁剪
                helper.mVideoView.thumbImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            }
        }
        GlideImageEngine.get().imageLoad(helper.mVideoView.thumbImageView, thumImgInfo.thumbnailUrl);
        helper.mPosition = helper.getAdapterPosition();
        helper.addOnClickListener(R.id.iv_publisher_avatar, R.id.iv_member_follow, R.id.ll_likes_box, R.id.tv_comment_num, R.id.tv_share_num);
    }
    @Override
    public void onViewDetachedFromWindow(@NonNull TiktokViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        NewsFeedAdatperBean item = mData.get(holder.mPosition);
        ImageInfo videoUrlInfo = item.myNewsResources.get(1);
        //撤销预加载
        VideoCachePreloadManager.getInstance(mContext).removePreloadTask(videoUrlInfo.bigImageUrl);
    }
    public static class TiktokViewHolder extends BaseViewHolder {
        public int mPosition;
        public DouYinVideoView mVideoView;
        public TiktokViewHolder(View view) {
            super(view);
            view.setTag(this);
            mVideoView = view.findViewById(R.id.jz_video);
        }
    }
}

结语

其实三种计划市面上都有完成的使用上线,都是能够做的,我个人仅仅觉得RV比较方便所以挑选的ViewPager2,其实内部的操控都是根据现找到了RV再进行的操作。

假如后期扩展需求像抖音相同左右滑动进入概况页面,咱们也能够运用嵌套ViewPager完成,或许DrawerLayout完成,又或许完全自定义View完成,都是能够的。

关于ViewPager2嵌套ViewPager的问题,之前的文章有讲到过【传送门】。因为咱们的需求并没有做左右滑动进行概况的逻辑,所以我也并没有进行尝试。假如有坑欢迎小伙伴分享一下哦。

因为咱们做的仅仅简略的版别,详细作用示例在文章开头现已贴出,假如你有更好的计划,或许优化的空间都也能够一同交流一下。如有讹夺的地方还请指出,假如有疑问也能够在谈论区咱们一同讨论哦。

假如感觉本文对你有一点点的启发,还望你能 点赞 支撑一下,你的支撑是我最大的动力。

Ok,这一期就此完结。

记录仿抖音的视频播放并缓存预加载视频的效果实现