新版的百度貼吧,網易新聞中有看視頻的界面。
是隨着view的滾動自動加載的。

如圖所示,很方便查看。
因為項目需要,我在開發一個APP,也需要查看視頻,便想實現一個差不多功能的。
經過搜索,我發現GITHUB上有這個開源的東西,可以很方便的實現這樣的效果
VideoPlayerManager
試着做了個Demo,在此記錄下,以后自己查起來也方便。
要使用這個很方便,只需要在android studio的build.gradle文件里加入以下內容就行了。
dependencies {
compile 'com.github.danylovolokh:video-player-manager:0.2.0'
compile 'com.github.danylovolokh:list-visibility-utils:0.2.0'
}
先從布局開始吧!
首先,需要一個recycleview。
所以定義一個布局:
video_watch_layout.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:orientation="vertical" android:layout_width="match_parent" 4 android:layout_height="match_parent"> 5 6 <android.support.v7.widget.RecyclerView 7 android:id="@+id/video_watch_list" 8 android:layout_width="match_parent" 9 android:layout_height="match_parent"> 10 11 </android.support.v7.widget.RecyclerView> 12 13 14 15 </LinearLayout>
然后再定義它的item
video_watch_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="320dp"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!--播放器-->
<com.volokh.danylo.video_player_manager.ui.VideoPlayerView
android:id="@+id/item_video_vpv_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/item_video_tv_title"/>
<!--背景-->
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/item_video_iv_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/item_video_tv_title"
/>
<!--標題-->
<TextView
android:id="@+id/item_video_tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
</LinearLayout>
其中SimpleDraweeView這個控件,可以改成Imageview,用來顯示視頻封面圖的。
下面是展示視頻的Activity的代碼。
package com.duanqu.Idea.activity; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import com.duanqu.Idea.Adapter.OnlineVideoListItem; import com.duanqu.Idea.Adapter.VideoListItem; import com.duanqu.Idea.Adapter.VideoWatchAdapter; import com.duanqu.Idea.R; import com.volokh.danylo.video_player_manager.manager.PlayerItemChangeListener; import com.volokh.danylo.video_player_manager.manager.SingleVideoPlayerManager; import com.volokh.danylo.video_player_manager.manager.VideoItem; import com.volokh.danylo.video_player_manager.manager.VideoPlayerManager; import com.volokh.danylo.video_player_manager.meta.MetaData; import com.volokh.danylo.visibility_utils.calculator.DefaultSingleItemCalculatorCallback; import com.volokh.danylo.visibility_utils.calculator.ListItemsVisibilityCalculator; import com.volokh.danylo.visibility_utils.calculator.SingleListViewItemActiveCalculator; import com.volokh.danylo.visibility_utils.items.ListItem; import com.volokh.danylo.visibility_utils.scroll_utils.RecyclerViewItemPositionGetter; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2016/7/28. */ public class VideoWatchActivity extends AppCompatActivity implements View.OnClickListener{ private RecyclerView mRecyclerView; //視頻數據,相當於普通adapter里的datas private List<VideoListItem> mLists = new ArrayList<>(); //它充當ListItemsVisibilityCalculator和列表(ListView, RecyclerView)之間的適配器(Adapter)。 private RecyclerViewItemPositionGetter mItemsPositionGetter; //ListItemsVisibilityCalculator可以追蹤滑動的方向並在過程中計算每個Item的可見度 //SingleListViewItemActiveCalculator會在滑動時獲取每個View的可見度百分比. //所以其構造方法里需要傳入mLists,而mLists里的每個item實現了ListItem接口 //的getVisibilityPercents方法,也就是返回當前item可見度的方法. //這樣ListItemsVisibilityCalculator就可以計算當前item的可見度了. private final ListItemsVisibilityCalculator mVideoVisibilityCalculator = new SingleListViewItemActiveCalculator(new DefaultSingleItemCalculatorCallback(), mLists); //SingleVideoPlayerManager就是只能同時播放一個視頻。 //當一個view開始播放時,之前那個就會停止 private final VideoPlayerManager<MetaData> mVideoPlayerManager = new SingleVideoPlayerManager(new PlayerItemChangeListener() { @Override public void onPlayerItemChanged(MetaData metaData) { } }); private int mScrollState; private LinearLayoutManager mLayoutManager = new LinearLayoutManager(this); private static final String URL = "http://dn-chunyu.qbox.me/fwb/static/images/home/video/video_aboutCY_A.mp4"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.video_watch_layout); mRecyclerView = (RecyclerView) findViewById(R.id.video_watch_list); //添加視頻數據 for (int i = 0; i < 10; ++i) { mLists.add(new OnlineVideoListItem(mVideoPlayerManager, "測試", "http://115.159.159.65:8080/EAsy/cover.jpg", URL));
} mRecyclerView.setLayoutManager(mLayoutManager); VideoWatchAdapter adapter = new VideoWatchAdapter(mLists); mRecyclerView.setAdapter(adapter); ////////////////////////////////////////////// //這里是文檔上默認的寫法,直接復制下來。 //查看了下源碼其中VisibilityCalculator.onScrollStateIdle的這 //個方法又調用了方法calculateMostVisibleItem,用來計算滑動狀態改變時 //的最大可見度的item.這個方法的計算方法是這樣的:當view無論是向上還是 //向下滾動時,在滾動的過程中,計算可見度最大的item。當滾動狀態為空閑時 //此時最后一個計算得出的可見度最大的item就是當前可見度最大的item //而onScroll方法是處理item滾出屏幕后的計算,用於發現新的活動item mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int scrollState) { mScrollState = scrollState; if(scrollState == RecyclerView.SCROLL_STATE_IDLE && !mLists.isEmpty()){ mVideoVisibilityCalculator.onScrollStateIdle( mItemsPositionGetter, mLayoutManager.findFirstVisibleItemPosition(), mLayoutManager.findLastVisibleItemPosition()); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if(!mLists.isEmpty()){ mVideoVisibilityCalculator.onScroll( mItemsPositionGetter, mLayoutManager.findFirstVisibleItemPosition(), mLayoutManager.findLastVisibleItemPosition() - mLayoutManager.findFirstVisibleItemPosition() + 1, mScrollState); } } }); mItemsPositionGetter = new RecyclerViewItemPositionGetter(mLayoutManager, mRecyclerView); ///////////////////////////////////////////// } //文檔上的默認實現,復制下來 //onResume()中調用方法,使屏幕亮起時啟動對View的可見度的計算。 @Override public void onResume() { super.onResume(); if(!mLists.isEmpty()){ // need to call this method from list view handler in order to have filled list mRecyclerView.post(new Runnable() { @Override public void run() { mVideoVisibilityCalculator.onScrollStateIdle( mItemsPositionGetter, mLayoutManager.findFirstVisibleItemPosition(), mLayoutManager.findLastVisibleItemPosition()); } }); } } @Override public void onClick(View v) { } @Override public void onStop() { super.onStop(); mVideoPlayerManager.resetMediaPlayer(); // 頁面不顯示時, 釋放播放器 } }
其次是
ListItem的實現,其實現的是當前item的可見度計算。
當然也保存了視頻數據的一些信息,供Adapter使用
package com.duanqu.Idea.Adapter; import android.graphics.Rect; import android.support.annotation.DrawableRes; import android.view.View; import com.volokh.danylo.video_player_manager.manager.VideoItem; import com.volokh.danylo.video_player_manager.manager.VideoPlayerManager; import com.volokh.danylo.video_player_manager.meta.CurrentItemMetaData; import com.volokh.danylo.video_player_manager.meta.MetaData; import com.volokh.danylo.visibility_utils.items.ListItem; /** * 基本視頻項, 實現適配項和列表項 * <p/> * Created by wangchenlong on 16/1/27. */ public abstract class VideoListItem implements VideoItem, ListItem { private final Rect mCurrentViewRect; // 當前視圖的方框 private final VideoPlayerManager<MetaData> mVideoPlayerManager; // 視頻播放管理器 private final String mTitle; // 標題 private final String CoverImageUrl; // 圖片資源 // 構造器, 輸入視頻播放管理器 public VideoListItem( VideoPlayerManager<MetaData> videoPlayerManager, String title, String imageResource) { mVideoPlayerManager = videoPlayerManager; mTitle = title; CoverImageUrl = imageResource; mCurrentViewRect = new Rect(); } // 視頻項的標題 public String getTitle() { return mTitle; } public String getCoverImageUrl() { return CoverImageUrl; } // 顯示可視的百分比程度 @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; } return percents; } @Override public void setActive(View newActiveView, int newActiveViewPosition) { VideoWatchAdapter.VideoViewHolder viewHolder = (VideoWatchAdapter.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 boolean viewIsPartiallyHiddenTop() { return mCurrentViewRect.top > 0; } // 底部出現 private boolean viewIsPartiallyHiddenBottom(int height) { return mCurrentViewRect.bottom > 0 && mCurrentViewRect.bottom < height; } }
對這個類進行繼承
package com.duanqu.Idea.Adapter; import android.support.annotation.DrawableRes; import com.volokh.danylo.video_player_manager.manager.VideoPlayerManager; import com.volokh.danylo.video_player_manager.meta.MetaData; import com.volokh.danylo.video_player_manager.ui.VideoPlayerView; /** * Created by Administrator on 2016/7/28. */ public class OnlineVideoListItem extends VideoListItem { private final String mOnlineUrl; // 資源文件描述 public OnlineVideoListItem( VideoPlayerManager<MetaData> videoPlayerManager, String title, String imageResource, String onlineUrl ) { super(videoPlayerManager, title, imageResource); mOnlineUrl = onlineUrl; } @Override public void playNewVideo(MetaData currentItemMetaData, VideoPlayerView player, VideoPlayerManager<MetaData> videoPlayerManager) { videoPlayerManager.playNewVideo(currentItemMetaData, player, mOnlineUrl); } }
復寫一個playNewVideo方法。
最后是recycleview的Adapter方法:
package com.duanqu.Idea.Adapter; import android.content.Context; import android.net.Uri; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.duanqu.Idea.R; import com.volokh.danylo.video_player_manager.ui.MediaPlayerWrapper; import com.volokh.danylo.video_player_manager.ui.VideoPlayerView; import java.util.List; /** * Created by Administrator on 2016/7/28. */ public class VideoWatchAdapter extends RecyclerView.Adapter<VideoWatchAdapter.VideoViewHolder>{ private final List<VideoListItem> mList; // 視頻項列表 // 構造器 public VideoWatchAdapter(List<VideoListItem> list) { mList = list; } @Override public VideoWatchAdapter.VideoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.video_watch_list_item, parent, false); // 必須要設置Tag, 否則無法顯示 VideoWatchAdapter.VideoViewHolder holder = new VideoWatchAdapter.VideoViewHolder(view); view.setTag(holder); return new VideoWatchAdapter.VideoViewHolder(view); } @Override public void onBindViewHolder(final VideoWatchAdapter.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 { VideoPlayerView mVpvPlayer; // 播放控件 ImageView mIvCover; // 覆蓋層 TextView mTvTitle; // 標題 private Context mContext; private MediaPlayerWrapper.MainThreadMediaPlayerListener mPlayerListener; public VideoViewHolder(View itemView) { super(itemView); mVpvPlayer = (VideoPlayerView) itemView.findViewById(R.id.item_video_vpv_player); // 播放控件 mTvTitle = (TextView) itemView.findViewById(R.id.item_video_tv_title); mIvCover = (ImageView) itemView.findViewById(R.id.item_video_iv_cover); 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.setImageURI(Uri.parse(vli.getCoverImageUrl())); } // 返回播放器 public VideoPlayerView getVpvPlayer() { return mVpvPlayer; } } }
嘿,寫了一點,就開始復制粘貼了。。反正是寫給自己看的。。。
