Android 手机影音 开发过程记录(三)

2024-03-11 14:10

本文主要是介绍Android 手机影音 开发过程记录(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前一篇已经将MainActivity编写好了,其中主页面的ViewPager控件嵌入的是两个fragment,分别是VideoListFragment 和 AudioListFragment。今天主要理一下视频这一模块,包括:

  1. 通过ContentProvider获取视频列表数据(使用AsyncQueryHandler异步获取)

  2. 自定义播放页面

  3. 播放页面逻辑处理(播放、暂停、切换、音量等)

效果图

这里写图片描述

这里写图片描述

结构图

这里写图片描述

获取视频列表数据

分析:

  • 观察视频列表,我们需要这些数据:视频名称(TITLE),视频时长(DURATION),视频大小(SIZE),当然每一个视频都有一个唯一的路径(DATA),这个也需要。
    那么就可以将这些视频共有的属性封装成一个JavaBean。

  • 这里用游标cursor去查视频数据,适配器就继承CursorAdapter,它是BaseAdapter的子类。在newView()方法里,加载每一项的布局文件;在bindView()方法中,如果view不为空,设置数据,否则会先调用newView()方法生成一个View。

public class VideoListAdapter extends CursorAdapter {public VideoListAdapter(Context context, Cursor c, boolean autoRequery) {super(context, c, autoRequery);}@Overridepublic View newView(Context context, Cursor cursor, ViewGroup parent) {return View.inflate(context, R.layout.item_video_list, null);}@Overridepublic void bindView(View view, Context context, Cursor cursor) {ViewHolder holder = getHolder(view);VideoItem videoItem = VideoItem.fromCursor(cursor);holder.tvName.setText(videoItem.getTitle());holder.tvDuration.setText(StringUtil.formatVideoDuration(videoItem.getDuration()));holder.tvSize.setText(Formatter.formatFileSize(context, videoItem.getSize()));}private ViewHolder getHolder(View view) {ViewHolder holder = (ViewHolder) view.getTag();if (holder == null) {holder = new ViewHolder(view);view.setTag(holder);}return holder;}private static class ViewHolder {private TextView tvName, tvDuration, tvSize;public ViewHolder(View view) {tvName = (TextView) view.findViewById(R.id.tv_name);tvDuration = (TextView) view.findViewById(R.id.tv_duration);tvSize = (TextView) view.findViewById(R.id.tv_size);}}
}

说明:bindView()方法中通过cursor获取数据的方法封装到VideoItem的JavaBean中了,主要是为代码整洁,具体代码如下:

    public static VideoItem fromCursor(Cursor cursor) {VideoItem videoItem = new VideoItem();videoItem.setDuration(cursor.getLong(cursor.getColumnIndex(Media.DURATION)));videoItem.setPath(cursor.getString(cursor.getColumnIndex(Media.DATA)));videoItem.setSize(cursor.getLong(cursor.getColumnIndex(Media.SIZE)));videoItem.setTitle(cursor.getString(cursor.getColumnIndex(Media.TITLE)));return videoItem;}
  • 视频列表的适配器写好了,如何获取数据呢?想一想,如果视频很多的话,能在主线程操作吗?显然不能,android给我们提供了这样一个类:AsyncQueryHandler,它是个抽象类,我们需要写一个类去继承它。(它给我们提供了一个查询完成的回调方法,我们可以在这个方法里更新listview)
    class SimpleQueryHandler extends AsyncQueryHandler{public SimpleQueryHandler(ContentResolver cr) {super(cr);}/*** token: 查询的标识*/@Overrideprotected void onQueryComplete(int token, Object cookie, Cursor cursor) {super.onQueryComplete(token, cookie, cursor);if(cookie!=null && cookie instanceof CursorAdapter){CursorAdapter adapter = (CursorAdapter) cookie;adapter.changeCursor(cursor);//相当于notifyDatasetChangeCursorUtil.printCursor(cursor);}}}
  • 获取数据的方法如下:(罗列出要查询的列后,通过我们自己写的queryHandler的startQuery()就行了,就这么简单)
    @Overrideprotected void initData() {adapter = new VideoListAdapter(getActivity(), null, false);lv.setAdapter(adapter);queryHandler = new SimpleQueryHandler(getActivity().getContentResolver());String[] projection = { Media._ID, Media.TITLE, Media.SIZE,Media.DURATION, Media.DATA };queryHandler.startQuery(0, adapter, Media.EXTERNAL_CONTENT_URI,projection, null, null, null);}

至此,就可实现上面视频列表的效果图的样子了。


自定义播放页面

接来下实现播放视频的Activity。

  • 视频为什么能播放?肯定是在点击listview的一个item传了数据过来了呀,是传当前视频的信息吗?进一步想想,播放视频的页面是可以切换上一个下一个视频的啊,所以传过来的不能只是一个视频信息,得是所有视频信息的一个集合。还得传一个当前视频的position来作为要播放的一个标记。
    @Overrideprotected void initListener() {lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {Cursor cursor = (Cursor) adapter.getItem(position);ArrayList<VideoItem> videoList = cursorToList(cursor);Bundle bundle = new Bundle();bundle.putInt("currentPosition", position);bundle.putSerializable("videoList", videoList);enterActivity(VitamioVideoPlayerActivity.class, bundle);}});}/*** 将cursor中的所有记录转为对象放入集合中* @param cursor* @return*/private ArrayList<VideoItem> cursorToList(Cursor cursor){cursor.moveToPosition(-1);ArrayList<VideoItem> list = new ArrayList<VideoItem>();while(cursor.moveToNext()){list.add(VideoItem.fromCursor(cursor));}return list;}
  • 数据是传过来了,别忙处理,先将VideoPlayerActivity的布局分析一下(开头的结构图已经列出该页面的布局了。这里再讲一下具体的布局方法)

    1. 首先,像这种层叠式布局用RelativeLayout或者FrameLayout都可以,这里用RelativeLayout,它更灵活些。
    2. 里面首先是个VideoView,宽高都是铺满屏幕,但是系统提供的这个视频控件只能支持部分格式的视频,我们知道视频有很多格式,如果你这个播放器不能支持绝大多数,那肯定没人会用,所以,这里使用一个开源的框架vitamio,支持所有格式视频。
    3. 其次,上下的布局文件可分别写在不同文件中,最后include进来。
    4. 为增加用户体验,可在增加一个正在加载的页面和一个播放卡顿时的缓冲页面。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/black"><io.vov.vitamio.widget.VideoView
        android:id="@+id/vv"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_centerHorizontal="true" /><include
        android:id="@+id/layout_top_control"layout="@layout/layout_top_control" /><LinearLayout
        android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"><include
            android:id="@+id/layout_bottom_control"layout="@layout/layout_bottom_control" /></LinearLayout><LinearLayout
        android:id="@+id/ll_loading"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/bg_player_loading_background"android:gravity="center"android:orientation="horizontal"><ProgressBar
            android:layout_width="20dp"android:layout_height="20dp" /><TextView
            android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:singleLine="true"android:text="正在加载中..."android:textColor="@color/white"android:textSize="16sp" /></LinearLayout><LinearLayout
        android:id="@+id/ll_buffering"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:visibility="gone"><ProgressBar
            android:layout_width="20dp"android:layout_height="20dp" /></LinearLayout></RelativeLayout>

头部控制面板的布局:

<?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="wrap_content"android:orientation="vertical"><LinearLayout
        android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/bg_player_status"android:gravity="center_vertical"android:orientation="horizontal"android:paddingLeft="5dp"android:paddingRight="5dp"><TextView
            android:id="@+id/tv_name"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="视频名称"android:textColor="@color/white"android:textSize="14sp" /><ImageView
            android:id="@+id/iv_battery"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@mipmap/ic_battery_0" /><TextView
            android:id="@+id/tv_system_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:text="系统时间"android:textColor="@color/white"android:textSize="14sp" /></LinearLayout><LinearLayout
        android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/bg_player_top_control"android:gravity="center_vertical"android:orientation="horizontal"><ImageView
            android:id="@+id/iv_voice"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/selector_btn_voice" /><SeekBar
            android:id="@+id/sb_volume"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginRight="25dp"android:maxHeight="6dp"android:minHeight="6dp"android:progressDrawable="@drawable/video_progress_drawable"android:thumb="@mipmap/progress_thumb" /></LinearLayout></LinearLayout>

底部控制面板布局:

<?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="wrap_content"android:orientation="vertical"><LinearLayout
        android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/bg_player_bottom_seekbar"android:gravity="center_vertical"android:orientation="horizontal"android:paddingLeft="5dp"android:paddingRight="5dp"><TextView
            android:id="@+id/tv_current_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="00:00"android:textColor="@color/white"android:textSize="14sp" /><SeekBar
            android:id="@+id/sb_Video"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:layout_marginRight="5dp"android:layout_weight="1"android:maxHeight="6dp"android:minHeight="6dp"android:progressDrawable="@drawable/video_progress_drawable"android:thumb="@mipmap/progress_thumb" /><TextView
            android:id="@+id/tv_total_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="总时间"android:textColor="@color/white"android:textSize="14sp" /></LinearLayout><LinearLayout
        android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/bg_player_bottom_control"android:gravity="center"android:orientation="horizontal"><RelativeLayout
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"><ImageView
                android:id="@+id/iv_exit"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/selector_btn_exit" /></RelativeLayout><RelativeLayout
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"><ImageView
                android:id="@+id/iv_pre"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/selector_btn_pre" /></RelativeLayout><RelativeLayout
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"><ImageView
                android:id="@+id/iv_play"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/selector_btn_pause" /></RelativeLayout><RelativeLayout
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"><ImageView
                android:id="@+id/iv_next"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/selector_btn_next" /></RelativeLayout><RelativeLayout
            android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"><ImageView
                android:id="@+id/iv_screen"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/selector_btn_fullscreen" /></RelativeLayout></LinearLayout></LinearLayout>

至此,视频播放器页面自定义的布局就弄好了。


至于播放页面逻辑,有很多很多,准备在下一篇博文中在去整理。先放一张MediaPlayer的类图:了解一下音视频在播放前中后各个方法的调用。
这里写图片描述

补充

  1. 引入库工程
    android studio在导入外部库工程的时候,网上有很多方法,我是这样做的:就以vitamio为例,将下载好的压缩解压,找到vitamio文件夹,然后整体复制到android studio的工作区间中,clean一下project,这个资源库会报错,应该是编译版本的问题,打开vitamio的build.gradle,修改里面的编译的sdk版本,就可以了,附张图吧
    这里写图片描述

  2. 引用.9图片
    在上面很长很长的布局文件中,如果仔细看的话,会发现在引用资源图片时,有的是@mipmap,有的是@drawable,是这样的:在android studio下引用的 .9 图片放在mipmap文件夹下面不能被引用,我的做法是,新建了一个drawable-xhdpi文件夹,将.9图片放进去,就能正常引用了。

  3. 自定义SeekBar样式:

        <SeekBarandroid:id="@+id/sb_volume"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginRight="25dp"android:maxHeight="6dp"android:minHeight="6dp"android:progressDrawable="@drawable/video_progress_drawable"android:thumb="@mipmap/progress_thumb" />

thumb就是进度条上的那个小圆点的图片

video_progress_drawable代码如下:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"><!--SeekBar的背景--><item
        android:id="@android:id/background"android:drawable="@drawable/progress_background"></item><!--SeekBar第二级进度的样式--><item android:id="@android:id/secondaryProgress"><clip><shape><corners android:radius="5dip" /><solid android:color="#666" /></shape></clip></item><!--SeekBar进度的样式--><item
        android:id="@android:id/progress"android:drawable="@mipmap/video_progress"></item></layer-list>

这篇关于Android 手机影音 开发过程记录(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/798016

相关文章

Python开发文字版随机事件游戏的项目实例

《Python开发文字版随机事件游戏的项目实例》随机事件游戏是一种通过生成不可预测的事件来增强游戏体验的类型,在这篇博文中,我们将使用Python开发一款文字版随机事件游戏,通过这个项目,读者不仅能够... 目录项目概述2.1 游戏概念2.2 游戏特色2.3 目标玩家群体技术选择与环境准备3.1 开发环境3

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Android实现两台手机屏幕共享和远程控制功能

《Android实现两台手机屏幕共享和远程控制功能》在远程协助、在线教学、技术支持等多种场景下,实时获得另一部移动设备的屏幕画面,并对其进行操作,具有极高的应用价值,本项目旨在实现两台Android手... 目录一、项目概述二、相关知识2.1 MediaProjection API2.2 Socket 网络

Android实现悬浮按钮功能

《Android实现悬浮按钮功能》在很多场景中,我们希望在应用或系统任意界面上都能看到一个小的“悬浮按钮”(FloatingButton),用来快速启动工具、展示未读信息或快捷操作,所以本文给大家介绍... 目录一、项目概述二、相关技术知识三、实现思路四、整合代码4.1 Java 代码(MainActivi

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt