1. VideoView简介

  • Android实现视频播放主要是使用VideoView类来实现的。
  • VideoView背后是使用MediaPlayer来对视频文件进行控制的。
  • 只支持mp4、avi、3gp格式的视频,支持格式单一。

2. VideoView常用方法:

  • setVideoPath:设置要播放的视频文件的位置
  • start:开始或继续播放视频
  • pause:暂停播放视频
  • resume:将视频从头开始播放
  • seekTo:从指定的位置开始播放视频
  • isPlaying:判断当前是否正在播放视频
  • getDuration:获取载入的视频文件的时长

3. VideoView播放视频的小栗子:

  • 添加网络和SD卡权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
  • 添加VideoView布局:
<?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="match_parent"
    android:orientation="vertical">

    <VideoView
        android:id="@+id/vv_VideoView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/play"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Play"/>

        <Button
            android:id="@+id/pause"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Pause"/>

        <Button
            android:id="@+id/replay"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Replay"/>
    </LinearLayout>

</LinearLayout>
  • 添加ButterKnife:
compile 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
  • 初始化权限:
private void requestSDpermission() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        } else {
            initVideoPath();
        }
    }
@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    initVideoPath();
                } else {
                    Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }
  • 初始化VideoPath:
private void initVideoPath() {
        File file = new File(Environment.getExternalStorageDirectory(), "vivo.mp4");
        vvVideoView.setVideoPath(file.getPath());
    }
  • 完整代码:
/**
 * VideoView
 * fu kai qiang 2017/17/31
 */
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.vv_VideoView)
    VideoView vvVideoView;
    Unbinder mUnbinder;
    @BindView(R.id.play)
    Button play;
    @BindView(R.id.pause)
    Button pause;
    @BindView(R.id.replay)
    Button replay;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mUnbinder = ButterKnife.bind(this);
        requestSDpermission();
        initVideoPath();
    }

    private void requestSDpermission() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        } else {
            initVideoPath();
        }
    }

    private void initVideoPath() {
        File file = new File(Environment.getExternalStorageDirectory(), "vivo.mp4");
        vvVideoView.setVideoPath(file.getPath());
    }

    @OnClick({R.id.play, R.id.pause, R.id.replay})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.play:
                if (!vvVideoView.isPlaying()) {
                    vvVideoView.start();
                }
                break;
            case R.id.pause:
                if (vvVideoView.isPlaying()) {
                    vvVideoView.pause();
                }
                break;
            case R.id.replay:
                if (vvVideoView.isPlaying()) {
                    vvVideoView.resume();
                }
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    initVideoPath();
                } else {
                    Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mUnbinder.unbind();
        if (vvVideoView != null) {
            vvVideoView.suspend();
        }
    }
}

4. MediaController简介

  • 从上文可知VideoView自身可以实现视频播放的逻辑,但是我们需要去写布局来操作视频的播放暂停等,那能不能不写布局,就能实现呢?当然可以:VideoView可以借助MediaController实现视频播放的逻辑。MediaController是一个多媒体的类,它提供了丰富的Api,支持快进、快退、上一个、下一个等多媒体操作。

5. MediaController小栗子

  • 添加网络和SD卡权限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
  • 添加VideoView布局:
<?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="match_parent"
    >

    <VideoView
        android:id="@+id/vv_videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout>
  • 初始化本地或网络播放路径
private void initVideoPath() {
        String path_local = Environment.getExternalStorageDirectory().getAbsolutePath() + "/vivo.mp4";
        //本地播放
        mVvVideoView.setVideoPath(path_local);
        //网络播放
//      mVvVideoView.setVideoURI(Uri.parse(...);
    }

提示:网络测试的话Tomcat下webapps下面放vivo.mp4
  • videoView和MediaController进行绑定
private void initBind() {
        MediaController mediaController = new MediaController(this);
        mVvVideoView.setMediaController(mediaController);
        mediaController.setMediaPlayer(mVvVideoView);
    }

注意:必须互相设置进行绑定
  • 暂时设置为横屏:
