相关文章推荐
儒雅的针织衫  ·  【VS2019 C++】"const ...·  5 月前    · 
斯文的烈马  ·  How to convert a ...·  1 年前    · 
想旅行的遥控器  ·  boost ...·  1 年前    · 
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/viewBox"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="beforeDescendants">
    <com.qiangyu.test.commonvideoview.MyVideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
     //底部状态栏
    <LinearLayout android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="#CC282828"
        android:padding="3dip"
        android:id="@+id/videoControllerLayout"
        android:gravity="center"
        android:layout_gravity="bottom">
        <LinearLayout android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:id="@+id/videoPauseBtn"
            android:paddingRight="10dip"
            android:paddingLeft="10dp">
            <ImageView android:layout_width="22dp"
                android:layout_height="22dp"
                android:id="@+id/videoPauseImg" />
        </LinearLayout>
        <LinearLayout android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:orientation="horizontal"
            android:paddingRight="0dip">
            <SeekBar android:layout_width="fill_parent"
                android:id="@+id/videoSeekBar"
                android:layout_weight="1"
                style="@android:style/Widget.Holo.SeekBar"
                android:layout_height="wrap_content"/>
            <TextView android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:gravity="center"
                android:text="00:00"
                android:textSize="12dp"
                android:id="@+id/videoCurTime"
                android:textColor="#FFF"
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center"
                android:textSize="12dp"
                android:textColor="#FFF"
                android:text="/"/>
            <TextView android:layout_width="wrap_content"
                android:layout_height="fill_parent"
                android:gravity="center"
                android:text="00:00"
                android:textSize="12dp"
                android:id="@+id/videoTotalTime"
                android:textColor="#FFF"
                android:layout_marginRight="10dp"
        </LinearLayout>
        <LinearLayout
            android:id="@+id/screen_status_btn"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center">
            <ImageView
                android:id="@+id/screen_status_img"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@mipmap/iconfont_enter_32"/>
        </LinearLayout>
    </LinearLayout>
    //VideoVIEW中间的开始按钮和进度条以及快进快退的提示
    <ProgressBar android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:id="@+id/progressBar"
        style="@android:style/Widget.Holo.ProgressBar.Small"/>
    <ImageView android:layout_width="30dip"
        android:layout_height="30dip"
        android:id="@+id/videoPlayImg"
        android:layout_gravity="center"
        android:src="@mipmap/video_box_play"/>
    <LinearLayout
        android:id="@+id/touch_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center"
        android:visibility="invisible"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:background="#000">
        <ImageView android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:id="@+id/touchStatusImg"/>
        <TextView
            android:id="@+id/touch_time"
            android:layout_width="wrap_content"
            android:text="25:00/59:00"
            android:textSize="12sp"
            android:textColor="#fff"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</FrameLayout>

上面的布局很简单,VideoView用了自定义是因为当布局改变的时候,要让VideoView重新获取布局位置,在里面设置它的分辨率为全屏.VideoView的代码如下

