RecyclerView 视频
例:
.......
1.项目框架模式:MVP(得分点);注意:分包分层,避免内存泄漏;
2.图片加载:Fresco图片加载框架;
3.网络加载框架:retrofit;使用Retrofit+RxJava+okHttp实现网络加载;
5.列表数据展示使用RecyclerView;
1、MVP分包明确,避免内存泄漏,必须对P成V层抽取基类,否则无分;
2、访问网络接口使用retrofit+Rxjava,必须封装retrofit单例工具类,否则无分;
3、图片展示使用Fresco图片加载框架,视频展示*选择播放器;
4、使用RecyclerView实现图一图二所示的视频模块和图片模块,
5、左右滑动可以相互切换当前模块;
6、给RecyclerView添加分割线
7、数据必须解析正确,必须使用retrofit和Rxjava;
8、图片模块可能包含GIF,使用is_gif 字段判断
9、最终效果和图一图二保持一致,视频能够正确播放
依赖:
dependencies { compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' compile 'jp.wasabeef:glide-transformations:2.0.1' compile 'com.android.support:cardview-v7:22.2.1' implementation 'com.android.support:recyclerview-v7:26.1.0' }
权限:
<uses-permission android:name="android.permission.INTERNET"/>
主方法类:
public class MainActivity extends AppCompatActivity { private Toolbar toolbar; private RecyclerView recyclerView; private VideoRecyclerAdapter mAdapter; private FrameLayout videoRootViewFl; private MyVideoView videoView; private FrameLayout fullScreen; private View lastView; private int videoPosition = -1; private List<VideoBean> videoBeanList = new ArrayList<>(); private int[] imageIds = new int[]{R.drawable.hzw_a, R.drawable.hzw_b, R.drawable.hzw_d, R.drawable.hzw_e, R.drawable.hzw_f, R.drawable.hzw_h, R.drawable.hzw_i, R.drawable.hzw_j, R.drawable.hzw_k}; private static String VIDEO_PATH = "http://dn-chunyu.qbox.me/fwb/static/images/home/video/video_aboutCY_A.mp4"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); initEvent(); } private void initView() { toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar supportActionBar = getSupportActionBar(); if (supportActionBar != null) { supportActionBar.setDisplayHomeAsUpEnabled(true); supportActionBar.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp); supportActionBar.setTitle(""); } recyclerView = (RecyclerView) findViewById(R.id.recyclerView); videoRootViewFl = (FrameLayout) findViewById(R.id.video_root_fl); fullScreen = (FrameLayout) findViewById(R.id.video_full_screen); mAdapter = new VideoRecyclerAdapter(videoBeanList); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(mAdapter); } private void showVideo(View view, final String videoPath) { View v; removeVideoView(); if (videoRootViewFl.getVisibility() == View.VISIBLE) { videoRootViewFl.removeAllViews(); videoRootViewFl.setVisibility(View.GONE); } if (videoView == null) { videoView = new MyVideoView(MainActivity.this); // videoView.setListener(new MyVideoView.IFullScreenListener() { // @Override // public void onClickFull(boolean isFull) { // if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { // fullScreen.setVisibility(View.VISIBLE); // removeVideoView(); // setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); // fullScreen.addView(videoView, new ViewGroup.LayoutParams(-1, -1)); // videoView.setVideoPath(VIDEO_PATH); // videoView.start(); // } else { // fullScreen.removeAllViews(); // fullScreen.setVisibility(View.GONE); // setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // if (lastView instanceof ViewGroup) { // ((ViewGroup) lastView).addView(videoView); // } // videoView.setVideoPath(VIDEO_PATH); // videoView.start(); // } // // } // }) } videoView.stop(); v = view.findViewById(R.id.item_imageview); if (v != null) v.setVisibility(View.INVISIBLE); v = view.findViewById(R.id.item_image_play); if (v != null) v.setVisibility(View.INVISIBLE); v = view.findViewById(R.id.item_video_root_fl); if (v != null) { v.setVisibility(View.VISIBLE); FrameLayout fl = (FrameLayout) v; fl.removeAllViews(); fl.addView(videoView, new ViewGroup.LayoutParams(-1, -1)); VIDEO_PATH = videoPath; videoView.setVideoPath(videoPath); videoView.start(); } lastView = view; } private void removeVideoView() { View v; if (lastView != null) { v = lastView.findViewById(R.id.item_imageview); if (v != null) v.setVisibility(View.VISIBLE); v = lastView.findViewById(R.id.item_image_play); if (v != null) v.setVisibility(View.VISIBLE); v = lastView.findViewById(R.id.item_video_root_fl); if (v != null) { FrameLayout ll = (FrameLayout) v; ll.removeAllViews(); v.setVisibility(View.GONE); } } } private void initData() { VideoBean videoBean; for (int i = 0; i < 100; i++) { videoBean = new VideoBean(imageIds[i % imageIds.length], VIDEO_PATH); videoBeanList.add(videoBean); } } private void initEvent() { mAdapter.setListener(new VideoRecyclerAdapter.OnClickPlayListener() { @Override public void onPlayClick(View view, String videoPath) { showVideo(view, videoPath); } }); recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() { @Override public void onChildViewAttachedToWindow(View view) { if (videoPosition == -1 || videoRootViewFl.getVisibility() != View.VISIBLE) { return; } if (videoPosition == recyclerView.getChildAdapterPosition(view)) { videoPosition = -1; showVideo(view, VIDEO_PATH); } } @Override public void onChildViewDetachedFromWindow(View view) { if (videoView == null || videoRootViewFl.getVisibility() == View.VISIBLE) return; View v = view.findViewById(R.id.item_video_root_fl); if (v != null) { FrameLayout fl = (FrameLayout) v; videoPosition = recyclerView.getChildAdapterPosition(view); if (fl.getChildCount() > 0) { fl.removeAllViews(); int position = 0; if (videoView.isPlaying()) { position = videoView.getPosition(); videoView.stop(); } videoRootViewFl.setVisibility(View.VISIBLE); videoRootViewFl.removeAllViews(); lastView = videoRootViewFl; videoRootViewFl.addView(videoView, new ViewGroup.LayoutParams(-1, -1)); videoView.setVideoPath(VIDEO_PATH); videoView.start(); videoView.seekTo(position); // if (videoView.isPause()) { // videoView.resume(); // } } fl.setVisibility(View.GONE); } v = view.findViewById(R.id.item_imageview); if (v != null) { if (v.getVisibility() != View.VISIBLE) { v.setVisibility(View.VISIBLE); } } v = view.findViewById(R.id.item_image_play); if (v != null) { if (v.getVisibility() != View.VISIBLE) { v.setVisibility(View.VISIBLE); } } } }); } @Override protected void onDestroy() { if (videoView != null) { videoView.stop(); } super.onDestroy(); } }
主方法布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/colorPrimary" app:layout_scrollFlags="scroll|enterAlways|snap" /> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> </android.support.design.widget.CoordinatorLayout> <!-- 右下角的FragmeLayout --> <FrameLayout android:id="@+id/video_root_fl" android:layout_width="150dp" android:layout_height="150dp" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:background="#000" android:visibility="gone" /> <!--全屏播放的FragmeLayout--> <FrameLayout android:id="@+id/video_full_screen" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" /> </RelativeLayout>
VideoRecyclerAdapter
public class VideoRecyclerAdapter extends RecyclerView.Adapter<VideoRecyclerAdapter.ViewHolder> { private List<VideoBean> mList; private OnClickPlayListener listener; public void setListener(OnClickPlayListener listener) { this.listener = listener; } public VideoRecyclerAdapter(List<VideoBean> list) { this.mList = list; } public void addVideoBean(VideoBean videoBean) { if (videoBean == null) return; if (mList == null) { mList = new ArrayList<>(); } mList.add(videoBean); notifyDataSetChanged(); } public void addAllVideoBean(List<VideoBean> list) { if (list == null) return; if (mList == null) { mList = new ArrayList<>(); } mList.clear(); mList.addAll(list); notifyDataSetChanged(); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_video, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(final ViewHolder holder, int position) { final VideoBean videoBean = mList.get(position); Glide.with(holder.itemView.getContext()).load(videoBean.mImageId).crossFade().into(holder.mImageView); holder.mImageViewPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onPlayClick(holder.mCardView, videoBean.mVideoPath); } } }); } @Override public int getItemCount() { if (mList == null) return 0; return mList.size(); } public static class ViewHolder extends RecyclerView.ViewHolder { CardView mCardView; FrameLayout mVideoRootFl; ImageView mImageView; ImageView mImageViewPlay; public ViewHolder(View itemView) { super(itemView); mCardView = (CardView) itemView.findViewById(R.id.item_cardview); mVideoRootFl = (FrameLayout) itemView.findViewById(R.id.item_video_root_fl); mImageView = (ImageView) itemView.findViewById(R.id.item_imageview); mImageViewPlay = (ImageView) itemView.findViewById(R.id.item_image_play); } } public interface OnClickPlayListener { void onPlayClick(View view, String videoPath); } }
item_video
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/item_cardview" android:layout_width="match_parent" android:layout_height="200dp" android:layout_margin="10dp" android:background="#fff" android:elevation="8dp" android:padding="5dp" app:cardBackgroundColor="#fff" app:cardCornerRadius="5dp"> <!--列表播放使用的FrameLayout--> <FrameLayout android:id="@+id/item_video_root_fl" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" /> <ImageView android:id="@+id/item_imageview" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> <ImageView android:id="@+id/item_image_play" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/ic_play_circle_outline_white_48dp" /> </android.support.v7.widget.CardView>
VideoBean
public class VideoBean { public int mImageId; public String mVideoPath; public VideoBean(int imageId, String videoPath) { this.mImageId = imageId; this.mVideoPath = videoPath; } }
MyVideoView
public class MyVideoView extends RelativeLayout { private View rootLayout; private VideoView videoView; private View mClickView; private ImageView playOrPauseCenterIv; private LinearLayout playControlLl; private ImageView playOrPauseIv; private SeekBar seekBar; private ImageView fullIv; private static final int SHOW_CONTROL = 0x0001; private static final int HIDE_CONTROL = 0x0002; private static final int UPDATE_POSITION = 0x0003; private boolean isTrackingTouch = false; private boolean isShowControl = false; private boolean isPause = false; private boolean isFull = false; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case SHOW_CONTROL: showControLl(); break; case HIDE_CONTROL: hideControLl(); break; case UPDATE_POSITION: updatePosition(); break; default: break; } } }; private IFullScreenListener listener; public void setListener(IFullScreenListener listener) { this.listener = listener; } public MyVideoView(Context context) { super(context); init(); } public MyVideoView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MyVideoView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public MyVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { rootLayout = LayoutInflater.from(getContext()).inflate(R.layout.video_view, this, true); mClickView = findViewById(R.id.my_video_click); videoView = (VideoView) findViewById(R.id.my_videoview); playOrPauseCenterIv = (ImageView) findViewById(R.id.my_video_center_playpause_iv); playControlLl = (LinearLayout) findViewById(R.id.my_video_play_control_ll); playOrPauseIv = (ImageView) findViewById(R.id.my_video_play_pause_iv); seekBar = (SeekBar) findViewById(R.id.my_video_seekbar); fullIv = (ImageView) findViewById(R.id.my_video_full_iv); initSetting(); initEvent(); } private void initSetting() { playOrPauseCenterIv.setImageResource(R.drawable.ic_play_circle_outline_white_48dp); showControLl(); playOrPauseIv.setImageResource(R.drawable.ic_play_circle_outline_white_24dp); seekBar.setProgress(0); fullIv.setImageResource(R.drawable.ic_fullscreen_white_24dp); seekBar.setEnabled(false); } private void initEvent() { videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { stop(); } }); videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { //TODO return false; } }); videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { playOrPauseIv.setImageResource(R.drawable.ic_pause_circle_outline_white_24dp); playOrPauseCenterIv.setImageResource(R.drawable.ic_pause_circle_outline_white_48dp); seekBar.setMax(mp.getDuration()); seekBar.setProgress(mp.getCurrentPosition()); seekBar.setEnabled(true); handler.sendEmptyMessage(UPDATE_POSITION); } }); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { isTrackingTouch = true; handler.removeMessages(HIDE_CONTROL); } @Override public void onStopTrackingTouch(SeekBar seekBar) { isTrackingTouch = false; handler.sendEmptyMessageDelayed(HIDE_CONTROL, 3000); int position = seekBar.getProgress(); if (videoView.isPlaying()) { videoView.seekTo(position); } } }); mClickView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (isShowControl) { hideControLl(); } else { showControLl(); } } }); playOrPauseIv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (videoView.isPlaying()) { pause(); } else { resume(); } } }); playOrPauseCenterIv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (videoView.isPlaying()) { pause(); } else { resume(); } } }); fullIv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { isFull = !isFull; setFullScreen(isFull); if (listener != null) { listener.onClickFull(isFull); } } }); } /** * 更新进度 */ private void updatePosition() { handler.removeMessages(UPDATE_POSITION); if (videoView.isPlaying()) { int currentPosition = videoView.getCurrentPosition(); if (!isTrackingTouch) { seekBar.setProgress(currentPosition); } handler.sendEmptyMessageDelayed(UPDATE_POSITION, 500); } } /** * 隐藏控制条 */ public void hideControLl() { isShowControl = false; handler.removeMessages(HIDE_CONTROL); playOrPauseCenterIv.setVisibility(View.GONE); playControlLl.clearAnimation(); playControlLl.animate().translationY(playControlLl.getHeight()).setDuration(500).start(); } /** * 显示控制条 */ public void showControLl() { isShowControl = true; handler.sendEmptyMessageDelayed(HIDE_CONTROL, 3000); playOrPauseCenterIv.setVisibility(View.VISIBLE); playControlLl.clearAnimation(); playControlLl.animate().translationY(0).setDuration(500).start(); } /** * 设置播放地址 * @param path */ public void setVideoPath(String path) { videoView.setVideoPath(path); } /** * 开始播放 */ public void start() { isPause = false; videoView.start(); showControLl(); } /** * 是否正在播放 * @return */ public boolean isPlaying() { return videoView.isPlaying(); } /** * 暂停 */ public void pause() { isPause = true; handler.removeMessages(UPDATE_POSITION); videoView.pause(); playOrPauseCenterIv.setImageResource(R.drawable.ic_play_circle_outline_white_48dp); playOrPauseIv.setImageResource(R.drawable.ic_play_circle_outline_white_24dp); } /** * 是否处于暂停状态 * @return */ public boolean isPause() { return isPause; } /** * 继续 */ public void resume() { isPause = false; handler.sendEmptyMessageDelayed(UPDATE_POSITION, 500); videoView.start(); playOrPauseCenterIv.setImageResource(R.drawable.ic_pause_circle_outline_white_48dp); playOrPauseIv.setImageResource(R.drawable.ic_pause_circle_outline_white_24dp); } public int getPosition() { return videoView.getCurrentPosition(); } public void seekTo(int position) { videoView.seekTo(position); } /** * 停止 */ public void stop() { initSetting(); handler.removeCallbacksAndMessages(null); videoView.stopPlayback(); } private void setFullScreen(boolean fullScreen) { if (getContext() != null && getContext() instanceof AppCompatActivity) { ActionBar supportActionBar = ((AppCompatActivity) getContext()).getSupportActionBar(); if (supportActionBar != null) { if (fullScreen) { supportActionBar.hide(); } else { supportActionBar.show(); } } } } public interface IFullScreenListener { void onClickFull(boolean isFull); } }video_view
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/my_video_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#000"> <VideoView android:id="@+id/my_videoview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" /> <View android:id="@+id/my_video_click" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00ffffff" /> <ImageView android:id="@+id/my_video_center_playpause_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/ic_play_circle_outline_white_48dp" /> <LinearLayout android:id="@+id/my_video_play_control_ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="#88cccccc" android:gravity="center_vertical" android:orientation="horizontal" android:padding="3dp"> <ImageView android:id="@+id/my_video_play_pause_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_play_circle_outline_white_24dp" /> <SeekBar android:id="@+id/my_video_seekbar" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <ImageView android:id="@+id/my_video_full_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_fullscreen_white_24dp" /> </LinearLayout> </RelativeLayout>