Android 简单定制一个视频播放器

安卓系统提供了VideoView用来播放一些特定格式的视频,与MediaController结合使用可以对视频播放进行简单控制 例如: 在布局文件中先声明个VideoView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content_main2"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

</RelativeLayout>

然后,在存储卡的根目录下先放置一个命名为“00.MP4”的视频文件

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        VideoView videoView = (VideoView) findViewById(R.id.videoView);
        MediaController mediaController = new MediaController(this);
        videoView.setMediaController(mediaController);
        mediaController.setMediaPlayer(videoView);
        //为videoView设置视频路径
        String path = Environment.getExternalStorageDirectory().getAbsolutePath();
        videoView.setVideoPath(path + "/00.mp4");
    }

播放效果如下:

这里写图片描述

这里再来自定义视频播放控制界面与控制逻辑,增添音量调节,亮度调节,沉浸式状态栏等功能

竖屏状态效果如下:

这里写图片描述

横屏状态下效果如下:

这里写图片描述

首先要先设计布局样式

<?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">

    <RelativeLayout
        android:id="@+id/rl_video"
        android:layout_width="match_parent"
        android:layout_height="240dp">

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

        <LinearLayout
            android:id="@+id/ll_control"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/vv_player"
            android:background="#8768423e"
            android:orientation="vertical">

            <SeekBar
                android:id="@+id/sb_play"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:indeterminate="false" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_horizontal">

                <LinearLayout
                    android:id="@+id/ll_playControl"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="5dp"
                    android:layout_marginLeft="5dp"
                    android:gravity="center"
                    android:orientation="horizontal">

                    <ImageView
                        android:id="@+id/iv_playControl"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:clickable="true"
                        android:src="@drawable/play_btn_style" />

                    <TextView
                        android:id="@+id/tv_currentTime"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="7dp"
                        android:text="00:00:00"
                        android:textColor="#ffffff"
                        android:textSize="15sp" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text=" / "
                        android:textColor="#ffffff"
                        android:textSize="15sp" />

                    <TextView
                        android:id="@+id/tv_totalTime"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="00:00:00"
                        android:textColor="#ef6363"
                        android:textSize="15sp" />

                </LinearLayout>

                <ImageView
                    android:id="@+id/iv_screenSwitch"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_marginRight="5dp"
                    android:src="@drawable/full_screen" />

                <LinearLayout
                    android:id="@+id/ll_volumeControl"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="10dp"
                    android:layout_toLeftOf="@id/iv_screenSwitch"
                    android:gravity="end"
                    android:orientation="horizontal"
                    android:visibility="gone">

                    <ImageView
                        android:id="@+id/iv_volume"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:src="@drawable/volume" />

                    <SeekBar
                        android:id="@+id/sb_volume"
                        android:layout_width="150dp"
                        android:layout_height="wrap_content"
                        android:indeterminate="false" />

                </LinearLayout>

            </RelativeLayout>

        </LinearLayout>
    </RelativeLayout>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

id为“ll_control”的LinearLayout包含了所有的控制View,将之置于VideoView上且设置半透明背景色,音量调节seekBar在竖屏状态下不可见

主要代码如下: 初始化UI

private void initUI() {
        videoView = (VideoView) findViewById(R.id.vv_player);
        sb_play = (SeekBar) findViewById(R.id.sb_play);
        sb_volume = (SeekBar) findViewById(R.id.sb_volume);
        iv_playControl = (ImageView) findViewById(R.id.iv_playControl);
        iv_screenSwitch = (ImageView) findViewById(R.id.iv_screenSwitch);
        iv_volume = (ImageView) findViewById(R.id.iv_volume);
        tv_currentTime = (TextView) findViewById(R.id.tv_currentTime);
        tv_totalTime = (TextView) findViewById(R.id.tv_totalTime);
        ll_volumeControl = (LinearLayout) findViewById(R.id.ll_volumeControl);
        ll_control = (LinearLayout) findViewById(R.id.ll_control);
        rl_video = (RelativeLayout) findViewById(R.id.rl_video);
        sb_volume.setMax(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
        sb_volume.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
    }

初始化各种事件

private void initEvent() {
        iv_playControl.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (videoView.isPlaying()) {
                    setPauseStatus();
                    videoView.pause();
                    uiHandler.removeMessages(UPDATE_TIME);
                } else {
                    setPlayStatus();
                    videoView.start();
                    uiHandler.sendEmptyMessage(UPDATE_TIME);
                }
            }
        });
        sb_play.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser) {
                    videoView.seekTo(progress);
                    Utils.updateTimeFormat(tv_currentTime, progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                uiHandler.removeMessages(UPDATE_TIME);
                if (!videoView.isPlaying()) {
                    setPlayStatus();
                    videoView.start();
                }
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                uiHandler.sendEmptyMessage(UPDATE_TIME);
            }
        });
        sb_volume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
        videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                iv_playControl.setImageResource(R.drawable.play_btn_style);
                videoView.seekTo(0);
                sb_play.setProgress(0);
                Utils.updateTimeFormat(tv_currentTime, 0);
                videoView.pause();
                uiHandler.removeMessages(UPDATE_TIME);
            }
        });
        iv_screenSwitch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                    iv_screenSwitch.setImageResource(R.drawable.exit_full_screen);
                } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                    iv_screenSwitch.setImageResource(R.drawable.full_screen);
                }
            }
        });
        videoView.setOnTouchListener(this);
    }