<activity
        android:name=".MainActivity"
        android:screenOrientation="landscape">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
  • Activity完整代码:
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.vv_videoView)
    VideoView mVvVideoView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        initVideoPath();
        initBind();
    }

    /**
     * videoView和MediaController绑定
     */
    private void initBind() {
        MediaController mediaController = new MediaController(this);
        mVvVideoView.setMediaController(mediaController);
        mediaController.setMediaPlayer(mVvVideoView);
    }

    /**
     * 初始化本地或网络播放路径
     */
    private void initVideoPath() {
//        mVvVideoView.setVideoPath(getLocalPath());
        mVvVideoView.setVideoURI(Uri.parse("http://192.168.0.108:8080/video/vivo.mp4"));
    }

    /**
     * 获取本地路径
     *
     * @return
     */
    @NonNull
    private String getLocalPath() {
        return new File(Environment.getExternalStorageDirectory(), "vivo.mp4").getPath();
    }

}

6. 自定义UI界面

  • VideoView需要添加播放暂停快进快退进度条等等按钮,而MdeiaController自带了播放暂停快进快退进度条等UI界面,但是这些都不是我们想要的,因为UI太过于简陋,所以为了美观,以及需求的多样化,我们需要定义我们自己的UI,通过VideoView自身的逻辑来实现视频播放。
  • 修改布局:
<?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="match_parent"
    >

    <RelativeLayout
        android:id="@+id/rl_videolayout"
        android:layout_width="wrap_content"
        android:layout_height="match_parent">

        <VideoView
            android:id="@+id/vv_videoView"
            android:layout_width="match_parent"
            android:layout_height="240dp"
            />

        <LinearLayout
            android:id="@+id/ll_controllerBar_layout"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:orientation="vertical">
            <!--进度条-->
            <SeekBar
                android:id="@+id/sb_progress_seekbar"
                android:layout_width="match_parent"
                android:layout_height="5dp"
                android:layout_marginLeft="-20dp"
                android:layout_marginRight="-20dp"
                android:indeterminate="false"
                android:max="100"
                android:progress="20"
                android:progressDrawable="@drawable/seekbar_style_pro"
                />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#101010"
                android:gravity="center_vertical">

                <LinearLayout
                    android:id="@+id/ll_left_layout"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:orientation="horizontal">
                    <!--播放暂停-->
                    <Button
                        android:id="@+id/bt_start_pause"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="10dp"
                        android:text="Pause"/>
                    <!--现在的时间-->
                    <TextView
                        android:id="@+id/tv_time_current"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="10dp"
                        android:text="00:00:00"
                        android:textColor="#FFF"
                        android:textSize="20sp"/>
                    <!--斜杠-->
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:text="/"
                        android:textColor="#4C4C4C"
                        android:textSize="5dp"/>
                    <!--总共的时间-->
                    <TextView
                        android:id="@+id/tv_time_total"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:text="00:00:00"
                        android:textColor="#4C4C4C"
                        android:textSize="20sp"/>
                </LinearLayout>

                <LinearLayout
                    android:id="@+id/ll_right_layout"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_alignParentRight="true"
                    android:layout_marginRight="10dp"
                    android:layout_toRightOf="@id/ll_left_layout"
                    android:gravity="center_vertical|right"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/tv_vol_name"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="20dp"
                        android:text="Vol"
                        android:textColor="#FFF"
                        android:textSize="20sp"
                        android:visibility="gone"/>
                    <!--音量-->
                    <SeekBar
                        android:id="@+id/sb_vol_seekbar"
                        android:layout_width="143dp"
                        android:layout_height="5dp"
                        android:layout_marginLeft="5dp"
                        android:indeterminate="false"
                        android:max="100"
                        android:progress="20"
                        android:progressDrawable="@drawable/seekbar_style_pro"
                        android:visibility="gone"/>

                    <View
                        android:id="@+id/v_line"
                        android:layout_width="1dp"
                        android:layout_height="match_parent"
                        android:layout_marginBottom="5dp"
                        android:layout_marginLeft="5dp"
                        android:layout_marginTop="5dp"
                        android:background="#1E1E1E"
                        android:visibility="gone"></View>
                    <!--横竖屏切换-->
                    <Button
                        android:id="@+id/bt_switch"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:text="Switch"/>
                </LinearLayout>
            </RelativeLayout>
        </LinearLayout>
    </RelativeLayout>
</RelativeLayout>
<!--seekbar_style_pro:-->

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@android:id/background">
        <shape>
            <solid android:color="#707070"></solid>
            <size android:height="5dp"></size>
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="#B94310"></solid>
                <size android:height="5dp"></size>
            </shape>
        </clip>
    </item>
</layer-list>
<!--seekbar_style_vol-->

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@android:id/background">
        <shape>
            <solid android:color="#101010"></solid>
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="#ffb97244"></solid>
            </shape>
        </clip>
    </item>
</layer-list>

7. 删除以下MediaController的逻辑:

/**
     * videoView和MediaController绑定
     */
    private void initBind() {
        MediaController mediaController = new MediaController(this);
        mVvVideoView.setMediaController(mediaController);
        mediaController.setMediaPlayer(mVvVideoView);
    }

8. 初始化控件:

//需要竖屏隐藏的音量title
    @BindView(R.id.tv_vol_name)
    TextView mTvVolName;
    //徐奥竖屏隐藏的音量分割线
    @BindView(R.id.v_line)
    //最外层的布局
    @BindView(R.id.rl_videolayout)
    RelativeLayout mRlVideolayout;
    //VideoView
    @BindView(R.id.vv_videoView)
    VideoView mVvVideoView;
    //进程进度条
    @BindView(R.id.sb_progress_seekbar)
    SeekBar mSbProgressSeekbar;
    //播放 暂停
    @BindView(R.id.bt_start_pause)
    Button mBtStartPause;
    //现在的时间
    @BindView(R.id.tv_time_current)
    TextView mTvTimeCurrent;
    //总共的时间
    @BindView(R.id.tv_time_total)
    TextView mTvTimeTotal;
    //音量进度条
    @BindView(R.id.sb_vol_seekbar)
    SeekBar mSbVolSeekbar;
    //全屏切换开关
    @BindView(R.id.bt_switch)
    Button mBtSwitch;
    //控制区域
    @BindView(R.id.ll_controllerBar_layout)
    LinearLayout mLlControllerBarLayout;
    //控制区域左半边
    @BindView(R.id.ll_left_layout)
    LinearLayout mLlLeftLayout;
    //控制区域右半边
    @BindView(R.id.ll_right_layout)
    LinearLayout mLlRightLayout;

9. 播放和暂停逻辑

//控制视频的播放和暂停
case R.id.bt_start_pause:
     if (mVvVideoView.isPlaying()) {
             mBtStartPause.setText("Start");
             mVvVideoView.pause();
        } else {
             mBtStartPause.setText("Pause");
             mVvVideoView.start();
        }
     break;

10. 定义格式时间的方法

/**
     * 时间的格式化
     * @param textView
     * @param millisecond
     */
    public void updateTime(TextView textView, int millisecond) {
        int second = millisecond / 1000; //总共换算的秒
        int hh = second / 3600;  //小时
        int mm = second % 3600 / 60; //分钟
        int ss = second % 60; //时分秒中的秒的得数

        String str = null;
        if (hh != 0) {
            //如果是个位数的话,前面可以加0  时分秒
            str = String.format("%02d:%02d:%02d", hh, mm, ss);
        } else {
            str = String.format("%02d:%02d", mm, ss);
        }
        textView.setText(str);
    }

11. 自动刷新并设置当前视频时间和视频总时间及同步SeekBar进度

//刷新机制的标志
    private static final int UPDATE_UI = 1;
     /**
     * 定义Handler刷新时间
     * 得到并设置当前视频播放的时间
     * 得到并设置视频播放的总时间
     * 设置SeekBar总进度和当前视频播放的进度
     * 并反复执行Handler刷新时间
     * 指定标识用于关闭Handler
     */
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == UPDATE_UI) {

                int currentPosition = mVvVideoView.getCurrentPosition();
                int totalduration = mVvVideoView.getDuration();

                updateTime(mTvTimeCurrent, currentPosition);
                updateTime(mTvTimeTotal, totalduration);

                mSbProgressSeekbar.setMax(totalduration);
                mSbProgressSeekbar.setProgress(currentPosition);

                mHandler.sendEmptyMessageDelayed(UPDATE_UI, 500);

            }
        }
    };

12. 初始化播放和刷新时间机制

private void initVideoPlay() {
        mVvVideoView.start();
        //第一个参数是标志,第二个参数是刷新间隔时间
        mHandler.sendEmptyMessageDelayed(UPDATE_UI, 500);
 }

13. 适时关闭和开启刷新机制

@OnClick(R.id.bt_start_pause)
    public void onViewClicked(View view) {
        switch (view.getId()) {
            //控制视频的播放和暂停
            case R.id.bt_start_pause:
                if (mVvVideoView.isPlaying()) {
                    mBtStartPause.setText("Start");
                    mVvVideoView.pause();
                    //停止刷新UI
                    mHandler.removeMessages(UPDATE_UI);
                } else {
                    mBtStartPause.setText("Pause");
                    mVvVideoView.start();
                    //开启刷新UI
                    mHandler.sendEmptyMessage(UPDATE_UI);
                }
                break;
        }
    }
@Override
    protected void onPause() {
        super.onPause();
        //停止刷新UI
        mHandler.removeMessages(UPDATE_UI);
    }

14. 拖动SeekBar同步SeekBar和Time和VideoView

private void synchScrollSeekBarAndTime() {
        mSbProgressSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            //进度改变的时候同步Time
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                updateTime(mTvTimeCurrent, progress);
            }

            //拖动的时候关闭刷新机制
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                mHandler.removeMessages(UPDATE_UI);
            }

            //拖动停止同步VideoView和开启刷新机制
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int progress = seekBar.getProgress();
                mVvVideoView.seekTo(progress);
                mHandler.sendEmptyMessage(UPDATE_UI);
            }
        });
    }

15. 自动横竖屏切换

  • 删掉强制横屏的代码:
android:screenOrientation="landscape"
  • 防止横竖屏切换重建Activity:配置文件添加
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
  • 防止横屏的时候视频右边有大量的空白:
//定义两个变量:代表当前屏幕的宽和屏幕的高
    private int screen_width, screen_height;

    /**
     * 获取屏幕的宽和屏幕的高
     */
    private void initScreenWidthAndHeight() {
        screen_width = getResources().getDisplayMetrics().widthPixels;
        screen_height = getResources().getDisplayMetrics().heightPixels;
    }

    /**
     * 设置VideoView和最外层相对布局的宽和高
     * @param width : 像素的单位
     * @param height : 像素的单位
     */
    private void setVideoViewScale(int width, int height) {
        //获取VideoView宽和高
        ViewGroup.LayoutParams layoutParams = mVvVideoView.getLayoutParams();
        //赋值给VideoView的宽和高
        layoutParams.width = width;
        layoutParams.height = height;
        //设置VideoView的宽和高
        mVvVideoView.setLayoutParams(layoutParams);

        //同上
        ViewGroup.LayoutParams layoutParams1 = mRlVideolayout.getLayoutParams();
        layoutParams.width = width;
        layoutParams.height = height;
        mRlVideolayout.setLayoutParams(layoutParams1);
    }

   /**
     * 监听屏幕方向的改变,横竖屏的时候分别做处理
     *
     * @param newConfig
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        //当屏幕方向是横屏的时候,我们应该对VideoView以及包裹VideoView的布局(也就是对整体)进行拉伸
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
        }
        //当屏幕方向是竖屏的时候,竖屏的时候的高我们需要把dp转为px
        else {
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT,DensityUtils.dip2px(this,240));
        }
    }

    /**
 * Created by FuKaiqiang on 2018-01-06.
 */

public class DensityUtils {
    /**
     * 根据手机的分辨率从 dip 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 将px值转换为sp值,保证文字大小不变
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * 将sp值转换为px值,保证文字大小不变
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

}

16. 音量

//初始化音频管理器
    private AudioManager mAudioManager;
    /**
     * 初始化音频管理器;获取设备最大音量和当前音量并设置
     */
    private void initAudioManager() {
        mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        mSbVolSeekbar.setMax(streamMaxVolume);
        mSbVolSeekbar.setProgress(streamVolume);
    }

17. 手动横竖屏切换

//  定义一个横竖屏切换的变量
    private boolean isFullScreen = false;
   //  根据横竖屏的变化设置变量值
 /**
     * 监听屏幕方向的改变,横竖屏的时候分别做处理
     *
     * @param newConfig
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        //当屏幕方向是横屏的时候,我们应该对VideoView以及包裹VideoView的布局(也就是对整体)进行拉伸
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            //横屏的时候显示
            mTvVolName.setVisibility(View.VISIBLE);
            mVLine.setVisibility(View.VISIBLE);
            mSbVolSeekbar.setVisibility(View.VISIBLE);
            //横屏的时候为true
            isFullScreen = true;
        }
        //当屏幕方向是竖屏的时候,竖屏的时候的高我们需要把dp转为px
        else {
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, DensityUtils.dip2px(this, 240));
            //竖屏的时候吟唱
            mTvVolName.setVisibility(View.GONE);
            mVLine.setVisibility(View.GONE);
            mSbVolSeekbar.setVisibility(View.GONE);
            //竖屏的时候为
            isFullScreen = false;
        }
    }
            //手动横竖屏切换
            case R.id.bt_switch:
                if (isFullScreen) {
                    //切换为竖屏
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                } else {
                    //切换为横屏
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                }
                break;

18. 自定义VideoView

  • 虽然可以实现了最外面的相对布局在横屏的时候全屏,但是VideoView并未全屏,原因是视频的宽度和高度并没有手机屏幕那么大,所以这个时候依然在VideoView宽留有空白区域,这个时候就应该自定义VideoView:
/**
 * Created by FuKaiqiang on 2018-01-06.
 */

public class MyVideoView extends VideoView {

    private int screen_width = 1920;
    private int screen_height = 1080;

    public MyVideoView(Context context) {
        super(context);
    }

    public MyVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //得到手机屏幕的宽和高
        DisplayMetrics dm = new DisplayMetrics();
        dm = getResources().getDisplayMetrics();
        int screenWidth = dm.widthPixels; // 屏幕宽(像素,如:3200px)
        int screenHeight = dm.heightPixels; // 屏幕高(像素,如:1280px)
        //最大限度的展示宽和高
        int width = getDefaultSize(screen_width, widthMeasureSpec);
        int height = getDefaultSize(screen_height, heightMeasureSpec);

        setMeasuredDimension(width, height);
    }
}
    <com.best.testvideoview.MyVideoView
            android:id="@+id/vv_videoView"
            android:layout_width="match_parent"
            android:layout_height="240dp"
            />
onConfigurationChanged方法中:
 //当横屏时主动取消半屏,该设置为全屏
 getWindow().clearFlags((WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN));
 getWindow().addFlags((WindowManager.LayoutParams.FLAG_FULLSCREEN));
//当竖屏时主动取消全屏,该设置为半屏
getWindow().clearFlags((WindowManager.LayoutParams.FLAG_FULLSCREEN));
getWindow().addFlags((WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN));

19. 手势调节音量和亮度

