温馨提示:

        功能比较多且文章也比较长,可在左边菜单栏打开目录按标题查找;

        或者按CTRL+F即网页按关键字查找,输入自己需要功能关键字就能跳转到目的地了。

先说明一下,本人大二,就读于一所普通的不能再普通的二本,Android是自学,这个项目自己前前后后花了比较久的时间,因为边学边做,也是磕磕绊绊的过来了,80%原创,20%是邵发的安卓入门视频(http://www.afanihao.cn/java.jsp)最后一个专题视频播放器带进来的,看视频纯手写代码,但是发哥的视频播放器项目只有简单的功能:利用VideoView打开本地视频,SeekBar滑动进度条,暂停/播放按钮就没了,自己还是感谢发哥把我带入安卓(之前从Java Web跳到Android)。

写这篇博客主要有几点原因,一是想把自己这段时间内容总结一下,二是跟大家互相讨论,看有什么可以优化。第一次写CSDN博客,有什么不足之处请谅解,毕节本人还是个大二的宝宝啊,是不是(嘿嘿嘿)?

项目原创!文章原创!所以转载请注明出处!

本人第一次写视频播放器项目,自己觉得有许多很多可以优化的地方,有什么不足的不够完美不够好的,欢迎大家举出来,一起讨论。

有个不知道算不算bug的bug,就是项目安卓到手机上,提示安装时显示的是我的创建项目的名字:Mydemo1101,但是安装完App名字是我设置好的App名字,估计有解决办法,目前只是学习测试用,不管了。

我用的是Android Studio 2.3.3 版本,写的博客教程代码只是截取代码,一些东西比较杂就没贴,太麻烦了,所以博客可以看到我的思路我的想法,如果想要完整项目可以在我的CSDN资源下载,我上传到CSDN了,项目下载地址贴上:

https://download.csdn.net/download/drogon1999/10393876

首先先说一下我的项目有什么功能:

1、发哥Android入门视频的 打开本地媒体器,SeekBar滑动进度条,暂停/播放按钮三个功能;

以下功能是自己完善添加的:

2、锁定按钮功能;

3、分享功能(两种机制 如果是本地视频则分享视频,如果是网络视频分享网络视频地址);

4、VideoView滑动监听(快进快退,音量加减、亮度加减);

5、全屏按钮和缩小按钮点击功能;

6、右上角显示当前时间,上面中间显示视频当前进度,左上角显示标题(本地视频显示标题,网络视频显示地址);

7、播放网络视频功能;

8、单击双击监听(双击暂停,单击显示所有状态)

9、按返回键,连续按两次才会退出,按一次提醒再按一次才会退出

10、视频播放器能够出现在打开方式上,即视频长按可以选择我的播放器进行播放

11、所有显示的东西三秒后自动隐藏

首先说一下布局文件,从发哥学到了横屏竖屏可以专门用两个xml文件分别对应竖屏和和横屏的界面,我就采用了两个xml文件。并且图标是从阿里巴巴矢量图标库下载,APP应用的图片是从百度文库下载的(好像部分图片有版权问题,但我只是学习用会不会被查水表???顶多赚点C币,应该不会吧(萌新瑟瑟发抖))。

先看预览图,我考虑了用户不清楚每个按钮的作用,在最开始显示了类似于说明的图片,点击任意地方都能隐藏,当然竖屏横屏显示的说明的图片是不一样的,但是功能是一样的,缺点是每次打开都会显示。。。(是不是可以把说明改成广告???嘿嘿嘿我真是个天才)

说明图片(横屏竖屏的说明图片只是里面内容位置不一样,就放一张横屏的了,但是我的项目好像设置了切换屏幕时将这个说明书关闭???大概可估计能你们只能看到竖屏的说明书,横屏你们就别想看了(这应该不重要,对,不重要)):

布局界面代码:

用了FrameLayout控件,个人看来好处是可以分层,优点是互不影响,缺点还是互不影响,可能对界面设计不精通,分层的控件不知道怎么相互联系。

一个水平Layout的标题栏,里面放了三个子控件(横屏左边视频标题,中间视频当前位置和总时间(类似于01:34/05:04),右边显示时间,),竖屏就两个子控件没有右边的显示当前时间。并且标题TextView设置了单行显示,8个字数限制,超过用省略号替代(在xml布局有注释);

一个VideoView空间播放视频;

一个水平Layout的网络视频栏,这个是输入网络视频地址和跳转;(默认属性是GONE隐藏的,只有点击网络视频按钮才会显示)

一个主工具栏,从左边开始全屏/缩小按钮,打开本地视频资源按钮,打开网络视频栏按钮,播放/暂停按钮,最后是进度条。

一个缓存加载的动态图,用于显示加载网络视频的缓存过程,不过Android Studio没有自带播放动态图的空间,用了第三方的控件(包名为android-gif_drawable-1.2.9.aar),如何加入第三方呢,简单说一下,将AAR包复制到项目的\app\libs文件夹下面,然后在build.gradle(Module:app)加入一些代码(如图):注意三个红框是否相同,修改guild文件记得点击右上角的Sync Now进行全局更新配置文件,这样就会Android Studio会自动加载libs文件夹下的android-gif_drawable-1.2.9.aar包,然后用法手动在Text手动添加第三方包,代码在下面xml布局文件里。

需要的权限:

先在mainifests/AndroidManifest.xml 写上需要的权限代码,并且更改主题为没有自带标题栏的主题,还有拦截VideoView视频源只能播放VIdeo/*:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="example.mydemo1101"><!-- 权限列表 --><uses-permission android:name="android.permission.CAMERA"/><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.VIBRATE"/><applicationandroid:allowBackup="true"android:icon="@drawable/im_icon"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme.NoActionBar"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter><intent-filter><action android:name="android.intent.action.VIEW"/><category android:name="android.intent.category.DEFAULT"/><data android:mimeType="video/*"/></intent-filter></activity></application></manifest>

而没有自带的标题栏需要在values/styles.xml文件添加代码:

<style name="AppTheme.NoActionBar"><item name="windowActionBar">false</item><item name="windowNoTitle">true</item></style>

横屏布局activity_main_landscape:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns: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:orientation="vertical"android:background="#000"tools:context="example.mydemo1101.MainActivity"><VideoViewandroid:id="@+id/id_videoview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center"android:layout_weight="1"/><ImageViewandroid:id="@+id/id_help"android:layout_width="match_parent"android:layout_height="match_parent"app:srcCompat="@drawable/im_help"/><LinearLayoutandroid:id="@+id/id_title_bar"android:layout_width="match_parent"android:layout_height="50dp"android:layout_gravity="top"android:orientation="horizontal"android:padding="8dp"><!-- android:lines="1"   // 单行显示android:maxEms="8"  //最多显示8个字android:ellipsize="end"  //剩余的用省略号代替android:hint="请输入视频地址" // 表示灰色的字体,不用删除点击直接输入会自动消失--><TextViewandroid:id="@+id/id_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="视频标题"android:gravity="left"android:lines="1"android:maxEms="8"android:ellipsize="end"android:textColor="#1296db"android:textSize="24sp"/><TextViewandroid:id="@+id/id_progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="2"android:text="00:00/00:00"android:gravity="center"android:textColor="#EDA24C"android:textSize="22sp"/><TextViewandroid:id="@+id/id_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="0.5"android:text="00:00:00"android:gravity="right"android:textColor="#1296db"android:textSize="24sp"/></LinearLayout><LinearLayoutandroid:id="@+id/id_internetvideo_bar"android:layout_width="match_parent"android:layout_height="50dp"android:layout_gravity="top"android:orientation="horizontal"android:visibility="gone"android:padding="8dp"android:weightSum="1"><EditTextandroid:id="@+id/id_internetUri"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="#FBD3B5"android:ems="10"android:inputType="textPersonName"android:hint="请输入视频地址"android:layout_weight="1"android:textColor="#00A1D6"android:textSize="24sp"/><ImageButtonandroid:id="@+id/id_internetSure"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="#DA5380"app:srcCompat="@drawable/ic_sure"/></LinearLayout><LinearLayoutandroid:id="@+id/id_control_bar"android:layout_width="match_parent"android:layout_height="50dp"android:layout_gravity="bottom"android:background="#0fff"android:orientation="horizontal"android:padding="4dp"android:weightSum="1"><ImageButtonandroid:id="@+id/id_fullscreen"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginRight="8dp"android:background="#0000"android:onClick="fullScreen"app:srcCompat="@drawable/ic_video_less_screen"/><ImageButtonandroid:id="@+id/id_selectFile"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginRight="8dp"android:background="#0000"android:onClick="openFile"app:srcCompat="@drawable/ic_media_open"/><ImageButtonandroid:id="@+id/id_internet"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginRight="8dp"android:background="#0000"app:srcCompat="@drawable/ic_internet"/><ImageButtonandroid:id="@+id/id_play_pause"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="#0000"app:srcCompat="@drawable/ic_play"/><SeekBarandroid:id="@+id/id_seekbar"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginLeft="4dp"/></LinearLayout><pl.droidsonroids.gif.GifImageViewandroid:id="@+id/id_loading_gif"android:layout_width="50dp"android:layout_height="50dp"android:layout_gravity="center"android:background="#0000"android:visibility="gone"android:src="@drawable/loading"/><TextViewandroid:id="@+id/id_textview"android:layout_width="192dp"android:layout_height="95dp"android:layout_gravity="center"android:background="#0000"android:gravity="center"android:textColor="#1296db"android:textSize="28sp"/><ImageButtonandroid:id="@+id/id_locked_unlocked"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="left|center"android:layout_marginLeft="16dp"android:background="#0000"app:srcCompat="@drawable/ic_unlocked"/><ImageButtonandroid:id="@+id/id_share"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="right|center"android:layout_marginRight="16dp"android:background="#0000"app:srcCompat="@drawable/ic_share"/></FrameLayout>

竖屏布局:

竖屏布局跟横屏布局有点差别,一是竖屏我没有隐藏手机自带的状态栏,所以右上角没有实时显示当前时间,而且竖屏显示的是全屏按钮:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns: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:orientation="vertical"android:background="#000"tools:context="example.mydemo1101.MainActivity"><VideoViewandroid:id="@+id/id_videoview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center"android:layout_weight="1"/><ImageViewandroid:id="@+id/id_help"android:layout_width="match_parent"android:layout_height="match_parent"app:srcCompat="@drawable/im_help1"/><LinearLayoutandroid:id="@+id/id_title_bar"android:layout_width="match_parent"android:layout_height="50dp"android:layout_gravity="top"android:orientation="horizontal"android:padding="8dp"><!-- android:lines="1"   // 单行显示android:maxEms="8"  //最多显示8个字android:ellipsize="end"  //剩余的用省略号代替android:hint="请输入视频地址" // 表示灰色的字体,不用删除点击直接输入会自动消失--><TextViewandroid:id="@+id/id_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="视频标题"android:lines="1"android:maxEms="6"android:ellipsize="end"android:gravity="left"android:textColor="#1296db"android:textSize="24sp"/><TextViewandroid:id="@+id/id_progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="2"android:text="00:00/00:00"android:gravity="center"android:textColor="#EDA24C"android:textSize="22sp"/></LinearLayout><LinearLayoutandroid:id="@+id/id_internetvideo_bar"android:layout_width="match_parent"android:layout_height="50dp"android:layout_gravity="top"android:orientation="horizontal"android:visibility="gone"android:padding="8dp"android:weightSum="1"><EditTextandroid:id="@+id/id_internetUri"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="#FBD3B5"android:ems="10"android:inputType="textPersonName"android:hint="请输入视频地址"android:layout_weight="1"android:textColor="#00A1D6"android:textSize="24sp"/><ImageButtonandroid:id="@+id/id_internetSure"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="#DA5380"app:srcCompat="@drawable/ic_sure"/></LinearLayout><LinearLayoutandroid:id="@+id/id_control_bar"android:layout_width="match_parent"android:layout_height="50dp"android:layout_gravity="bottom"android:background="#0fff"android:orientation="horizontal"android:padding="4dp"android:weightSum="1"><ImageButtonandroid:id="@+id/id_fullscreen"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginRight="8dp"android:background="#0000"android:onClick="fullScreen"app:srcCompat="@drawable/ic_video_full_screen"/><ImageButtonandroid:id="@+id/id_selectFile"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginRight="8dp"android:background="#0000"android:onClick="openFile"app:srcCompat="@drawable/ic_media_open"/><ImageButtonandroid:id="@+id/id_internet"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_marginRight="8dp"android:background="#0000"app:srcCompat="@drawable/ic_internet"/><ImageButtonandroid:id="@+id/id_play_pause"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="#0000"app:srcCompat="@drawable/ic_play"/><SeekBarandroid:id="@+id/id_seekbar"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginLeft="4dp"/></LinearLayout><pl.droidsonroids.gif.GifImageViewandroid:id="@+id/id_loading_gif"android:layout_width="50dp"android:layout_height="50dp"android:layout_gravity="center"android:background="#0000"android:visibility="gone"android:src="@drawable/loading"/><TextViewandroid:id="@+id/id_textview"android:layout_width="192dp"android:layout_height="95dp"android:layout_gravity="center"android:background="#0000"android:gravity="center"android:textColor="#1296db"android:textSize="28sp"/><ImageButtonandroid:id="@+id/id_locked_unlocked"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="left|center"android:layout_marginLeft="16dp"android:background="#0000"app:srcCompat="@drawable/ic_unlocked"/><ImageButtonandroid:id="@+id/id_share"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="right|center"android:layout_marginRight="16dp"android:background="#0000"app:srcCompat="@drawable/ic_share"/></FrameLayout>

布局说完接下来就是主程序代码了,本人刚学Android,除了刚开始的一个检测权限的类,就只有MainActivity一个文件,也就是说,总共就两个文件,一个检测权限,一个实现所有功能(感觉所有功能挤在一个文件太大了,觉得以后深入了解后可以优化)。

下面就贴上功能实现代码,小白一只,大神勿喷。。。

检测权限:

单独的一个类用于检测权限,需要什么权限并添加在上面,在主窗口先执行这个检测权限方法:

// 权限支持Permissions.check(this);

类代码:

package example.mydemo1101;import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;/*** Created by 骑着蜗牛飙车 on 2018/03/15.*/public class Permissions
{// 检查和申请权限static final int PERMISSION_REQ_CODE = 1;public static void check(Activity activity){// 要申请的权限列表final String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.INTERNET,Manifest.permission.CAMERA};// 检查本应用是否有了 WRITE_EXTERNAL_STORAGE 权限if (ContextCompat.checkSelfPermission(activity, permissions[0])!= PackageManager.PERMISSION_GRANTED){// 系统将弹出一个对话框,询问用户是否授权ActivityCompat.requestPermissions(activity, permissions, PERMISSION_REQ_CODE);}}// 权限申请的结果// requestCode:请求码// permissions: 申请的N个权限// grantResults: 每个权限是否被授权
//    @Override
//    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
//    {
//
//        if(requestCode == PERMISSION_REQ_CODE)
//        {
//            for(int i=0; i<permissions.length;i++)
//            {
//                if(grantResults[i] != PackageManager.PERMISSION_GRANTED)
//                {
//                    // 惨,用户没给我们授权...这意味着有此功能就不能用了
//                }
//            }
//        }
//    }
}

打开本地媒体器,SeekBar滑动进度条,暂停/播放按钮功能:

1、首先是打开本地媒体器,SeekBar滑动进度条,暂停/播放按钮,实现按钮点击事件有好几种,我选择了监听这个方法,个人觉得这种内部类监听一目了然:

打开本体媒体器选择本地视频,这个按钮是写的第一个按钮,没有用监听,而是直接在xml布局文件按钮属性OnClick属性那边写上函数名,这样也是可以的,有个返回码,用于判断是不是这个打开的响应:

其中mediaUri是静态全局变量,用于切换屏幕时重新加载

final int REQ_OPEN_FILE = 2333; // 返回码
static Uri mediaUri;  // 视频路径

打开按钮功能:

// 点击 ‘打开’ 按钮public void openFile( View view) {Intent intent = new Intent( Intent.ACTION_GET_CONTENT); // 特殊种类的数据,比如相片或者录音intent.setType("video/*"); // 只显示视频startActivityForResult(intent, REQ_OPEN_FILE);imageButton_pause = (ImageButton)findViewById(R.id.id_play_pause);findViewById(R.id.id_help).setVisibility(View.GONE); //将说明关闭if (mediaUri != null) {imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_pause));} else {imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_play));}}// 从媒体管理器返回protected void onActivityResult(int requestCode, int resultCode, Intent data) {if ( requestCode == REQ_OPEN_FILE) {if ( resultCode == RESULT_OK) {mediaUri = data.getData();videoView.setVideoURI(mediaUri);videoView.start();isInternetMedia = false;if (mediaUri != null) {imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_pause));} else {imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_play));}}// 隐藏掉主工具栏if(mRunnable_2 != null) {msgHandler.removeCallbacks(mRunnable_2);msgHandler.postDelayed(mRunnable_2, 3000); // 延时3秒}// 隐藏锁定按钮if(mRunnable_3 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_3); // 清除线程msgHandler.postDelayed(mRunnable_3, 3000); // 延时3秒}// 隐藏掉分享按钮if(mRunnable_5 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_5); // 清除线程msgHandler.postDelayed(mRunnable_5, 3000); // 延时3秒}// 隐藏标题Layout按钮if(mRunnable_7 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_7); // 清除线程msgHandler.postDelayed(mRunnable_7, 3000); // 延时3秒}}}

播放/暂停功能:

// 播放控制 (暂停、继承)imageButton_pause = (ImageButton)findViewById(R.id.id_play_pause);imageButton_pause.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {findViewById(R.id.id_help).setVisibility(View.GONE); //将说明关闭// 此处可以优化 (事先把两个图标先加载好, 不要每次现加载)if ( videoView.isPlaying()) {Log.w(TAG, "正在播放,现在暂停...");if ( mediaUri != null) {videoView.pause();imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_play));}} else {Log.w(TAG, "不在播放,现在继续...");if ( mediaUri != null){videoView.start();imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_pause));}}}});

进度条功能:

可拖放进度条设置就比较麻烦了,需要写个定时器每隔0.5秒监听当前位置信息,监听函数有三个实现接口,第一个onProgressChanged函数是为了监听拖动进度条实时显示在上面中间标题栏一个TextView,有个特别诡异的地方,如果不加if(fromUser)判断是不是用户操作,这样定时器每隔0.5秒就会执行这个方法,然后本来你想隐藏掉标题栏却发现它始终在那,从未消失。。。话说这个fromUser还是方法自带的参数,很好奇谁设计的这个方法:

// 播放控制(进度拖放)seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {// 进度条开始拖动的时候// 首先判断是不是用户操作,不然每隔0.5秒定时器会刷新一直显示if (fromUser) {controlBar.setVisibility(View.VISIBLE);SimpleDateFormat sdf;double currentTime = (progress / 100.0 )* videoView.getDuration(); // 将当前位置换算成视频总时长的指定位置时间int hour = (int) (currentTime / (3600 * 1000));if ( hour < 0 || hour == 0) {sdf = new SimpleDateFormat("mm:ss"); // 转换时间} else {sdf = new SimpleDateFormat("HH:mm:ss"); // 转换时间}sdf.setTimeZone(TimeZone.getTimeZone("GMT+00:00")); // 设置中国时区String result = sdf.format(currentTime) + " / " + getAllTime(); // 将时分秒转换成String类型//((TextView)findViewById(R.id.id_progress)).setText(result);brightnessTextView.setVisibility(View.VISIBLE);brightnessTextView.setText( result);} /*else {((TextView)findViewById(R.id.id_progress)).setText(result);}*/}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 控制视频跳转到目标位置int progress = seekBar.getProgress();int position = progress * videoView.getDuration() / 100;videoView.seekTo( position );// 隐藏掉主工具栏if(mRunnable_2 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_2); // 清除线程msgHandler.postDelayed(mRunnable_2, 3000); // 延时3秒}// 隐藏掉中间信息框if(mRunnable_4 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_4); // 清除线程msgHandler.postDelayed(mRunnable_4, 1000); // 延时1秒}}});

视频停止执行跳转:

// 当视频停止时// 更新显示进度: duration总时长 position 当前播放位置public void showProgess(int duration, int position){// 转成百分比int percent = position * 100 / duration;seekBar.setProgress( percent);}

定时器代码,这个有个重要原则(还是重要思想我也不知道选哪个反正都一样),就是不能在工作线程执行UI更新(好像是这么说的),就是想要更新UI,必须发消息给UI线程,然后在Handler里处理(我好像没有全部执行这个原则),消息支持比较复杂,后面再说(后面还有两个定时器,八个消息哈哈哈,想想就刺激,这么多消息,感觉有点冗余):

//  定时器任务   ///private class MyTimerTask extends TimerTask {@Overridepublic void run() {// 如果当前VideoView并不在播放中,就不做什么if ( !videoView.isPlaying() )return;// 取得当前播放进度int duration = videoView.getDuration(); // 获取视频的总时长 ,单位 毫秒(ms)int position = videoView.getCurrentPosition(); // 获取视频的当前播放位置,单位 毫秒(ms)// 注意:在工作线程里不能直接更新UI,必须发消息给UI线程,然后在Handler里处理// 发消息给UI线程Message msg = new Message();msg.what = 1; // 消息类型msg.arg1 = duration; // 第1个参数msg.arg2 = position; // 第2个参数msgHandler.sendMessage(msg);}}

2、锁定按钮功能:

锁定按钮是添加个boolean判断,其他按钮监听时用上if判断是不是处于锁定状态,这样就能实现锁定功能,当然,后面测试时发现锁定状态无法锁定屏幕自动旋转,只好加上屏幕判断,是的话锁定当前屏幕状态,无法自动旋转,在这之前还有自动旋转的功能,而且还有新建一个线程用于执行控件的隐藏,这个延时后面再说:

// 判断当前横屏还是竖屏,用于切换界面orientation = getResources().getConfiguration().orientation;if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {setContentView(R.layout.activity_main_portrait);Log.d(TAG,"现在为竖屏模式portrait...");} else {requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);setContentView(R.layout.activity_main_landscape);Log.d(TAG,"现在为横屏模式landscape...");}
// 锁定按钮点击监听imageButton_islocked = (ImageButton)findViewById(R.id.id_locked_unlocked);imageButton_islocked.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.w(TAG, "点中锁定按钮");findViewById(R.id.id_help).setVisibility(View.GONE); //将说明关闭if ( islocked) {Log.w(TAG, "锁定按钮解除,换成解锁按钮");islocked = false;imageButton_islocked.setImageDrawable( getDrawable(R.drawable.ic_unlocked));controlBar.setVisibility( View.VISIBLE); // 显示主工具栏imageButton_share.setVisibility( View.VISIBLE); // 显示分享按钮titleBar.setVisibility(View.VISIBLE); // 显示标题栏} else {Log.w(TAG, "解锁变成锁定");islocked = true;imageButton_islocked.setImageDrawable( getDrawable(R.drawable.ic_locked));controlBar.setVisibility( View.GONE); // 隐藏掉主工具栏imageButton_share.setVisibility(View.GONE); // 隐藏掉分享按钮titleBar.setVisibility(View.GONE); // 隐藏掉标题栏}// 判断锁定时屏幕状态,1为竖屏,2为横屏if (orientation == 1) {//锁定竖屏Log.w(TAG, "锁定竖屏");MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);} else if ( orientation == 2){//锁定全屏Log.w(TAG, "锁定全屏");MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}// 隐藏掉主工具栏if(mRunnable_2 != null) {msgHandler.removeCallbacks(mRunnable_2);msgHandler.postDelayed(mRunnable_2, 3000); // 延时3秒}// 隐藏锁定按钮if(mRunnable_3 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_3); // 清除线程msgHandler.postDelayed(mRunnable_3, 3000); // 延时3秒}// 隐藏掉分享按钮if(mRunnable_5 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_5); // 清除线程msgHandler.postDelayed(mRunnable_5, 3000); // 延时3秒}// 隐藏标题Layout按钮if(mRunnable_7 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_7); // 清除线程msgHandler.postDelayed(mRunnable_7, 3000); // 延时3秒}}});

3、分享功能的实现:

我用的是系统自带的分享,不是QQ的第三方包或者其他,这样分享页面在我的手机测试时有三页20多个选项,还有曾经自己写过一个小程序接收分享的图标,分享时会有判断,一是路径是否为空,还有一个判断为是不是网络视频,本地视频则分享整个视频,而网络视频只是分享网络地址。

// 分享按钮点击imageButton_share = (ImageButton) findViewById(R.id.id_share);imageButton_share.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.w(TAG, "点中分享按钮");findViewById(R.id.id_help).setVisibility(View.GONE); //将说明关闭if ( !isInternetMedia && mediaUri != null) {Intent intent = new Intent(Intent.ACTION_SEND);intent.putExtra(Intent.EXTRA_STREAM, mediaUri);intent.setType("video/*");startActivity(Intent.createChooser(intent, "分享我的视频到..."));} else if ( isInternetMedia) {String content = ((EditText) findViewById(R.id.id_internetUri)).getText().toString();content = content.trim();  // 去除两边的空白if ( content.length() == 0) return;Intent intent = new Intent( Intent.ACTION_SEND);intent.setType( "text/plain");intent.putExtra(Intent.EXTRA_TEXT, content);startActivity(Intent.createChooser(intent, "分享网络视频地址到..."));}}});

4、滑动监听:

我重写了onTouchEvent方法,首先获得屏幕的宽度,因为竖屏和横屏区域按比例划分,就是在一部分的区域滑动才会执行,我设置大概四分之一,最右边四分之一滑动音量加减,最左边亮度加减,而快进快退是计算滑动时有没有在一个范围(即Y轴偏移量的限制),同时判断路径是否为null(即是否有视频),否则不执行。有个缺点,就是音量,亮度,快进快退的速度设置了固定值,但我感觉可以优化成滑动X轴距离越大,加速度越大(即加减越快)。而单击双击事件是在这之前还有一个函数,而这个函数返回false即还能执行滑动监听,单击双击等下再说:

// 左边边屏幕滑动亮度显示private float startX = 0;//手指按下时的X坐标private float startY = 0;//手指按下时的Y坐标
// 监听触摸屏幕@Overridepublic boolean onTouchEvent(MotionEvent event) {int screenWidth = getResources().getDisplayMetrics().widthPixels;if (mediaUri == null) {mediaUrinull = true;} else {mediaUrinull = false;}switch ( event.getAction()) {case MotionEvent.ACTION_DOWN: {startX = event.getX();startY = event.getY();break;}case MotionEvent.ACTION_MOVE: {// TODO 音量float distanceX = event.getX() - startX;float distanceY = event.getY() - startY;if (!mediaUrinull && startX < 0.25*screenWidth && Math.abs(distanceX) < 50 && distanceY > 100) {// TODO 减小亮度setBrightness(-6);}else if (!mediaUrinull && startX < 0.25*screenWidth && Math.abs(distanceX) < 50 && distanceY < -100) {// TODO 增加亮度setBrightness(6);}if (!mediaUrinull && startX > 0.75*screenWidth && Math.abs(distanceX) < 50 && distanceY > 100) {// TODO 减小音量setVolume(false);}else if (!mediaUrinull && startX > 0.75*screenWidth && Math.abs(distanceX) < 50 && distanceY < -100) {// TODO 增加音量setVolume(true);}if ( !mediaUrinull && Math.abs(distanceY) < 50 && distanceX > 100) {// TODO 快进setvideo( true);}else if ( !mediaUrinull && Math.abs(distanceY) < 50 && distanceX < -100) {// TODO 快退setvideo( false);}break;}}return super.onTouchEvent(event);}

音量加减函数:

执行函数——音量加减,传进来的参数是boolean类型的,true是音量加,false是音量键,增减音量是固定值,但是在音量加减之前先判断是否被锁定,锁定无法执行,同时中间信息框提示当前音量的百分比,然后音量减到0时调用手机振动器震动,但我的震动好像有点不太一样,那种体会怪怪的,大家可以测试一下,感觉就像别人就震动一下,我也能震动一下,但是滑倒音量0时继续滑,你会感觉那一下还没结束又重新开始了新的一下,估计是我判断的问题或者重复实现震动的问题(目测以后可以优化的):

 // 设置音量大小private void setVolume(boolean flag) {// 首先判断是否被锁定if ( !islocked) {// 获取音量管理器AudioManager manager = (AudioManager) getSystemService(AUDIO_SERVICE);// 获取当前音量int curretnV = manager.getStreamVolume(AudioManager.STREAM_MUSIC);if ( flag) {curretnV ++ ;} else {curretnV -- ;if (curretnV < 0) {vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);vibrator.vibrate(300);curretnV = 0;}}manager.setStreamVolume(AudioManager.STREAM_MUSIC, curretnV, AudioManager.FLAG_SHOW_UI);brightnessTextView.setVisibility(View.VISIBLE);brightnessTextView.setText("音量:" + (float)Math.ceil((curretnV / 16.0) * 100) + "%");// 隐藏掉中间信息框if(mRunnable_4 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_4); // 清除线程msgHandler.postDelayed(mRunnable_4, 1000); // 延时1秒}}

亮度加减函数:

亮度加减参数设置的不是固定加减,而是依据传进来参数的大小来设定亮度加减的速度,值越大调整亮度越快,同时使用跟音量加减同一个信息框控件将当前亮度显示出来:

// 设置屏幕亮度 lp = 0 最暗 lp = 1 最亮private void setBrightness( float brightness) {// 首先判断是否被锁定if ( !islocked) {WindowManager.LayoutParams lp = getWindow().getAttributes();lp.screenBrightness = lp.screenBrightness + brightness / 255.0f;if (lp.screenBrightness > 1) {lp.screenBrightness = 1;} else if ( lp.screenBrightness < 0.1) {lp.screenBrightness = (float) 0.1;}getWindow().setAttributes(lp);float sb = lp.screenBrightness;brightnessTextView.setVisibility(View.VISIBLE);brightnessTextView.setText("亮度:" + (int) Math.ceil(sb * 100) + "%");// 隐藏掉中间信息框if(mRunnable_4 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_4); // 清除线程msgHandler.postDelayed(mRunnable_4, 1000); // 延时1秒}}}

快进快退函数:

快进快退参数也是boolean类型,快进快退是固定值,注意的是里面的数值是以毫秒计算,因为获得视频的位置返回值是毫秒,刚开始我发现快退能把时间变成-1负数!不明白原因的我只能在最后面加一个判断,就是小于0直接设置为0。。。

// 设置快进快退private void setvideo( boolean flag) {// 首先判断是否被锁定if ( !islocked) {controlBar.setVisibility( View.VISIBLE); // 显示主工具栏int currentT = videoView.getCurrentPosition();//播放的位置int nowTime;if ( flag) {// TODO 真为快进videoView.seekTo(currentT + 300);nowTime = (currentT + 300);} else {// TODO 假为快退videoView.seekTo(currentT - 300);nowTime = (currentT - 300);}if ( nowTime < 0)nowTime = 0;changeTime( nowTime);// 隐藏掉主工具栏if(mRunnable_2 != null) {msgHandler.removeCallbacks(mRunnable_2);msgHandler.postDelayed(mRunnable_2, 3000); // 延时3秒}}}

快进快退中间信息框需要提示当前位置和总时长,类似于03:04/05:06这种效果,因为是毫秒级别,需要转换成时分秒,就专门写了个函数用于计算并显示,在使用格式化函数SimpleDateFormat,测试时发现分钟和秒数正确但是小时却始终大八小时,最终才发现那是美国的时区,需要将时区设置成中国才是正确,并且有个判断,不满一小时就不显示小时数字:

// 秒数转换为分钟数private void changeTime( int time) {SimpleDateFormat sdf;int hour = time / (3600 * 1000);if ( hour < 0 || hour == 0) {sdf = new SimpleDateFormat("mm:ss"); // 转换时间} else {sdf = new SimpleDateFormat("HH:mm:ss"); // 转换时间}sdf.setTimeZone(TimeZone.getTimeZone("GMT+00:00")); // 设置中国时区String currentTime = sdf.format(time); // 将时分秒转换成String类型String result = currentTime + " / " + getAllTime();brightnessTextView.setVisibility(View.VISIBLE);brightnessTextView.setText( result);Log.w("时间", time + "");// 隐藏掉中间信息框if(mRunnable_4 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_4); // 清除线程msgHandler.postDelayed(mRunnable_4, 1000); // 延时1秒}}

5、全屏按钮和缩小按钮点击功能;

这个就比较变态了,因为全屏缩小会销毁Activity重新建立,虽然说有不销毁的,但是不知道为啥就不喜欢用,而且不销毁那个需要一个特定方法,需要在那个方法写(好吧,我不会啊,只知道重新销毁,那个以后再说再学),这里有个不是bug的bug自己感觉,就是打开软件,旋转手机能够自己横屏,还能四个方向旋转,但是点击全屏缩小或者锁定按钮设置了屏幕固定属性,发现自动旋转失效,只能通过按钮点击旋转,而且横屏之后不能上下180度旋转,但我发现bilibili客户端也近乎是这样,只是我没有上下180度旋转,可能是代码原因?

// 全屏控制imageButton_fullScreen = (ImageButton)findViewById(R.id.id_fullscreen);imageButton_fullScreen.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if(getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {//切换竖屏Log.w(TAG, "点击缩小");MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}else{//切换横屏Log.w(TAG, "点击全屏");MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}}});

切换屏幕时Activity的销毁和重绘:

重点难点又来了,就是切换屏幕时需要重新绘制Activity,这就需要用到两个系统自带的函数onRestoreInstanceState和onSaveInstanceState,前着恢复数据,后者切换屏幕销毁时保存数据:

因为我的视频地址和当前时间是静态全局变量,所以不需要保存,不过时间这个问题本地视频切换能自动会到切换屏幕当时的位置,网络视频切换需要重新加载等比较久的缓存才能到,可能有能优化的地方,但是其他一些数据就需要保存了,嘿嘿嘿,这就需要考虑哪些需要保存了,我保存了切换屏幕前暂停/播放状态,是否锁定状态,是否是网络视频,还有视频标题的信息,最重要先取出当前视频的时间,这样应该就差不多了:

当前位置时间的定义:

static int sec; // 当前视频位置的时间

代码:

// 切换屏幕时恢复@Overrideprotected void onRestoreInstanceState(Bundle savedInstanceState) {findViewById(R.id.id_help).setVisibility(View.GONE); //将说明关闭isPause = savedInstanceState.getBoolean("ispause");islocked = savedInstanceState.getBoolean("islocked");isInternetMedia = savedInstanceState.getBoolean("isInternetMedia");titleUri = savedInstanceState.getString("title");Log.w("a",orientation + "a");Log.w(TAG, String.valueOf(isPause));if ( !islocked) {controlBar.setVisibility(View.VISIBLE);} else {controlBar.setVisibility(View.GONE);}if ( islocked) {imageButton_islocked.setImageDrawable( getDrawable(R.drawable.ic_locked));} else {imageButton_islocked.setImageDrawable( getDrawable(R.drawable.ic_unlocked));}Log.w(TAG,sec+"");Log.w(TAG,mediaUri+"");if(mediaUri != null){loadingGif.setVisibility(View.VISIBLE);videoView.setVideoURI(mediaUri);//videoView.start();videoView.seekTo(sec);if ( /*videoView.isPlaying()*/ isPause ) {videoView.pause();imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_play));} else {videoView.start();imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_pause));}//loadingGif.setVisibility(View.GONE);}// 隐藏掉主工具栏if(mRunnable_2 != null) {msgHandler.removeCallbacks(mRunnable_2);msgHandler.postDelayed(mRunnable_2, 3000); // 延时3秒}// 隐藏锁定按钮if(mRunnable_3 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_3); // 清除线程msgHandler.postDelayed(mRunnable_3, 3000); // 延时3秒}// 隐藏掉分享按钮if(mRunnable_5 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_5); // 清除线程msgHandler.postDelayed(mRunnable_5, 3000); // 延时3秒}// 隐藏标题Layout按钮if(mRunnable_7 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_7); // 清除线程msgHandler.postDelayed(mRunnable_7, 3000); // 延时3秒}super.onRestoreInstanceState(savedInstanceState);}// 发生切换屏幕事件,,Acitivity即将销毁... 所以要先把当前界面的状态数据备份一下 ...@Overrideprotected void onSaveInstanceState(Bundle outState) {// 取出当前时间sec = videoView.getCurrentPosition();if ( !videoView.isPlaying()) {isPause = true;} else {isPause = false;}outState.putBoolean("ispause", isPause);outState.putBoolean("islocked", islocked);outState.putBoolean("isInternetMedia", isInternetMedia);outState.putString("title",titleUri);Log.w(TAG,mediaUri+"");Log.w(TAG,sec+"");Log.w("a",orientation + "b");super.onSaveInstanceState(outState);}

6、顶部标题栏功能的实现:

右上角显示当前时间,上面中间显示视频当前进度,左上角显示标题(本地视频显示标题,网络视频显示地址),这个就用到了两个定时器,三个线程,竖屏因为不需要显示时间就少一个,同时也用到了消息处理:

先把工作线程代码贴上,刚开始以为就几个线程,命名直接取1、2、3。。。到后面发现八个。。第九个只创建一次才没专门写,这是第八个线程,也是发送第八个消息(消息代码放到最下面贴上),用于显示标题,如果是本地视频截取Uri路径最后一个/后面的内容,要注意中文乱码问题,网络视频直接显示地址,判断条件是那个boolean类型,这个在执行跳转网络地址方法会变成true:

private Runnable mRunnable_8 = new Runnable() {@Overridepublic void run() {// 判断是不是网络视频,如果是本地视频标题显示视频名字,网络视频则显示地址if( !isInternetMedia) {String decodeUri = Uri.decode(String.valueOf(mediaUri)); // 将Uri转换成中文(乱码问题)titleUri = decodeUri.split("/")[decodeUri.split("/").length-1]; // 截取Uri的文件名} else {titleUri = String.valueOf(mediaUri);}Message msg = new Message();msg.what = 8;msg.obj = titleUri;msgHandler.sendMessage(msg);}};

中间实时显示进度信息当时有几个思想,一个实在seekBar那三个自带函数实现,发现可能功力不行,只好考虑使用定时器,这时候又有想法,本来想偷个懒,将实时显示视频当前位置信息混到其他定时器(比如混到实时显示当前时间,或者混到最开始那个代码),还是技术比行失败,只好新建一个定时器:

// 定时器任务,实时显示当前位置private class CurrentpositionTask extends TimerTask {SimpleDateFormat sdf;@Overridepublic void run() {// 得到当前时间int hour = videoView.getCurrentPosition() / (3600 * 1000);if ( hour < 0 || hour == 0) {sdf = new SimpleDateFormat("mm:ss"); // 转换时间} else {sdf = new SimpleDateFormat("HH:mm:ss"); // 转换时间}sdf.setTimeZone(TimeZone.getTimeZone("GMT+00:00")); // 设置中国时区String currentTime = sdf.format(videoView.getCurrentPosition()); // 将时分秒转换成String类型String result = currentTime + " / " + getAllTime();// 在定时器回调里也不能更新UI,需要发消息5Message msg = new Message();msg.what = 9; // 消息类型msg.obj = result;msgHandler.sendMessage(msg);}}

还有只在横屏显示当前时间的定时器:

// 定时器任务,在横屏实时显示当前时间private class CurrentTimerTask extends TimerTask {SimpleDateFormat sdfc = new SimpleDateFormat("HH:mm:ss");@Overridepublic void run() {// 得到当前时间String timestr = sdfc.format(System.currentTimeMillis());// 在定时器回调里也不能更新UI,需要发消息5Message msg = new Message();msg.what = 6; // 消息类型msg.obj = timestr;msgHandler.sendMessage(msg);}}

在自带的onStart方法重写启动定时器,也在onStop方法关闭,标题就执行一次应该就可以了,也放进去吧,然后一个文件执行全部的功能,我大概知道我的1200行代码怎么来的了:

先将定义写上,三个定时器三个定时器任务,总感觉可以优化放在一起执行。。。

private Timer timer;  // 定时器private TimerTask timerTask; // 定时功能private Timer timer_current;  // 定时器private TimerTask currentTimeTask; // 实时显示当前时间private Timer timer_currentposition;  // 定时器private TimerTask currentposiTask; // 实时显示当前视频位置和总长度

代码:

 // 定时器  /@Overrideprotected void onStart(){super.onStart();if(timer == null){// 启动定时器(间隔500ms)timerTask = new MyTimerTask();timer = new Timer();timer.schedule(timerTask, 500, 500);}if(timer_current == null && orientation == 2){// 启动定时器(间隔500ms)currentTimeTask = new CurrentTimerTask();timer_current = new Timer();timer_current.schedule(currentTimeTask, 500, 500);}if(timer_currentposition == null){// 启动定时器(间隔500ms)currentposiTask = new CurrentpositionTask();timer_currentposition = new Timer();timer_currentposition.schedule(currentposiTask, 500, 500);}if ( mediaUri != null && mRunnable_8 != null) {// 将标题显示msgHandler.removeCallbacks(mRunnable_7); // 清除线程new Thread(mRunnable_8).start();}}// 定时器  /@Overrideprotected void onStop(){if(timer != null){//本界面隐藏时,要停止定时器(因为本界面已经隐藏了,如果继续刷新界面将毫无意义、白白耗费CPU)timer.cancel();timer = null;}if(timer_current != null){//本界面隐藏时,要停止定时器(因为本界面已经隐藏了,如果继续刷新界面将毫无意义、白白耗费CPU)timer_current.cancel();timer_current = null;}if(timer_currentposition != null){//本界面隐藏时,要停止定时器(因为本界面已经隐藏了,如果继续刷新界面将毫无意义、白白耗费CPU)timer_currentposition.cancel();timer_currentposition = null;}super.onStop();}

7、播放网络视频功能:

来来来,说完变态的定时器的方法,再看看这个播放网络视频多么善解人意啊,需要注意的是,网络视频缓存自带的方法不太符合我的习惯,就是你点击跳转它不认为你是缓存,就不显示缓存动态图,你会发现黑屏一段时间后才播放,或者你拖动进度条后,视频先卡住然后显示你跳转的位置图片又卡住,这时才显示显示缓存动态图才告诉你正在加载,而我就在一些地方比如跳转按钮执行完先显示缓存动态图,然后又在一些地方关闭这个动态图,这样才符合我的习惯,个人感觉习惯,不喜欢可以自己删掉或者修改,就那个动态图setVisibility方法:

重点说一下,网络视频地址VideoView好像有限制,只支持部分视频流的播放,就别想用这个跳广告了(本人不会啊。。。),顺便丢几个网络视频测试地址(网上找的),大家可以测试一下:

http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4
http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4
代码:
// 网络视频地址跳转按钮点击imageButton_sureInternetVideo = (ImageButton)findViewById(R.id.id_internetSure);imageButton_sureInternetVideo.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mediaUri = Uri.parse(((EditText) findViewById(R.id.id_internetUri)).getText().toString());Log.w(TAG, "点中网络地址确定按钮..");videoView.setMediaController(new MediaController(MainActivity.this));videoView.setVideoURI(mediaUri);videoView.requestFocus(); // 获取焦点videoView.start();imageButton_pause.setImageDrawable( getDrawable(R.drawable.ic_pause));isInternetMedia = true;internetControlBar.setVisibility(View.GONE);loadingGif.setVisibility(View.VISIBLE);// 隐藏锁定按钮if(mRunnable_3 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_3); // 清除线程msgHandler.postDelayed(mRunnable_3, 3000); // 延时3秒}// 隐藏掉分享按钮if(mRunnable_5 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_5); // 清除线程msgHandler.postDelayed(mRunnable_5, 3000); // 延时3秒}// 隐藏标题Layout按钮if(mRunnable_7 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_7); // 清除线程msgHandler.postDelayed(mRunnable_7, 3000); // 延时3秒}if ( mediaUri != null && mRunnable_8 != null) {// 将标题显示msgHandler.removeCallbacks(mRunnable_8); // 清除线程new Thread(mRunnable_8).start();}}});

网络视频缓存监听:

这是VideoView自带的缓存监听的三个方法,重写了:

// 视频缓冲监听loadingGif = (GifImageView) findViewById(R.id.id_loading_gif);videoView.setOnInfoListener(new MediaPlayer.OnInfoListener() {@Overridepublic boolean onInfo(MediaPlayer mp, int what, int extra) {if(what == MediaPlayer.MEDIA_INFO_BUFFERING_START){//Toast.makeText(MainActivity.this, "正在缓冲", Toast.LENGTH_LONG).show();loadingGif.setVisibility(View.VISIBLE);Log.w(TAG,"正在缓冲");}else if(what == MediaPlayer.MEDIA_INFO_BUFFERING_END){//此接口每次回调完START就回调END,若不加上判断就会出现缓冲图标一闪一闪的卡顿现象if(mp.isPlaying()){//Toast.makeText(MainActivity.this, "缓冲结束", Toast.LENGTH_LONG).show();loadingGif.setVisibility(View.GONE);Log.w(TAG,"缓冲结束");videoView.setVisibility(View.VISIBLE);}}return true;}});// 加载网络资源黑屏结束监听videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {loadingGif.setVisibility(View.GONE);}});// 视频完成监听videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {Toast.makeText(MainActivity.this, "视频结束", Toast.LENGTH_SHORT).show();Log.w(TAG,"播放完成了");loadingGif.setVisibility(View.GONE);//videoView.start();}});

网络视频错误监听:

还有视频播放错误监听,我偷个懒,把所有错误信息都给贴上去了(我真是太机智了),需要注意的是,出错后应该将mediaUri设置成null,不然它会一直跳转输入的视频反复错误错误错误。。。:

// 错误监听videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {Log.w("通知", "播放中出现错误");switch (what) {case -1004:Log.w("Streaming Media", "MEDIA_ERROR_IO");break;case -1007:Log.w("Streaming Media", "MEDIA_ERROR_MALFORMED");break;case 200:Log.w("Streaming Media", "MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK");break;case 100:Log.w("Streaming Media", "MEDIA_ERROR_SERVER_DIED");break;case -110:Log.w("Streaming Media", "MEDIA_ERROR_TIMED_OUT");break;case 1:Log.w("Streaming Media", "MEDIA_ERROR_UNKNOWN");break;case -1010:Log.w("Streaming Media", "MEDIA_ERROR_UNSUPPORTED");break;}switch (extra) {case 800:Log.w("Streaming Media", "MEDIA_INFO_BAD_INTERLEAVING");break;case 702:Log.w("Streaming Media", "MEDIA_INFO_BUFFERING_END");break;case 701:Log.w("Streaming Media", "MEDIA_INFO_METADATA_UPDATE");break;case 802:Log.w("Streaming Media", "MEDIA_INFO_METADATA_UPDATE");break;case 801:Log.w("Streaming Media", "MEDIA_INFO_NOT_SEEKABLE");break;case 1:Log.w("Streaming Media", "MEDIA_INFO_UNKNOWN");break;case 3:Log.w("Streaming Media", "MEDIA_INFO_VIDEO_RENDERING_START");break;case 700:Log.w("Streaming Media", "MEDIA_INFO_VIDEO_TRACK_LAGGING");break;}loadingGif.setVisibility(View.GONE);mediaUri = null;return false;}});

8、单击双击监听(双击暂停,单击执行一些步骤):

我使用的是VideoView的setOnTouchListener,上面我又说过这个方法返回值是false的话可以继续滑动监听,不然返回true无法接着执行滑动监听。双击是用变量统计两次点击的时间间隔是否在500毫秒以内,是的话执行双击事件(暂停):

// 控制面板的控制,单击/双击判断:videoView.setOnTouchListener(new View.OnTouchListener() {long lastTime,curTime; // 最后一点击和当前点击的时间@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN: {lastTime = curTime; // 获取第一次点击时间curTime = System.currentTimeMillis(); // 获取当前时间Log.w(TAG, "单击点中画面..");imageButton_islocked.setVisibility(View.VISIBLE);internetControlBar.setVisibility(View.GONE);findViewById(R.id.id_help).setVisibility(View.GONE); //将说明关闭// 首先判断是否被锁定if (!islocked) {controlBar.setVisibility(View.VISIBLE);imageButton_share.setVisibility(View.VISIBLE);titleBar.setVisibility(View.VISIBLE);if (mediaUri == null) {mediaUrinull = true;} else {mediaUrinull = false;}// 隐藏掉主工具栏if (mRunnable_2 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_2); // 清除线程msgHandler.postDelayed(mRunnable_2, 3000); // 延时3秒}// 隐藏掉分享按钮if (mRunnable_5 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_5); // 清除线程msgHandler.postDelayed(mRunnable_5, 3000); // 延时3秒}// 隐藏标题Layout按钮if (mRunnable_7 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_7); // 清除线程msgHandler.postDelayed(mRunnable_7, 3000); // 延时3秒}}// 隐藏锁定按钮if (mRunnable_3 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_3); // 清除线程msgHandler.postDelayed(mRunnable_3, 3000); // 延时3秒}// 判断双击的时间间隔if (curTime - lastTime < 500) {Log.w(TAG, "双击点中画面..");// 首先判断是否被锁定if (!islocked) {if (videoView.isPlaying() && !mediaUrinull) {videoView.pause();imageButton_pause.setImageDrawable(getDrawable(R.drawable.ic_play));} else if (!videoView.isPlaying() && !mediaUrinull) {videoView.start();imageButton_pause.setImageDrawable(getDrawable(R.drawable.ic_pause));}}return true;}break;}default:break;}return false;}});

9、手机点击返回键连续点击两次才退出功能的实现:

按返回键,连续按两次才会退出,按一次提醒再按一次才会退出,我发现我的手机测试时Log.w不好使,就是不显示,只在编译器显示,所以只能在编译器调试用,为了能提示用户使用Toast.makeText方法,用一个变量统计点击的次数,并且判断是不是在两秒以内点击两次:

@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN){if((System.currentTimeMillis()-exitTime) > 2000){Toast.makeText(getApplicationContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show();exitTime = System.currentTimeMillis();} else {finish();System.exit(0);}return true;}return super.onKeyDown(keyCode, event);}

10、打开本地视频选择打开方式时可选择本播放器播放:

视频播放器能够出现在打开方式上,即视频长按可以选择我的播放器进行播放,在onCreate方法最后面添加上代码:

// 接受外部调用Intent intent = getIntent();Uri mediaUri = intent.getData();if(mediaUri != null){videoView.setVideoURI(mediaUri);videoView.start();}

11、三秒延时隐藏线程、消息处理:

最复杂的功能来了,就是消息处理,工作线程等用来更新UI的,话说连我都不知道最后怎么写的,只知道这么写能实现功能,却不知道为什么能实现,可能学的不够好,据说有三种方法能实现,我好像用了三种还是两种来着。。。

9个消息,分别处理不同的功能:

//  消息支持   /private  class MyHandler extends Handler {@Overridepublic void handleMessage(Message msg) {if ( msg.what == 1) {// 从消息里去除进度数据,然后更新UIint duration = msg.arg1;int position = msg.arg2;showProgess(duration, position);} else if (msg.what == 2) {// 将主工具栏隐藏controlBar.setVisibility(View.GONE);} else if ( msg.what == 3) {// 将锁定按钮隐藏imageButton_islocked.setVisibility( View.GONE);} else if ( msg.what == 4) {// 将中间信息框隐藏brightnessTextView.setVisibility(View.GONE);} else if ( msg.what == 5) {// 将分享按钮隐藏imageButton_share.setVisibility(View.GONE);} else if ( msg.what == 6) {// 实时更新当前系统时间UIString timestr = (String)msg.obj;((TextView)findViewById(R.id.id_time)).setText(timestr);} else if ( msg.what == 7) {// 将标题Layout隐藏titleBar.setVisibility(View.GONE);} else if ( msg.what == 8) {// 显示标题String titleUri = (String)msg.obj;((TextView)findViewById(R.id.id_title)).setText(titleUri);} else if ( msg.what == 9) {// 显示当前位置String curr = (String) msg.obj;((TextView)findViewById(R.id.id_progress)).setText(curr);}}}

线程代码:

// 发送隐藏主工具栏的消息private Runnable mRunnable_2 = new Runnable() {@Overridepublic void run() {Message msg = new Message();msg.what = 2;msgHandler.sendMessage(msg);}};// 发送隐藏锁定按钮的消息private Runnable mRunnable_3 = new Runnable() {@Overridepublic void run() {Message msg = new Message();msg.what = 3;msgHandler.sendMessage(msg);}};// 发送隐藏中间信息框的消息private Runnable mRunnable_4 = new Runnable() {@Overridepublic void run() {Message msg = new Message();msg.what = 4;msgHandler.sendMessage(msg);}};// 发送隐藏分享按钮的信息private Runnable mRunnable_5 = new Runnable() {@Overridepublic void run() {Message msg = new Message();msg.what = 5;msgHandler.sendMessage(msg);}};// 发送隐藏标题Layout的信息private Runnable mRunnable_7 = new Runnable() {@Overridepublic void run() {Message msg = new Message();msg.what = 7;msgHandler.sendMessage(msg);}};private Runnable mRunnable_8 = new Runnable() {@Overridepublic void run() {// 判断是不是网络视频,如果是本地视频标题显示视频名字,网络视频则显示地址if( !isInternetMedia) {String decodeUri = Uri.decode(String.valueOf(mediaUri)); // 将Uri转换成中文(乱码问题)titleUri = decodeUri.split("/")[decodeUri.split("/").length-1]; // 截取Uri的文件名} else {titleUri = String.valueOf(mediaUri);}Message msg = new Message();msg.what = 8;msg.obj = titleUri;msgHandler.sendMessage(msg);}};

而如何调用呢,举个简单的栗子:

比如说隐藏掉主工具栏,就那个全屏暂停进度条那一栏:

// 隐藏掉主工具栏if(mRunnable_2 != null) { // 判断是否重复点击,是的话清除掉这个线程重新新建一个msgHandler.removeCallbacks(mRunnable_2); // 清除线程msgHandler.postDelayed(mRunnable_2, 3000); // 延时3秒}

刚开始延时三秒发现反复点击到后面不到三秒就隐藏掉,这是因为重复点击创建N个线程,每个线程都三秒后隐藏,第一个三秒隐藏你还没执行,第二个点击就来了就导致执行了第一个新城三秒隐藏,所以首先判断线程不为空,然后移除掉那个线程,重新创建一个线程,这样就能正确执行三秒隐藏了。

总结一下第一次写CSDN博客有点害羞,花了将近两天完善了这篇博客,希望各位能够学到一些有用的东西,欢迎互相讨论,太累了,但是收获满满,不过最重要的还是:

项目原创!文章原创!所以转载请注明出处!

利用VideoView实现播放本地和网络视频,滑动快进快退、滑动调整音量和调整亮度,锁定按钮,分享功能,进度显示,双击暂停等功能相关推荐

  1. 基于VLC封装的播放器VlcPlayer,可播放本地及网络视频,支持视频录制。

    VlcPlayer.h文件 #ifndef VlcPlayer_H #define VlcPlayer_H #ifdef _WIN32 #include <basetsd.h> typed ...

  2. python 使用OpenCV2打开本地及网络摄像头本地及网络视频

    ''' python 使用OpenCV2打开本地及网络摄像头&本地及网络视频 by 郑瑞国 ''' import cv2user, pwd, ip, channel = "admin ...

  3. ios:播放在线的网络视频

    播放在线的网络视频 发表于  2012 年 11 月 19 日  由  logger_huang 第一种: //UIWebView 加在网络视频 在线播放  有声音 UIWebView *myWeb ...

  4. python 循环播放音乐_python gstreamer实现视频快进/快退/循环播放功能

    这篇文章主要介绍了python gstreamer 实现视频快进/快退/循环播放功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下 Gstreamer到底是个啥? ...

  5. vue项目视频实现键盘快进快退,音量调大小及监听播放事件

    直接上代码 <div style="padding-top:56.25%" ><video style="width:100%;height:672px ...

  6. 安卓平板倍速_推荐:安卓上本地音频播放器,可实现5秒快进快退和倍速播放...

    前一篇文章推荐过一个app,但那app没倍速播放功能.我又找到了一个更完美的app 安卓手机上我自己需要这样一款app,主要用来听电子书,知识音频等.需要能实现快进快退几秒钟的以及倍速播放功能.这功能 ...

  7. python 网站视频快进_python gstreamer实现视频快进/快退/循环播放功能

    Gstreamer到底是个啥? GStreamer 是一个 基于pipeline的多媒体框架,基于GObject,以C语言写成. 应用GStreamer这个这个多媒体框架,你可以写出任意一种流媒体的应 ...

  8. Silverlight 5 beta新特性探索系列:9.视频快进快退和TextSearch对象对文字项查询

    本节讲诉两个新特性:一.在Silverlight 5中可以控制MediaElement对象播放的视频进行快进快退控制.二.在Silverlight 5中的文字项进行搜索查询. 一.对于MediaEle ...

  9. Java后端处理video快进快退播放以及断点续传的原理和代码

    video 快进快退的原理: 通过对所在服务器上的流媒体进行skip操作,然后再response的header里设置相应的Content-Range以及其他属性,来控制视频流的快进快退的功能. 断点续 ...

  10. ffmpeg播放器快进快退(七)

    指导7:快进快退 处理快进快退命令 现在我们来为我们的播放器加入一些快进和快退的功能,因为如果你不能全局搜索一部电影是很让人讨厌的.同时,这将告诉你av_seek_frame函数是多么容易使用. 我们 ...

最新文章

  1. 微软亚洲互联网工程院招聘NLP算法工程师、AI应用科学家
  2. phpMyAdmin安装图解教程
  3. 某33岁国企程序员求助:目前税后60+,工作975,拿到蚂蚁p7offer,3.8k,6200期权,有必要去镀金吗?...
  4. rails小重构:将图片加入产品Model
  5. (转)jQuery选择器总结
  6. 双中心积分计算氟化氢HF的动能和势能
  7. c# 再次尝试 连接失败_和平精英ios充值失败该怎么办
  8. 使用Java将HTML转成Word格式文件
  9. html文件钓起始标志,关于html页面head标签顺序
  10. 2.6 定位数据不匹配
  11. 编译器、Make和CMake之间的关系
  12. jpa原生query_Spring Data Jpa @Query原生SQL
  13. EDA技术实用教程 | 复习十三 | 计数器
  14. C程序设计语言(KR)笔记
  15. Maven -- dependency详解
  16. 前端(HTML css JS)开发工具及常用插件推荐
  17. CSDN-markdown(文字加色加字号、背景色等)
  18. 互联网让我的人生逆袭
  19. 法拉克机器人自动怎么调_FANUC机器人程序自动启动介绍汇总.ppt
  20. 【重要通知】定了!全国各地的中小商户扶持政策,都在这了!

热门文章

  1. 触摸开关模块,TTP223
  2. 基于单片机的模拟电磁曲炮设计
  3. 易捷行云大规模云数据中心小时级安装部署|轻运维之安装部署
  4. 无刷直流电机学习笔记1
  5. WEB 渗透之文件类操作
  6. 蓝桥杯李白打酒php,蓝桥杯:李白打酒
  7. 海思SDK学习(5)海思媒体处理软件平台MMP(4)VO视频输出
  8. 关于勒索软件的硬道理:我们还没有准备好,这是一场与新规则的斗争,而且它的影响还没有接近顶峰。
  9. QNap Container Station 配置docker源 安装docker镜像
  10. 新手入门matlab之线性系统频域分析