在横屏和竖屏切换时,会回调以下方法

public void onConfigurationChanged(Configuration newConfig)

在此要对View的大小进行调整以适应屏幕

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        screenWidth = getResources().getDisplayMetrics().widthPixels;
        screenHeight = getResources().getDisplayMetrics().heightPixels;
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            setSystemUiHide();
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            iv_screenSwitch.setImageResource(R.drawable.exit_full_screen);
            ll_volumeControl.setVisibility(View.VISIBLE);
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, Utils.dp2px(MainActivity.this, 240f));
            iv_screenSwitch.setImageResource(R.drawable.full_screen);
            ll_volumeControl.setVisibility(View.GONE);
            setSystemUiVisible();
        }
    }

View大小调节

 /**
     * 设置布局大小
     *
     * @param width  宽度
     * @param height 高度
     */
    private void setVideoViewScale(int width, int height) {
        ViewGroup.LayoutParams params = rl_video.getLayoutParams();
        params.width = width;
        params.height = height;
        rl_video.setLayoutParams(params);
        ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
        layoutParams.width = width;
        layoutParams.height = height;
        videoView.setLayoutParams(layoutParams);
    }

此外,为了使视频在全屏播放时更加和谐,调用以下方法对系统状态栏和虚拟按键进行隐藏与显示

private void setSystemUiHide() {
        if (Build.VERSION.SDK_INT >= 19) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
        }
    }

    private void setSystemUiVisible() {
        if (Build.VERSION.SDK_INT >= 19) {
            View decorView = getWindow().getDecorView();
            decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
        }
    }

此外,通过手势识别可以对亮度和音量进行调节

private void changeVolume(float offset) {
        int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        int index = (int) (offset / screenHeight * maxVolume);
        int volume = Math.max(currentVolume + index, 0);
        volume = Math.min(volume, maxVolume);
        audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
        sb_volume.setProgress(volume);
    }

    private void changeBrightness(float offset) {
        WindowManager.LayoutParams attributes = getWindow().getAttributes();
        float brightness = attributes.screenBrightness;
        float index = offset / screenHeight / 2;
        brightness = Math.max(brightness + index, WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF);
        brightness = Math.min(WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL, brightness);
        attributes.screenBrightness = brightness;
        getWindow().setAttributes(attributes);
    }

关于手势识别 OnGestureListener的使用在我的上一篇博客也介绍过了

此外,为了使seekBar的进度能够在用户通过音量键调节音量时也能自动变化,需要再加上一个广播接收器

/**
 * 音量变化广播接收器
 * Created by CZY on 2017/1/31.
 */
public class VolumeReceiver extends BroadcastReceiver {

    private ImageView iv_volume;

    private SeekBar seekBar_volume;

    /**
     * 音频管理器
     */
    private AudioManager audioManager;

    public VolumeReceiver(Context context, ImageView iv_volume, SeekBar seekBar_volume) {
        this.iv_volume = iv_volume;
        this.seekBar_volume = seekBar_volume;
        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
            int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
            if (volume == 0) {
                iv_volume.setImageResource(R.drawable.mute);
            } else {
                iv_volume.setImageResource(R.drawable.volume);
            }
            seekBar_volume.setProgress(volume);
        }
    }

}

为了在屏幕切换时可以不重新创建Activity而只是回调onConfigurationChanged 函数,可以为Activity增添以下属性

android:configChanges="orientation|screenSize|keyboard|keyboardHidden"

且程序要读取存储卡文件,需要申请权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

代码中使用到的图标我都是从一个公开图标库中下载的,个人感觉还是挺不错的 网址:http://iconfont.cn/collections

这里也提高源代码下载:简单定制一个视频播放器

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

java9系列(五)Stack-Walking API

java9新增这个类的目的是提供一个标准API用于访问当前线程栈,之前只有Throwable::getStackTrace、Thread::getStackTr...

411
来自专栏一个会写诗的程序员的博客

java.sql.SQLException: connection holder is null

java.sql.SQLException: connection holder is null

1341
来自专栏WOLFRAM

向日葵中的数学之美

1823
来自专栏码匠的流水账

聊聊hikari连接池的isAllowPoolSuspension

本文主要研究一下hikari连接池的isAllowPoolSuspension属性

1332
来自专栏码匠的流水账

聊聊HystrixThreadPool

hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/HystrixThreadPool.java

761
来自专栏一个会写诗的程序员的博客

java.base.jmod

/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/jmods$ jmod list java....

1112
来自专栏前端儿

Web 前端颜色值--字体--使用,整理整理

颜色值 CSS 颜色使用组合了红绿蓝颜色值 (RGB) 的十六进制 (hex) 表示法进行定义。对光源进行设置的最低值可以是 0(十六进制 00)。最高值是 2...

2152
来自专栏码匠的流水账

spring security reactive获取security context

本文主要研究下reactive模式下的spring security context的获取。

1742
来自专栏码匠的流水账

聊聊HystrixCommandExecutionHook

hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/strategy/executionhook/Hyst...

652
来自专栏跟着阿笨一起玩NET

c# 使用timer定时器操作,上次定时到了以后,下次还未执行完怎么处理

------解决方案-------------------------------------------------------- 开始的时候,禁用定时器,你...

2571

扫码关注云+社区