/**
     * 初始化手势
     */
    private void initGesture() {
        mVvVideoView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //现在的x,y坐标
                float x = event.getX();
                float y = event.getY();

                switch (event.getAction()) {
                    //手指按下:
                    case MotionEvent.ACTION_DOWN:
                        lastX = x;
                        lastY = y;
                        break;
                    //手指移动:
                    case MotionEvent.ACTION_MOVE:
                        //偏移量
                        float moveX = x - lastX;
                        float moveY = y - lastY;
                        //计算绝对值
                        float absMoveX = Math.abs(moveX);
                        float absMoveY = Math.abs(moveY);
                        //手势合法性的验证
                        if (absMoveX > Num && absMoveY > Num) {
                            if (absMoveX < absMoveY) {
                                isEMove = true;
                            } else {
                                isEMove = false;
                            }
                        } else if (absMoveX < Num && absMoveY > Num) {
                            isEMove = true;
                        } else if (absMoveX > Num && absMoveY < Num) {
                            isEMove = false;
                        }
                        /**
                         * 区分手势合法的情况下,区分是去调节亮度还是去调节声音
                         */
                        if (isEMove) {
                            //手势在左边
                            if (x < screen_width / 2) {
                                /**
                                 * 调节亮度
                                 */
                                if (moveY > 0) {
                                    //降低亮度
                                } else {
                                    //升高亮度
                                }
                                changeBright(-moveY);
                                //手势在右边
                            } else {
                                Log.e("Emove", "onTouch: " + "手势在右边");
                                /**
                                 * 调节音量
                                 */
                                if (moveY > 0) {
                                    //减小音量
                                } else {
                                    //增大音量
                                }
                                changeVolume(-moveY);
                            }
                        }
                        lastX = x;
                        lastY = y;
                        break;
                    //手指抬起:
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return true;
            }
        });
    }
     /**
     * 调节音量:偏移量和音量值的换算
     */
    private void changeVolume(float moveY) {
        int max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int current = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        int index = (int) (moveY / screen_height * max * 3);
        int volume = Math.max(current + index, 0);
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
        mSbVolSeekbar.setProgress(volume);
    }

     /**
     * 调节亮度:
     */
    private void changeBright(float moveY) {
        WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
        mBrightness = layoutParams.screenBrightness;
        float index = moveY / screen_height / 3;
        mBrightness += index;
        //做临界值的判断
        if (mBrightness > 1.0f) {
            mBrightness = 1.0f;
        }
        if (mBrightness < 0.01) {
            mBrightness = 0.01f;
        }
        layoutParams.screenBrightness = mBrightness;
        getWindow().setAttributes(layoutParams);
    }

20. 亮度调节的显示

  • 定义一个名为layout_progress的布局,位置放在屏幕中央
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/fl_content"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerInParent="true"
              android:layout_marginTop="-50dp"
              android:background="#50000000"
              android:orientation="vertical"
              android:visibility="gone">
   // 放置音量或者亮度的图片
    <ImageView
        android:id="@+id/operation_bg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        >

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_vertical"
        android:layout_marginTop="20dp"
        android:paddingBottom="25dp">
        // 音量或者亮度进度条的背景--进度条拿布局来写的,不是seekbar--都Ok的
        <View
            android:layout_width="94dp"
            android:layout_height="4dp"
            android:layout_gravity="left"
            android:background="#000000"
            android:scaleType="fitXY"
            />
       // 音量或者亮度进度条的进度--进度条拿布局来写的,不是seekbar--都Ok的
        <View
            android:id="@+id/operation_percent"
            android:layout_width="94dp"
            android:layout_height="4dp"
            android:layout_gravity="left"
            android:background="#FFF"
            android:scaleType="fitXY"/>
    </FrameLayout>
</LinearLayout>
  <include layout="@layout/layout_progress"></include>

      @BindView(R.id.operation_percent)
      View mOperationPercent;
      @BindView(R.id.fl_content)
      LinearLayout mFlContent;
  • 调节音量方法changeVolume中添加:
if (mFlContent.getVisibility()==View.GONE) mFlContent.setVisibility(View.VISIBLE);
        mOperationBg.setImageResource(R.mipmap.ic_vol);
        ViewGroup.LayoutParams layoutParams = mOperationPercent.getLayoutParams();
        layoutParams.width = (int) (DensityUtils.dip2px(this, 94) * (float) volume / max);
        mOperationPercent.setLayoutParams(layoutParams);
  • 调节亮度方法中添加:
if (mFlContent.getVisibility()==View.GONE) mFlContent.setVisibility(View.VISIBLE);
        mOperationBg.setImageResource(R.mipmap.bright);
        ViewGroup.LayoutParams layoutParams = mOperationPercent.getLayoutParams();
        layoutParams.width = (int) (DensityUtils.dip2px(this, 94) *mBrightness);
        mOperationPercent.setLayoutParams(layoutParams);

21. 不足:

  • 音量的手势调节并非像亮度那般流畅,后续会优化这一部分。
  • 横屏的时候,屏幕的高并没有填充整个屏幕。

22. Github下载地址,欢迎Star