public class MyVideoView extends VideoView {
    public MyVideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    public MyVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
    public MyVideoView(Context context) {
        super(context);
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = getDefaultSize(0, widthMeasureSpec);
        int height = getDefaultSize(0, heightMeasureSpec);
        this.getHolder().setFixedSize(width,height);//设置分辨率
        setMeasuredDimension(width, height);

好,布局写好了我来第二步,获取内部控件初始化事件

2、第二步,onFinishInflate()中得到内部的控件,做初始化工作

onFinishInflate方法在xml解析完毕的时候会回调该方法,一般在做组合控件的时候最常用

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        initView();
private void initView() {
        View view = LayoutInflater.from(context).inflate(R.layout.common_video_view,null);
        viewBox = (FrameLayout) view.findViewById(R.id.viewBox);
        videoView = (MyVideoView) view.findViewById(R.id.videoView);
        videoPauseBtn = (LinearLayout) view.findViewById(R.id.videoPauseBtn);
        screenSwitchBtn = (LinearLayout) view.findViewById(R.id.screen_status_btn);
        videoControllerLayout = (LinearLayout) view.findViewById(R.id.videoControllerLayout);
        touchStatusView = (LinearLayout) view.findViewById(R.id.touch_view);
        touchStatusImg = (ImageView) view.findViewById(R.id.touchStatusImg);
        touchStatusTime = (TextView) view.findViewById(R.id.touch_time);
        videoCurTimeText = (TextView) view.findViewById(R.id.videoCurTime);
        videoTotalTimeText = (TextView) view.findViewById(R.id.videoTotalTime);
        videoSeekBar = (SeekBar) view.findViewById(R.id.videoSeekBar);
        videoPlayImg = (ImageView) view.findViewById(R.id.videoPlayImg);
        videoPlayImg.setVisibility(GONE);
        videoPauseImg = (ImageView) view.findViewById(R.id.videoPauseImg);
        progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
        videoPauseBtn.setOnClickListener(this);
        videoSeekBar.setOnSeekBarChangeListener(this);
        videoPauseBtn.setOnClickListener(this);
        videoView.setOnPreparedListener(this);
        videoView.setOnCompletionListener(this);
        screenSwitchBtn.setOnClickListener(this);
        videoPlayImg.setOnClickListener(this);
        //注册在设置或播放过程中发生错误时调用的回调函数。如果未指定回调函数,或回调函数返回false,VideoView 会通知用户发生了错误。
        videoView.setOnErrorListener(this);
        viewBox.setOnTouchListener(this);
        viewBox.setOnClickListener(this);
        addView(view);

很简单的做了代码初始化和videoView的播放事件的注册,这里的事件待会要处理的有viewBox.setOnTouchListener(this)注册的onTouch事件,我们要在里面写左右滑动快进快退的效果,videoSeekBar.setOnSeekBarChangeListener(this);处理拖动seekbar快进快退。

3、第三步,事件、效果处理

viewBox.setOnTouchListener(this);

1、onTouch里的实现快进快退

@Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //没播放的时候不处理
                if (!videoView.isPlaying()){
                    return false;
                float downX =  event.getRawX();
                touchLastX = downX;
                Log.d("FilmDetailActivity", "downX" + downX);
                //保存当前播放的位置用与做事件显示
                this.position = videoView.getCurrentPosition();
                break;
            case MotionEvent.ACTION_MOVE:
                //没播放的时候不处理
                if (!videoView.isPlaying()){
                    return false;
                float currentX =  event.getRawX();
                float deltaX = currentX - touchLastX;
                float deltaXAbs  =  Math.abs(deltaX);
                if (deltaXAbs>1){//正向移动,快进
                    if (touchStatusView.getVisibility()!=View.VISIBLE){
                        touchStatusView.setVisibility(View.VISIBLE);
                        //显示快进的时间view
                    touchLastX = currentX;
                    Log.d("FilmDetailActivity","deltaX"+deltaX);
                    if (deltaX > 1) {
                        position += touchStep;
                        if (position > duration) {
                            position = duration;
                        touchPosition = position;
                        touchStatusImg.setImageResource(R.mipmap.ic_fast_forward_white_24dp);
                        int[] time = getMinuteAndSecond(position);
                        touchStatusTime.setText(String.format("%02d:%02d/%s", time[0], time[1],formatTotalTime));
                    } else if (deltaX < -1) {//快退
                        position -= touchStep;
                        if (position < 0) {
                            position = 0;
                        touchPosition = position;
                        touchStatusImg.setImageResource(R.mipmap.ic_fast_rewind_white_24dp);
                        int[] time = getMinuteAndSecond(position);
                        touchStatusTime.setText(String.format("%02d:%02d/%s", time[0], time[1],formatTotalTime));
                        //mVideoView.seekTo(position);
                break;
            case MotionEvent.ACTION_UP:
                if (touchPosition!=-1){
                    videoView.seekTo(touchPosition);
//放开手指的时候快进或快退到滑动决定的时间位置                    touchStatusView.setVisibility(View.GONE);
                    touchPosition = -1;
                    if (videoControllerShow){
                        return true;
                break;
        return false;

2、处理 seekBar的拖动事件

@Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        int[] time = getMinuteAndSecond(progress);
        videoCurTimeText.setText(String.format("%02d:%02d", time[0], time[1]));
        //设置底部时间显示
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        videoView.pause();
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        videoView.seekTo(videoSeekBar.getProgress());
        videoView.start();
        videoPlayImg.setVisibility(View.INVISIBLE);
        videoPauseImg.setImageResource(R.mipmap.icon_video_pause);
       //拖动之后到指定的时间位置

3、videoView的回调事件

@Override
    public void onPrepared(MediaPlayer mp) {
        duration = videoView.getDuration();
        int[] time = getMinuteAndSecond(duration);
        videoTotalTimeText.setText(String.format("%02d:%02d", time[0], time[1]));
        formatTotalTime = String.format("%02d:%02d", time[0], time[1]);
        videoSeekBar.setMax(duration);
        progressBar.setVisibility(View.GONE);
        mp.start();
        videoPauseBtn.setEnabled(true);
        videoSeekBar.setEnabled(true);
        videoPauseImg.setImageResource(R.mipmap.icon_video_pause);
        timer.schedule(timerTask, 0, 1000);
            //初始化总时间等一些界面显示,同时用timer定时去修改时间进度textView
    @Override
    public void onCompletion(MediaPlayer mp) {
        videoView.seekTo(0);
        videoSeekBar.setProgress(0);
        videoPauseImg.setImageResource(R.mipmap.icon_video_play);
        videoPlayImg.setVisibility(View.VISIBLE);

还有一些其它的点击时间就不放了,都是暂停,播放,点击显示隐藏底部状态栏,全屏切换等的事件。到了这里我们的事件处理完毕啦,接下来就要我们视频怎么播放呢?为了播放我们为外部提供一个方法

//开始播放
public void start(String url){
        videoPauseBtn.setEnabled(false);
        videoSeekBar.setEnabled(false);
        videoView.setVideoURI(Uri.parse(url));
        videoView.start();
//进入全屏时候调用
public void setFullScreen(){
        touchStatusImg.setImageResource(R.mipmap.iconfont_exit);
        this.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        videoView.requestLayout();
//退出全屏时候调用
    public void setNormalScreen(){
        touchStatusImg.setImageResource(R.mipmap.iconfont_enter_32);
        this.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,400));
        videoView.requestLayout();

上面提供的setFullScreen()和setNormalScreen()需要在Activity的 onConfigurationChanged(Configuration newConfig)横竖屏发生改变的 回调方法里面调用,还需要注意的是我这里写的是LinearLayout的LayoutParams,所以我们自定义的view的父空间要是LinearLayout,当然你也可以修改。

4、控件的使用

我们只需要在获得空间调用start方法,然后在onConfigurationChanged方法里调用setFullScreen和setNormalScreen就可以了,

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.qiangyu.test.commonvideoview.MainActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay"/>
    <com.qiangyu.test.commonvideoview.CommonVideoView
        android:id="@+id/common_videoView"
        android:layout_width="match_parent"
        android:layout_height="300dp" />
</LinearLayout>

activity代码

public class MainActivity extends AppCompatActivity {
    CommonVideoView videoView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.content_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        videoView = (CommonVideoView) findViewById(R.id.common_videoView);
        videoView.start("你的服务器视频地址");
    @Override public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            videoView.setFullScreen();
        }else {
            videoView.setNormalScreen();

最后为了防止你的Activity在横竖屏切换的时候重新创建别忘记在AndroidManifest.xml文件里面配置
android:configChanges=”orientation|screenSize|screenLayout”, 如果你这里有疑惑可以参考我的文章–>深入了解Activity-生命周期

<activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar"
            android:configChanges="orientation|screenSize|screenLayout">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

好了,整个控件到这里就完成了,源码下载请戳它—>欢迎戳我

如果你觉得有对你有帮助,请动动手给个赞吧,有问题记得反馈给我哦!