新版的百度贴吧,网易新闻中有看视频的界面。
是随着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; } } }
嘿,写了一点,就开始复制粘贴了。。反正是写给自己看的。。。