美文网首页实用开发技术科普贴。高级Android
实现类似朋友圈视频的滚动播放功能

实现类似朋友圈视频的滚动播放功能

作者: SpikeKing | 来源:发表于2016-01-30 13:00 被阅读4530次

    欢迎Follow我的GitHub, 关注我的简书. 其余参考Android目录.

    在应用的信息流中, 用户会分享视频, 连续展示, 这就需要处理视频滚动播放. 然而, 在列表视图(RecyclerView)中使用MediaPlayer播放视频时, 会产生一些问题, 即无法同步控制视频的播放和停止. 使用控件库可以解决这一问题.

    滚动播放功能: 在页面中, 判断视频的可视比例, 最大视频项开始播放, 其余视频项关闭, 滚动中自动控制切换视频状态. 让我们来看看如何实现这一功能.

    本文源码的GitHub下载地址.

    使用的视频管理库.

        // 视频播放库
        compile 'com.github.danylovolokh:video-player-manager:0.2.0'
        compile 'com.github.danylovolokh:list-visibility-utils:0.2.0'
    

    效果


    效果

    1. 基础

    依赖注入, 图片加载, 和视频播放.

        compile 'com.jakewharton:butterknife:7.0.1' // 依赖注入
        compile 'com.squareup.picasso:picasso:2.5.2' // 图片加载
    
        // 视频播放库
        compile 'com.github.danylovolokh:video-player-manager:0.2.0'
        compile 'com.github.danylovolokh:list-visibility-utils:0.2.0'
    

    首页跳转到Fragment, 可以选择本地视频或者网络视频两种方式.

    public class MainActivity extends AppCompatActivity {
    
        public static final int LOCAL = 0; // 本地
        public static final int ONLINE = 1; // 在线
    
        @Bind(R.id.main_t_toolbar) Toolbar mTToolbar;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
    
            mTToolbar.setTitle("列表");
            setSupportActionBar(mTToolbar);
    
            if (savedInstanceState == null) {
                getSupportFragmentManager()
                        .beginTransaction()
                        .replace(R.id.main_fl_container, VideoListFragment.newInstance(LOCAL))
                        .commit();
            }
        }
    
        @Override public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    
        @Override public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                case R.id.enable_local_video:
                    if (!item.isChecked()) {
                        getSupportFragmentManager()
                                .beginTransaction()
                                .replace(R.id.main_fl_container, VideoListFragment.newInstance(LOCAL))
                                .commit();
                    }
                    break;
                case R.id.enable_online_video:
                    if (!item.isChecked()) {
                        getSupportFragmentManager()
                                .beginTransaction()
                                .replace(R.id.main_fl_container, VideoListFragment.newInstance(ONLINE))
                                .commit();
                    }
                    break;
            }
    
            item.setChecked(!item.isChecked());
    
            return true;
        }
    }
    

    使用Fragment的工厂模式添加参数. 通过菜单选项可以切换模式.
    item.setChecked(!item.isChecked());改变切换状态


    2. 视频列表

    设置Video列表的Adapter, 添加滚动状态监听, 实现动态切换视频.
    ItemsPositionGetter判断显示百分比, 提供回调控制视频状态.
    通过Fragment的设置参数, 判断播放使用本地视频还是网络视频.

        @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            Bundle args = getArguments();
            if (args != null) {
                // 设置类型
                if (args.getInt(VIDEO_TYPE_ARG) == MainActivity.LOCAL) {
                    initLocalVideoList();
                } else {
                    initOnlineVideoList();
                }
            } else {
                initLocalVideoList();
            }
    
            mRvList.setHasFixedSize(true);
    
            mLayoutManager = new LinearLayoutManager(getActivity());
            mRvList.setLayoutManager(mLayoutManager);
    
            VideoListAdapter adapter = new VideoListAdapter(mList);
    
            mRvList.setAdapter(adapter);
    
            // 获取Item的位置
            mItemsPositionGetter = new RecyclerViewItemPositionGetter(mLayoutManager, mRvList);
            mRvList.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int scrollState) {
                    mScrollState = scrollState;
                    if (scrollState == RecyclerView.SCROLL_STATE_IDLE && !mList.isEmpty()) {
                        mVisibilityCalculator.onScrollStateIdle(
                                mItemsPositionGetter,
                                mLayoutManager.findFirstVisibleItemPosition(),
                                mLayoutManager.findLastVisibleItemPosition());
                    }
                }
    
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    if (!mList.isEmpty()) {
                        mVisibilityCalculator.onScroll(
                                mItemsPositionGetter,
                                mLayoutManager.findFirstVisibleItemPosition(),
                                mLayoutManager.findLastVisibleItemPosition() -
                                        mLayoutManager.findFirstVisibleItemPosition() + 1,
                                mScrollState);
                    }
                }
            });
        }
    

    视频列表主要是监听出现百分比, 动态切换视频.

    3. 适配器

    适配器和ViewHolder. 绑定视频元素, 播放监听控制覆盖层的显示与隐藏.

    /**
     * 视频列表的适配器
     * <p/>
     * Created by wangchenlong on 16/1/27.
     */
    public class VideoListAdapter extends RecyclerView.Adapter<VideoListAdapter.VideoViewHolder> {
    
        private final List<VideoListItem> mList; // 视频项列表
    
        // 构造器
        public VideoListAdapter(List<VideoListItem> list) {
            mList = list;
        }
    
        @Override
        public VideoListAdapter.VideoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_video, parent, false);
    
            // 必须要设置Tag, 否则无法显示
            VideoListAdapter.VideoViewHolder holder = new VideoListAdapter.VideoViewHolder(view);
            view.setTag(holder);
    
            return new VideoListAdapter.VideoViewHolder(view);
        }
    
        @Override
        public void onBindViewHolder(final VideoListAdapter.VideoViewHolder holder, int position) {
            VideoListItem videoItem = mList.get(position);
            holder.bindTo(videoItem);
        }
    
        @Override public int getItemCount() {
            return mList.size();
        }
    
        public static class VideoViewHolder extends RecyclerView.ViewHolder {
            @Bind(R.id.item_video_vpv_player) VideoPlayerView mVpvPlayer; // 播放控件
            @Bind(R.id.item_video_iv_cover) ImageView mIvCover; // 覆盖层
            @Bind(R.id.item_video_tv_title) TextView mTvTitle; // 标题
            @Bind(R.id.item_video_tv_percents) TextView mTvPercents; // 百分比
    
            private Context mContext;
            private MediaPlayerWrapper.MainThreadMediaPlayerListener mPlayerListener;
    
            public VideoViewHolder(View itemView) {
                super(itemView);
                ButterKnife.bind(this, itemView);
    
                mContext = itemView.getContext().getApplicationContext();
                mPlayerListener = new MediaPlayerWrapper.MainThreadMediaPlayerListener() {
                    @Override
                    public void onVideoSizeChangedMainThread(int width, int height) {
                    }
    
                    @Override
                    public void onVideoPreparedMainThread() {
                        // 视频播放隐藏前图
                        mIvCover.setVisibility(View.INVISIBLE);
                    }
    
                    @Override
                    public void onVideoCompletionMainThread() {
                    }
    
                    @Override
                    public void onErrorMainThread(int what, int extra) {
                    }
    
                    @Override
                    public void onBufferingUpdateMainThread(int percent) {
                    }
    
                    @Override
                    public void onVideoStoppedMainThread() {
                        // 视频暂停显示前图
                        mIvCover.setVisibility(View.VISIBLE);
                    }
                };
    
                mVpvPlayer.addMediaPlayerListener(mPlayerListener);
            }
    
            public void bindTo(VideoListItem vli) {
                mTvTitle.setText(vli.getTitle());
                mIvCover.setVisibility(View.VISIBLE);
                Picasso.with(mContext).load(vli.getImageResource()).into(mIvCover);
            }
    
            // 返回播放器
            public VideoPlayerView getVpvPlayer() {
                return mVpvPlayer;
            }
    
            // 返回百分比
            public TextView getTvPercents() {
                return mTvPercents;
            }
        }
    }
    

    注意, 在onCreateViewHolder中, 在View的Tag中绑定所属的ViewHolder. 视频项类VideoListItem会在Tag中提取ViewHolder, 设置显示效果.


    4. 视频项

    通过ItemsPositionGetter类提供的接口, 返回显示比例, 根据显示区域的大小, 控制视频播放的启动还是停止, 实现自动切换视频状态功能.

    public abstract class VideoListItem implements VideoItem, ListItem {
    
        private final Rect mCurrentViewRect; // 当前视图的方框
        private final VideoPlayerManager<MetaData> mVideoPlayerManager; // 视频播放管理器
        private final String mTitle; // 标题
        @DrawableRes private final int mImageResource; // 图片资源
    
        // 构造器, 输入视频播放管理器
        public VideoListItem(
                VideoPlayerManager<MetaData> videoPlayerManager,
                String title,
                @DrawableRes int imageResource) {
            mVideoPlayerManager = videoPlayerManager;
            mTitle = title;
            mImageResource = imageResource;
    
            mCurrentViewRect = new Rect();
        }
    
        // 视频项的标题
        public String getTitle() {
            return mTitle;
        }
    
        // 视频项的背景
        public int getImageResource() {
            return mImageResource;
        }
    
        // 显示可视的百分比程度
        @Override public int getVisibilityPercents(View view) {
            int percents = 100;
    
            view.getLocalVisibleRect(mCurrentViewRect);
            int height = view.getHeight();
    
            if (viewIsPartiallyHiddenTop()) {
                percents = (height - mCurrentViewRect.top) * 100 / height;
            } else if (viewIsPartiallyHiddenBottom(height)) {
                percents = mCurrentViewRect.bottom * 100 / height;
            }
    
            // 设置百分比
            setVisibilityPercentsText(view, percents);
    
            return percents;
        }
    
        @Override public void setActive(View newActiveView, int newActiveViewPosition) {
            VideoListAdapter.VideoViewHolder viewHolder =
                    (VideoListAdapter.VideoViewHolder) newActiveView.getTag();
            playNewVideo(new CurrentItemMetaData(newActiveViewPosition, newActiveView),
                    viewHolder.getVpvPlayer(), mVideoPlayerManager);
        }
    
        @Override public void deactivate(View currentView, int position) {
            stopPlayback(mVideoPlayerManager);
        }
    
        @Override public void stopPlayback(VideoPlayerManager videoPlayerManager) {
            videoPlayerManager.stopAnyPlayback();
        }
    
        // 显示百分比
        private void setVisibilityPercentsText(View currentView, int percents) {
            VideoListAdapter.VideoViewHolder vh =
                    (VideoListAdapter.VideoViewHolder) currentView.getTag();
            String percentsText = "可视百分比: " + String.valueOf(percents);
            vh.getTvPercents().setText(percentsText);
        }
    
        // 顶部出现
        private boolean viewIsPartiallyHiddenTop() {
            return mCurrentViewRect.top > 0;
        }
    
        // 底部出现
        private boolean viewIsPartiallyHiddenBottom(int height) {
            return mCurrentViewRect.bottom > 0 && mCurrentViewRect.bottom < height;
        }
    }
    

    动画效果

    动画

    虽然使用视频播放的管理器, 但播放功能还是需要注意一些细节. 毕竟视频播放是个比较复杂的过程, 需要考虑的很多事情.

    参考

    OK, that's all! Enjoy it!

    相关文章

      网友评论

      • afe0c4c1d3c1:快速滑动崩溃 不知道楼主解决了没有
      • Archer_Coder:E/AndroidRuntime: FATAL EXCEPTION: pool-1-thread-1
        E/AndroidRuntime: Process: com.example.kongjian.scrollplaydemo, PID: 30299
        E/AndroidRuntime: java.lang.IllegalStateException: cannot stop. Player in mState ERROR
      • Archer_Coder:快速滑动就崩了
      • psj_psj:正好学习下
      • 水一瓢:写的蛮好的
        Archer_Coder:@SpikeKing 哥们,回复一下
        SpikeKing:@水一瓢 蛮好玩的流程控制.
      • b488620a100f:………太难了身为一个学美术每一个字都认识连一起完全不认识
        SpikeKing:@仙女山 视频播放,即使简单的东西,也挺复杂的,还需要认真学些基础。

      本文标题:实现类似朋友圈视频的滚动播放功能

      本文链接:https://www.haomeiwen.com/subject/oeqjkttx.html