RecyclerView实现图片和视频列表展示查看删除等功能
Posted 我的宝宝最可爱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RecyclerView实现图片和视频列表展示查看删除等功能相关的知识,希望对你有一定的参考价值。
RecyclerView具备强大的复用机制,高效的替代了最初的ListView和GridView等组件,很大程度上实现了代码解耦,并且提供了多种默认LayoutMananger用来处理多种布局,本篇介绍使用RecyclerView来实现图片和视频列表的展示、查看、删除等功能。
1. RecyclerView重要组成部分
- Adapter:数据和视图对接的桥梁,定义了数据以怎样的视图形式进行展示。
- ViewHolder:Item视图的持有者。
- LayoutManager:管理视图渲染、Item复用回收等功能,有LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager等。
- ItemDecoration:常用来实现列表的分割线等功能。
- ItemAnimator:用来实现Item的动画功能。
2. 引入库
implementation 'androidx.recyclerview:recyclerview:1.0.0'
3. 图片和视频列表功能实现
话不多说,先上效果图。图中可以看到,支持查看图片和视频两种列表,且列表中以日期进行分类显示。
- 布局activity_media.xml(页面主布局)
-
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="60dp" android:background="@color/colorBlack"> <LinearLayout android:id="@+id/llayMediaAction" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="90dp" android:layout_marginTop="15dp" android:orientation="horizontal" android:visibility="invisible"> <Button android:id="@+id/btnDelete" android:layout_width="60dp" android:layout_height="30dp" android:background="@drawable/xml_media_btn_action" android:text="删除" android:textColor="@color/colorWhite" /> <Button android:id="@+id/btnCancel" android:layout_width="60dp" android:layout_height="30dp" android:layout_marginLeft="10dp" android:background="@drawable/xml_media_btn_action" android:text="取消" android:textColor="@color/colorWhite" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="horizontal"> <TextView android:id="@+id/txtMediaMid" android:layout_width="wrap_content" android:layout_height="wrap_content" android:enabled="false" android:text="@string/media_photo" android:textColor="#00ff00" android:textSize="20sp" /> </LinearLayout> <ImageView android:id="@+id/imgBack" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginLeft="15dp" android:layout_marginTop="15dp" android:background="@drawable/xml_media_back" /> <TextView android:id="@+id/txtMediaRight" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginTop="15dp" android:layout_marginRight="15dp" android:text="@string/media_video" android:textColor="@color/colorWhite" android:textSize="20sp" /> </RelativeLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyListMedia" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="@dimen/common_split_10" android:layout_marginRight="@dimen/common_split_10" /> </LinearLayout> </LinearLayout>
- 布局media_item_title.xml(效果图中日期的布局)
-
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="40dp"> <TextView android:id="@+id/txtTitle" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="7dp" android:text="TextView" android:textColor="@color/colorFontCommon" android:textSize="20sp" /> </RelativeLayout>
- 布局media_item_photo.xml(效果图中图片的布局),布局中添加了复选框,用来做删除功能时使用。
-
<?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" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/imgPhoto" android:layout_width="200dp" android:layout_height="200dp" android:layout_marginStart="1dp" android:layout_marginLeft="1dp" android:layout_marginTop="1dp" android:layout_marginEnd="1dp" android:layout_marginRight="1dp" android:layout_marginBottom="1dp" app:srcCompat="@drawable/app_deploy" /> <CheckBox android:id="@+id/chkSelect" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:visibility="invisible" /> </RelativeLayout>
- 布局media_item_video.xml(视频的布局)
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imgVideo"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginStart="1dp"
android:layout_marginLeft="1dp"
android:layout_marginTop="1dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="1dp"
android:layout_marginBottom="1dp"
app:srcCompat="@drawable/app_deploy" />
<ImageButton
android:id="@+id/btnPlay"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/btn_play" />
<CheckBox
android:id="@+id/chkSelect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:visibility="invisible" />
</RelativeLayout>
-
MediaActivity.java
由于列表中小图查看不方便,此部分中还使用Mango实现大图的滑动查看。
public class MediaActivity extends FragmentActivity
private MediaActivity _this;
@BindView(R.id.imgBack)
ImageView imgBack;
@BindView(R.id.txtMediaMid)
TextView txtMediaMid;
@BindView(R.id.txtMediaRight)
TextView txtMediaRight;
@BindView(R.id.btnDelete)
Button btnDelete;
@BindView(R.id.btnCancel)
Button btnCancel;
@BindView(R.id.llayMediaAction)
LinearLayout llayMediaAction;
@BindView(R.id.recyListMedia)
RecyclerView recyListMedia;
private MediaListAdapter mediaListAdapter;
//实际展示的图片列表,包括日期和图片两种数据类型
private List<Object> listMediaBean = new ArrayList<Object>();
//图片预览的数据源,不包括日期类型,所以比listMediaBean size小
private List<MultiplexImage> listMangoImage = new ArrayList<MultiplexImage>();
private AtomicBoolean readyDelete = new AtomicBoolean(false);
private AtomicInteger count = new AtomicInteger(0);
private Unbinder unbinder;
private int currentShowMedia = MediaService.Photo;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media);
unbinder = ButterKnife.bind(this);
EventBus.getDefault().register(this);
_this = this;
this.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
txtMediaRight.setOnClickListener(new TxtMediaClickHandler());
imgBack.setOnClickListener(new ImgBackClickHandler());
MediaActionHandler mediaActionHandler = new MediaActionHandler();
btnDelete.setOnClickListener(mediaActionHandler);
btnCancel.setOnClickListener(mediaActionHandler);
initRecyclerView();
showMedias(MediaService.Photo);
@Override
protected void onResume()
super.onResume();
@Override
protected void onDestroy()
super.onDestroy();
unbinder.unbind();
EventBus.getDefault().unregister(this);
private void initRecyclerView()
mediaListAdapter = new MediaListAdapter(this, listMediaBean, readyDelete, count);
final GridLayoutManager gridLayoutManager = new GridLayoutManager(this, Constants.MediaList.SpanCount);
gridLayoutManager.setSpanSizeLookup(new MediaSpanSizeLookup());
recyListMedia.setLayoutManager(gridLayoutManager);
recyListMedia.setHasFixedSize(true);
recyListMedia.setNestedScrollingEnabled(false);
recyListMedia.setItemViewCacheSize(200);
recyListMedia.setRecycledViewPool(new RecyclerView.RecycledViewPool());
recyListMedia.setAdapter(mediaListAdapter);
private class MediaSpanSizeLookup extends GridLayoutManager.SpanSizeLookup
@Override
public int getSpanSize(int position)
Object obj = listMediaBean.get(position);
if(obj instanceof String)
return Constants.MediaList.SpanCount;
else if(obj instanceof MediaBean)
return 1;
return 0;
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMediaItemClick(MediaItemClickEvent event)
try
int position = event.getPosition();
int mediaType = event.getMediaType();
if(mediaType == MediaService.Photo)
//使用Mango实现大图活动查看
Mango.setImages(listMangoImage);
Mango.setPosition(position);
Mango.open(this);
else if(mediaType == MediaService.Video)
MediaBean videoBean = (MediaBean) listMediaBean.get(position);
String path = videoBean.getPath();
File file = new File(path);
if(!file.exists() || file.length() <= 0)
CustomToast.showToast(this, "视频文件不存在。");
return;
//调用系统播放视频
Uri uri = Uri.parse(path);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "video/mp4");
startActivity(intent);
else
/*Do nothing.*/
catch (Exception e)
LOGUtil.e(e.getMessage());
@Subscribe(threadMode = ThreadMode.MAIN)
public void onShowDelete(MessageShowDeleteEvent event)
if(event.isShow())
llayMediaAction.setVisibility(View.VISIBLE);
else
llayMediaAction.setVisibility(View.INVISIBLE);
private class MediaActionHandler implements View.OnClickListener
@Override
public void onClick(View v)
try
switch (v.getId())
case R.id.btnCancel :
for (Object obj : listMediaBean)
if(obj instanceof MediaBean)
MediaBean bean = (MediaBean) obj;
CheckBox chkSelect = bean.getChkSelect();
if(chkSelect != null)
chkSelect.setChecked(false);
chkSelect.setVisibility(View.INVISIBLE);
readyDelete.set(false);
llayMediaAction.setVisibility(View.INVISIBLE);
mediaListAdapter.notifyDataSetChanged();
break;
case R.id.btnDelete :
//删除照片或视频
boolean isSelect = false;
for (Object obj : listMediaBean)
if(obj instanceof MediaBean)
MediaBean bean = (MediaBean) obj;
CheckBox chkSelect = bean.getChkSelect();
if(chkSelect != null && chkSelect.isChecked())
isSelect = true;
String path = bean.getPath();
File file = new File(path);
file.delete();
if(!isSelect)
if(currentShowMedia == MediaService.Photo)
CustomToast.showToast(_this, "未选择图片");
else
CustomToast.showToast(_this, "未选择视频");
return;
clearPreMediaInfo();
if(currentShowMedia == MediaService.Photo)
showMedias(MediaService.Photo);
else
showMedias(MediaService.Video);
llayMediaAction.setVisibility(View.INVISIBLE);
break;
default:break;
catch (Exception e)
LOGUtil.e(e.getMessage());
private class ImgBackClickHandler implements View.OnClickListener
@Override
public void onClick(View view)
try
finish();
catch (Exception e)
LOGUtil.e(e.getMessage());
private class TxtMediaClickHandler implements View.OnClickListener
@Override
public void onClick(View view)
try
String txt = txtMediaRight.getText().toString();
if(txt.equals("图片"))
txtMediaMid.setText(R.string.media_photo);
txtMediaRight.setText(R.string.media_video);
showMedias(MediaService.Photo);
else if(txt.equals("视频"))
txtMediaMid.setText(R.string.media_video);
txtMediaRight.setText(R.string.media_photo);
showMedias(MediaService.Video);
else
/*Do nothing.*/
catch (Exception e)
LOGUtil.e(e.getMessage());
private void showMedias(int mediaType)
try
clearPreMediaInfo();
currentShowMedia = mediaType;
Map<String, List<MediaBean>> mapMedia = MediaService.findAllMedia(mediaType);
if(mapMedia == null || mapMedia.size() == 0)
mediaListAdapter.notifyDataSetChanged();
return;
Set<String> setMedia = mapMedia.keySet();
Set<String> sortSet = new TreeSet<String>(new Comparator<String>()
@Override
public int compare(String s1, String s2)
return s2.compareTo(s1);
);
sortSet.addAll(setMedia);
Iterator<String> it = sortSet.iterator();
while (it.hasNext())
String key = it.next();
listMediaBean.add(key);
List<MediaBean> listBean = mapMedia.get(key);
Collections.sort(listBean, new Comparator<MediaBean>()
@Override
public int compare(MediaBean o1, MediaBean o2)
return o1.getPath().compareTo(o2.getPath());
);
int count = listBean.size();
for (int i = 0; i < count; i++)
MediaBean bean = listBean.get(count - 1 - i);
listMediaBean.add(bean);
if(mediaType == MediaService.Photo)
listMangoImage.add(new MultiplexImage(bean.getPath(), bean.getPath(), MultiplexImage.ImageType.NORMAL));
mediaListAdapter.notifyDataSetChanged();
catch (Exception e)
LOGUtil.e(e.getMessage());
private void clearPreMediaInfo()
listMediaBean.clear();
listMangoImage.clear();
count.set(0);
readyDelete.set(false);
MediaListAdapter.java
public class MediaListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private static final int TypeTitle = 1;
private static final int TypeImage = 2;
private static final int TypeVideo = 3;
private Context context;
private List<Object> listMediaBean;
private SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
private SimpleDateFormat newFormat = new SimpleDateFormat("yyyy年MM月dd日");
private AtomicBoolean readyDelete;
private AtomicInteger count;
public MediaListAdapter(Context context, List<Object> listMediaBean, AtomicBoolean readyDelete, AtomicInteger count)
this.context = context;
this.listMediaBean = listMediaBean;
this.readyDelete = readyDelete;
this.count = count;
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
//此处根据数据的类型(日期、图片、还是视频),生成对应的视图
int width = parent.getWidth();
View view;
if(viewType == TypeTitle)
view = LayoutInflater.from(context).inflate(R.layout.media_item_title, parent, false);
ImageTitleViewHolder imageTitleViewHolder = new ImageTitleViewHolder(view);
return imageTitleViewHolder;
else if(viewType == TypeImage)
view = LayoutInflater.from(context).inflate(R.layout.media_item_photo, parent, false);
ImageListViewHolder imageListViewHolder = new ImageListViewHolder(view, width);
return imageListViewHolder;
else if(viewType == TypeVideo)
view = LayoutInflater.from(context).inflate(R.layout.media_item_video, parent, false);
VideoListViewHolder videoListViewHolder = new VideoListViewHolder(view, width);
return videoListViewHolder;
return null;
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position)
try
if(holder instanceof ImageTitleViewHolder)
count.set(count.get() + 1);
ImageTitleViewHolder imageTitleViewHolder = (ImageTitleViewHolder)holder;
String title = (String) listMediaBean.get(position);
Date date = format.parse(title);
imageTitleViewHolder.txtTitle.setText(newFormat.format(date));
else
ImageView imgPhoto = null;
CheckBox chkSelect = null;
int parentWidth = 0;
MediaBean bean = (MediaBean)listMediaBean.get(position);
if(holder instanceof ImageListViewHolder)
ImageListViewHolder imageListViewHolder = (ImageListViewHolder)holder;
imgPhoto = imageListViewHolder.imgPhoto;
chkSelect = imageListViewHolder.chkSelect;
parentWidth = imageListViewHolder.parentWidth;
else if(holder instanceof VideoListViewHolder)
VideoListViewHolder videoListViewHolder = (VideoListViewHolder)holder;
imgPhoto = videoListViewHolder.imgVideo;
chkSelect = videoListViewHolder.chkSelect;
parentWidth = videoListViewHolder.parentWidth;
bean.setChkSelect(chkSelect);
//此处需注意,由于图片或视频量可能很大,因此使用Glide库进行图片和视频的处理。
Glide.with(context).load(bean.getPath()).thumbnail(0.1F).into(imgPhoto);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) imgPhoto.getLayoutParams();
int width = parentWidth / Constants.MediaList.SpanCount;
int height = width * 1200 / 1920;
params.width = width;
params.height = height;
imgPhoto.setLayoutParams(params);
ItemClickHandler clickHandler = new ItemClickHandler(count.get(), position);
ItemLongClickHandler longClickHandler = new ItemLongClickHandler(count.get(), position);
if(holder instanceof VideoListViewHolder)
VideoListViewHolder videoListViewHolder = (VideoListViewHolder)holder;
params = (RelativeLayout.LayoutParams) videoListViewHolder.btnPlay.getLayoutParams();
int wh = DensityUtil.dip2px(40);
params.leftMargin = (width - wh) / 2;
params.topMargin = (height - wh) / 2;
videoListViewHolder.btnPlay.setLayoutParams(params);
videoListViewHolder.btnPlay.setOnClickListener(clickHandler);
videoListViewHolder.btnPlay.setOnLongClickListener(longClickHandler);
imgPhoto.setOnClickListener(clickHandler);
imgPhoto.setOnLongClickListener(longClickHandler);
chkSelect.setChecked(false);
if(readyDelete.get())
chkSelect.setVisibility(View.VISIBLE);
else
chkSelect.setVisibility(View.INVISIBLE);
catch (Exception e)
LOGUtil.e(e.getMessage());
@Override
public int getItemViewType(int position)
Object obj = listMediaBean.get(position);
if(obj instanceof String)
return TypeTitle;
else
if(obj instanceof MediaBean)
MediaBean bean = (MediaBean)obj;
int type = bean.getType();
if(type == MediaService.Photo)
return TypeImage;
else if(type == MediaService.Video)
return TypeVideo;
return super.getItemViewType(position);
@Override
public int getItemCount()
return listMediaBean.size();
private class ItemLongClickHandler implements View.OnLongClickListener
private int titleCount;
private int position;
public ItemLongClickHandler(int titleCount, int position)
this.titleCount = titleCount;
this.position = position;
@Override
public boolean onLongClick(View v)
try
if(readyDelete.get())
MediaBean bean = (MediaBean) listMediaBean.get(position);
bean.getChkSelect().setChecked(!bean.getChkSelect().isChecked());
return true;
for (Object obj: listMediaBean)
if(obj instanceof MediaBean)
MediaBean bean = (MediaBean) obj;
CheckBox chkSelect = bean.getChkSelect();
if(chkSelect == null)
break;
chkSelect.setVisibility(View.VISIBLE);
MediaBean bean = (MediaBean) listMediaBean.get(position);
bean.getChkSelect().setChecked(true);
readyDelete.set(true);
EventBus.getDefault().post(new MessageShowDeleteEvent(true));
catch (Exception e)
LOGUtil.e(e.getMessage());
return true;
private class ItemClickHandler implements View.OnClickListener
private int titleCount;
private int position;
public ItemClickHandler(int titleCount, int position)
this.titleCount = titleCount;
this.position = position;
@Override
public void onClick(View view)
try
if(readyDelete.get())
MediaBean bean = (MediaBean) listMediaBean.get(position);
bean.getChkSelect().setChecked(!bean.getChkSelect().isChecked());
return;
int tempPos = 0;
int mediaType = 0;
switch (view.getId())
case R.id.imgPhoto :
tempPos = position - titleCount;
mediaType = MediaService.Photo;
break;
case R.id.imgVideo :
tempPos = position;
mediaType = MediaService.Video;
break;
case R.id.btnPlay :
tempPos = position;
mediaType = MediaService.Video;
default:break;
EventBus.getDefault().post(new MediaItemClickEvent(tempPos, mediaType));
catch (Exception e)
LOGUtil.e(e.getMessage());
public class ImageTitleViewHolder extends RecyclerView.ViewHolder
private View view;
private TextView txtTitle;
public ImageTitleViewHolder(View view)
super(view);
this.view = view;
txtTitle = (TextView) view.findViewById(R.id.txtTitle);
public class ImageListViewHolder extends RecyclerView.ViewHolder
private View view;
private ImageView imgPhoto;
private CheckBox chkSelect;
private int parentWidth;
public ImageListViewHolder(View view, int parentWidth)
super(view);
this.view = view;
this.parentWidth = parentWidth;
imgPhoto = (ImageView) view.findViewById(R.id.imgPhoto);
chkSelect = (CheckBox) view.findViewById(R.id.chkSelect);
public class VideoListViewHolder extends RecyclerView.ViewHolder
private View view;
private ImageView imgVideo;
private ImageButton btnPlay;
private CheckBox chkSelect;
private int parentWidth;
public VideoListViewHolder(View view, int parentWidth)
super(view);
this.view = view;
this.parentWidth = parentWidth;
imgVideo = (ImageView) view.findViewById(R.id.imgVideo);
btnPlay = (ImageButton) view.findViewById(R.id.btnPlay);
chkSelect = (CheckBox) view.findViewById(R.id.chkSelect);
-
MediaBean.java
MediaBean用来存储列表中的数据,界面更新时,根据type字段判断数据是图片、还是视频,从而根据数据生成对应的视图。
public class MediaBean
private String parentDir;
private String path;
private int type;//图片或视频
private CheckBox chkSelect;//用于删除时标定是不是选中
public CheckBox getChkSelect()
return chkSelect;
public void setChkSelect(CheckBox chkSelect)
this.chkSelect = chkSelect;
public String getParentDir()
return parentDir;
public void setParentDir(String parentDir)
this.parentDir = parentDir;
public String getPath()
return path;
public void setPath(String path)
this.path = path;
public int getType()
return type;
public void setType(int type)
this.type = type;
(更新版)Android VideoPlayer 在滚动列表实现item视频播放(ListView控件和RecyclerView)
最新提取了 jar包 以提供给Eclipse 同仁使用 http://download.csdn.net/detail/wooder111/9649652
或者
https://github.com/hongyang51/VideoPlayerManager
2016.10.10 16时 晴 at BJ
由于写这篇文章时挂着梯子 ,回来发现没有图片 对不起各位了….. 现在我改好了!
2016.5.27 15时 阴 at BJ
转载请标明出处:粪乧 http://blog.csdn.net/wooder111/article/details/51513582
原文翻译: 点击跳转
在这篇文章中,我将介绍如何实现列表中的视频播放。在流行的应用,如Facebook,Instagram的或Magisto的工作原理相同:
Facebook的:
Magisto的:
Instagram的:
这篇文章是基于开源项目:VideoPlayerManager。
所有代码和工作示例是存在的。在这篇文章中的很多东西会被跳过,因此,如果有人真的需要理解它是如何工作最好是下载源代码和阅读在你的IDE的源代码的文章。但即使没有代码这篇文章将有利于与我们正在处理此理解。
两个问题
为了实现所需要的,我们必须解决两个问题:
我们要管理的视频播放。在Android中,我们有一个类MediaPlayer.class与SurfaceView协同工作,并且可以播放视频。但它也有很多缺点。我们不能在列表中使用通常的VideoView。VideoView延伸SurfaceView和SurfaceView不具有的UI同步缓冲器。所有这一切将导致我们到视频正在播放尝试,当你滚动它赶上名单的情况。同步缓存存在于TextureView但没有VideoView是基于TextureView在Android的SDK版本15. 因此,我们需要扩展TextureView并与Android的MediaPlayer工作的看法。另外,几乎所有的方法,从(准备,启动,停止等…) MediaPlayer的基本上都是调用与硬件配合本地方法。硬件可以是棘手的,如果将做任何工作时间超过16毫秒(它肯定会),那么我们将看到一个滞后的列表。这就是为什么需要从后台线程调用它们。
我们还需要知道哪些鉴于滚动名单上的当前活动,可以切换回放时,用户滚动。所以基本上我们必须跟踪滚动,并确定最引人注目的观点,如果它的变化。
管理视频播放
在这里,我们的目标是提供以下功能:
假设电影播放上。用户滚动列表中的列表和新产品变得比该视频播放所述一个更为明显。所以,现在我们必须停止现有的视频播放,并开始新的。
其主要功能是:停止播放以前,并开始播放新老一停之后。
这里是它如何工作的视频采样:当您按视频缩略图 - 当前视频播放停止,另一个开始。
VideoPlayerView
我们需要实现的第一件事情是一个基于TextureView的VideoView。我们不能在滚动列表中使用VideoView。因为视频渲染会搞砸了,如果用户在播放过程中滚动我们的名单。
我分这个任务分为几个部分:
创建一个ScalableTextureView。它是TextureView的后代,并且知道如何调整表面纹理(在重放运行该表面纹理),并提供了类似的ImageView scaleType几个选项。
public enum ScaleType CENTER_CROP, TOP, BOTTOM, FILL
创建VideoPlayerView。这是ScalableTextureView的后裔,它包含了所有与MediaPlayer.class的功能。这个自定义视图封装MediaPlayer.class,并提供了非常相似的VideoView的API。它具有所有被直接调用MediaPlayer的方法:的setDataSource,准备,启动,停止,暂停,恢复,释放。
视频播放器管理器和消息处理线程
视频播放经理与负责调用的MediaPlayer的方法MessagesHandlerThread一起工作。我们需要调用的方法类似制备(),启动()等在一个单独的线程,因为它们被直接连接到设备的硬件。并有情况下,当我们叫MediaPlayer.reset()在UI线程,但出现了一些问题的球员,这种方法是阻塞UI线程近4分钟!这就是为什么我们没有使用异步MediaPlayer.prepareAsync,我们可以使用同步MediaPlayer.prepare。我们是在一个单独的线程同步无所不为。
与启动新的播放流。下面是几个步骤做的MediaPlayer:
- 以前停止播放。它是通过调用
MediaPlayer.stop()
方法来实现。 - 通过调用
MediaPlayer.reset()
方法重置的MediaPlayer。我们需要做的是因为在滚动列表视图可能会被重用,我们希望有发布的所有资源。 - 通过调用
MediaPlayer.release()
方法释放的MediaPlayer。 - 清除的MediaPlayer的实例。当这种观点新的播放应该开始新的MediaPlayer实例将被创建。
- 新的最明显的视图创建实例上的MediaPlayer。
- 通过调用
MediaPlayer.setDataSource(字符串URL)
设置新的MediaPlayer的数据源。 - 呼叫
MediaPlayer.prepare()
。没有必要使用异步MediaPlayer.prepareAsync()
。 - 呼叫
MediaPlayer.start()
- 等待实际回放启动。
所有这些行动被包装成在一个单独的线程处理的消息,例如这是停止的消息。它调用VideoPlayerView.stop()
,最终调用MediaPlayer.stop()
。我们需要自定义消息,因为我们可以设置当前状态。我们知道它是停止或已停止否则后果不堪设想。它可以帮助我们来控制哪些消息是现在进行时,我们可以做些什么,如果我们需要,例如,开始新的播放内容。
/**
* This PlayerMessage calls @link MediaPlayer#stop() on the instance that is used inside @link VideoPlayerView
*/
public class Stop extends PlayerMessage
public Stop(VideoPlayerView videoView, VideoPlayerManagerCallback callback)
super(videoView, callback);
@Override
protected void performAction(VideoPlayerView currentPlayer)
currentPlayer.stop();
@Override
protected PlayerMessageState stateBefore()
return PlayerMessageState.STOPPING;
@Override
protected PlayerMessageState stateAfter()
return PlayerMessageState.STOPPED;
如果我们需要启动新的回放,我们只是呼吁VideoPlayerManager的方法。并增加了以下一组信息到MessagesHandlerThread的:
// pause the queue processing and check current state
// if current state is "started" then stop old playback
mPlayerHandler.addMessage(new Stop(mCurrentPlayer, this));
mPlayerHandler.addMessage(new Reset(mCurrentPlayer, this));
mPlayerHandler.addMessage(new Release(mCurrentPlayer, this));
mPlayerHandler.addMessage(new ClearPlayerInstance(mCurrentPlayer, this));
// set new video player view
mPlayerHandler.addMessage(new SetNewViewForPlayback(newVideoPlayerView, this));
// start new playback
mPlayerHandler.addMessages(Arrays.asList(
new CreateNewPlayerInstance(videoPlayerView, this),
new SetAssetsDataSourceMessage(videoPlayerView, assetFileDescriptor, this), // I use local file for demo
new Prepare(videoPlayerView, this),
new Start(videoPlayerView, this)
));
// resume queue processing
这些消息同步运行,这就是为什么我们可以在任何时候暂停队列处理和发布新的消息,例如:
目前的电影是在准备状态(MedaiPlayer.prepare()
被调用,并MediaPlayer.start()
在队列中等待)和用户滚动列表,所以我们需要在一个新的视图开始播放。在这种情况下我们:
- 暂停队列处理
- 删除所有未决信息
- 邮报“停止”,“复位”,“释放”,“清除Player实例”队列。当我们从“准备”回报,他们将运行权
- 邮报“创建新的媒体播放器实例”,“设置当前媒体播放器”(这个变化在其上执行了邮件的MediaPlayer对象),“设置数据源”,“准备”,“开始”。而这一消息将开始新视图播放。
好了,我们必须运行在我们需要的方式播放公用事业:停止播放以前,只是再开始下一个。
下面是该库的依赖关系的gradle:
dependencies
compile 'com.github.danylovolokh:video-player-manager:0.2.0'
标识列表中的最明显的图。清单能见度utils的
第一个问题是管理视频播放。第二个问题是跟踪哪些观点是最明显和回放切换到这一观点。
有一个叫ListItemsVisibilityCalculator及其实施SingleListViewItemActiveCalculator做所有作业的实体。
即在适配器使用必须实现的ListItem接口,以便计算列表中的项目的知名度模型类:
/**
* A general interface for list items.
* This interface is used by @link ListItemsVisibilityCalculator
*
* @author danylo.volokh
*/
public interface ListItem
/**
* When this method is called, the implementation should provide a
* visibility percents in range 0 - 100 %
* @param view the view which visibility percent should be
* calculated.
* Note: visibility doesn't have to depend on the visibility of a
* full view.
* It might be calculated by calculating the visibility of any
* inner View
*
* @return percents of visibility
*/
int getVisibilityPercents(View view);
/**
* When view visibility become bigger than "current active" view
* visibility then the new view becomes active.
* This method is called
*/
void setActive(View newActiveView, int newActiveViewPosition);
/**
* There might be a case when not only new view becomes active,
* but also when no view is active.
* When view should stop being active this method is called
*/
void deactivate(View currentView, int position);
该ListItemsVisibilityCalculator跟踪滚动的方向,并计算在运行项目的可见性。该项目的知名度可能取决于列表中的单个项目内部的任何视图。这是由你来实现getVisibilityPercents()方法。
有样品演示应用该方法的默认实现:
/**
* This method calculates visibility percentage of currentView.
* This method works correctly when currentView is smaller then it's enclosure.
* @param currentView - view which visibility should be calculated
* @return currentView visibility percents
*/
@Override
public int getVisibilityPercents(View currentView)
int percents = 100;
currentView.getLocalVisibleRect(mCurrentViewRect);
int height = currentView.getHeight();
if(viewIsPartiallyHiddenTop())
// view is partially hidden behind the top edge
percents = (height - mCurrentViewRect.top) * 100 / height;
else if(viewIsPartiallyHiddenBottom(height))
percents = mCurrentViewRect.bottom * 100 / height;
return percents;
因此,每个视图需要知道如何计算其知名度百分比。SingleListViewItemActiveCalculator将投票从滚动时发生这样的实现不应该是非常沉重的每个视图此值。
当任何邻居项目的知名度超过了当前活动项目的知名度SETACTIVE方法将被调用。而当它是我们应该切换的再现。
此外,还有可以作为ListItemsVisibilityCalculator和ListView或RecyclerView之间的适配器的ItemsPositionGetter。这样ListItemsVisibilityCalculator不知道,如果是的ListView或RecyclerView。它只是它的工作。但它需要一个通过ItemsPositionGetter提供了一些信息:
/**
* This class is an API for @link ListItemsVisibilityCalculator
* Using this class is can access all the data from RecyclerView /
* ListView
*
* There is two different implementations for ListView and for
* RecyclerView.
* RecyclerView introduced LayoutManager that's why some of data moved
* there
*
* Created by danylo.volokh on 9/20/2015.
*/
public interface ItemsPositionGetter
View getChildAt(int position);
int indexOfChild(View view);
int getChildCount();
int getLastVisiblePosition();
int getFirstVisiblePosition();
有那种逻辑的模型是搞乱了一下从模型中分离业务逻辑的想法。但有一些修改它可能被分离。顺便说一句,它工作正常,现在连怎么回事。
下面是简单的电影,显示它是如何工作:
下面是一个utils的Gradle依赖关系:
dependencies
compile 'com.github.danylovolokh:list-visibility-utils:0.2.0'
视频播放器管理器和目录能见度utils的组合来实现滚动列表的视频播放。
现在我们有解决我们所需要的一切两个库。让我们将它们结合起来,以获得我们所需要的功能。
在这里,从使用RecyclerView片段的代码:
初始化ListItemsVisibilityCalculator,并传递一个参考到一个列表吧。
/** * Only the one (most visible) view should be active (and playing). * To calculate visibility of views we use @link SingleListViewItemActiveCalculator */ private final ListItemsVisibilityCalculator mVideoVisibilityCalculator = new SingleListViewItemActiveCalculator( new DefaultSingleItemCalculatorCallback(), mList);
DefaultSingleItemCalculatorCallback只是调用ListItem.setActive方法时,活动视图变化,但你可以自己覆盖它,做任何你需要:
/** * Methods of this callback will be called when new active item is found @link Callback#activateNewCurrentItem(ListItem, View, int) * or when there is no active item @link Callback#deactivateCurrentItem(ListItem, View, int) - this might happen when user scrolls really fast */ public interface Callback<T extends ListItem> void activateNewCurrentItem(T item, View view, int position); void deactivateCurrentItem(T item, View view, int position);
初始化VideoPlayerManager。
/** * Here we use @link SingleVideoPlayerManager, which means that only one video playback is possible. */ private final VideoPlayerManager<MetaData> mVideoPlayerManager = new SingleVideoPlayerManager(new PlayerItemChangeListener() @Override public void onPlayerItemChanged(MetaData metaData) );
设置上滚动监听器RecyclerView并通过滚动的事件列表中的知名度utils的。
@Override public void onScrollStateChanged(RecyclerView view, int scrollState) mScrollState = scrollState; if(scrollState == RecyclerView.SCROLL_STATE_IDLE && mList.isEmpty()) mVideoVisibilityCalculator.onScrollStateIdle( mItemsPositionGetter, mLayoutManager.findFirstVisibleItemPosition(), mLayoutManager.findLastVisibleItemPosition());
@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) if(!mList.isEmpty()) mVideoVisibilityCalculator.onScroll( mItemsPositionGetter, mLayoutManager.findFirstVisibleItemPosition(), mLayoutManager.findLastVisibleItemPosition() - mLayoutManager.findFirstVisibleItemPosition() + 1, mScrollState); );
创建ItemsPositionGetter。
ItemsPositionGetter mItemsPositionGetter = new RecyclerViewItemPositionGetter(mLayoutManager, mRecyclerView);
我们呼吁在onResume的方法来启动,只要我们打开屏幕计算最明显的项目。
@Override public void onResume() super.onResume(); if(!mList.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()); );
就是这样。我们有一组正在玩的滚动列表中的视频:
基本上,这是最重要的部分仅仅是解释。有一个在这里的示例应用程序有更多的代码:
https://github.com/danylovolokh/VideoPlayerManager
请看到源代码的更多细节。谢谢!
以上是关于RecyclerView实现图片和视频列表展示查看删除等功能的主要内容,如果未能解决你的问题,请参考以下文章
(更新版)Android VideoPlayer 在滚动列表实现item视频播放(ListView控件和RecyclerView)
在recyclerview中使用textureview播放视频