1. 程式人生 > >炫麗的朋友圈視訊滾動播放功能

炫麗的朋友圈視訊滾動播放功能

歡迎Follow我的GitHub, 關注我的CSDN.

效果

Book

在應用的資訊流中, 使用者會分享視訊, 連續展示, 這就需要處理視訊滾動播放. 然而, 在列表檢視(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!