自定义VideoView的演进

编写简单自定义VideoView中尝试编写自己的VideoView实现类。这样对VideoView的实现机制有了一个比较深入的理解。经过整理发现,其实要自定义需求,还真不一定需要重新自己的VideoView实现。在本文中将原来的CustomerVideoView的方法全部删除,并继承VideoView,发现功能上没有什么不能实现的。继承的CustomerVideoView最后其实只剩下继承来的构造方法,也就是说直接使用VideoView也没问题。

这次演进,实现了自定义的播放控制条:

image

这个播放控制条,实际是替代了编写简单自定义VideoView中的MediaController。这样就可以自定义各种样式和风格的控制条界面了。

另外,本文示例中默认不出现播放控制条,当识别到横向手势的时候,才显示该滚动条,并且根据横向手势的x轴位移前进或者后退播放视频的位置。

如果手指单击视频,则停止播放,再单击继续播放。

CustomerVideoView已经简化到可以直接使用VideoView替代:

package com.easymorse.videoplayer;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.VideoView;

public class CustomerVideoView extends VideoView {

    private static String TAG = "customer.videoplayer";

    public CustomerVideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

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

}

 

保留自定义CustomerVideoView的目的是:

  • 以后可以通过覆盖方法增加更加个性化的功能;
  • 为什么要继承VideoView,是否能直接写自己的VideoView实现?这是一个问题,当然是可行的,比如mVideoPlayer就是这么干的,但是再查看VideoView源代码的时候,发现一些在Android源代码中公开的(public)类,在android sdk api中并没有,比如找不到android.media.Metadata类,调用MediaPlayer的时候也无法找到源代码中公开的(public)方法getMetadata方法,因此直接使用继承可以获得很多好处,间接使用未公开的api。

有关播放控制条的布局:

<RelativeLayout android:id="@+id/mediaControllerLayout"
    android:layout_height="55dip" android:layout_width="fill_parent"
    android:visibility="invisible" android:layout_alignParentBottom="true">
    <View android:background="#50878787" android:layout_width="fill_parent"
        android:layout_height="1dip" />
    <ImageButton android:id="@+id/playButton"
        android:layout_width="wrap_content" android:src="@drawable/pause_button_gray"
        android:layout_height="wrap_content" android:layout_marginRight="15.0dip"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true" />
    <RelativeLayout android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:layout_marginLeft="15.0dip"
        android:layout_marginRight="15.0dip" android:layout_centerVertical="true"
        android:layout_toLeftOf="@id/playButton">
        <SeekBar android:id="@+id/videoSeekBar" android:focusable="false"
            android:layout_width="fill_parent" android:layout_height="wrap_content"
            android:layout_marginBottom="5.0dip"
            android:layout_alignParentBottom="true" />
    </RelativeLayout>
</RelativeLayout>

在这里还能加入其他需要的信息,比如当前播放时间,总时间等等。SeekBar是使用默认样式的,可以指定自己的样式和thumb小图标。

为了实现横向手势指定播放进度功能,需要让activity实现OnGestureListener接口,以前写过一个简单的示例,编写android简单的手势切换视图示例,可先参考了解。

在OnGestureListener中主要实现了以下方法。

onFling:

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
        float velocityY) {
    mediaControllerLayout.setVisibility(View.VISIBLE);
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            mediaControllerLayout.setVisibility(View.INVISIBLE);
        }
    }, 1000);
    return false;
}

 

其实这个方法中的内容也可以实现在下面提到的onScroll方法中。onFling主要是处理有有关速度横向和纵向的手势。这里用来触发显示播放控制条界面。并且通过postDelayed方法在1秒钟后让控制条不可见。这里还可以改进,用动画来处理出现和消失的效果。

onSingleTapUp:

@Override
public boolean onSingleTapUp(MotionEvent e) {
    Toast.makeText(this, "taped", Toast.LENGTH_SHORT).show();
    if (videoView.isPlaying()) {
        videoView.pause();
    } else {
        videoView.start();
    }
    return false;
}

 

用来识别单击屏幕的手势,并做视频的暂停和继续播放。

onScroll:

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
        float distanceY) {
    if (distanceX > 0) {
        this.videoSeekBar
                .setProgress(this.videoSeekBar.getProgress() – 1);
    } else {
        this.videoSeekBar
                .setProgress(this.videoSeekBar.getProgress() + 1);
    }
    videoView.seekTo((int) (this.videoSeekBar.getProgress() * 1.0
            / videoSeekBar.getMax() * videoView.getDuration()));
    return false;
}

 

识别手势横向左右位移,这里实现的很简单,进度条根据动作固定的为进度条加1%或者减1%。视频再根据滚动条做调整。正式的代码,应该根据位移的长度来适当的定位滚动条的位置。

手势的监听器要实现,还需要两件事情。

实例化GestureDetector:

private GestureDetector gestureDetector;

this.gestureDetector = new GestureDetector(this);

覆盖Activity的onTouchEvent方法:

@Override
public boolean onTouchEvent(MotionEvent event) {
    return this.gestureDetector.onTouchEvent(event);
}

 

这样才会把触摸事件转到手势监听器。

另外,也可以手动拨动滚动条上的thumb,需要通过下面方法支持:

@Override
public void onProgressChanged(SeekBar seekBar,
        int progress, boolean fromUser) {
    if (fromUser) {
        videoView.seekTo((int) (progress * 1.0
                / seekBar.getMax() * videoView
                .getDuration()));
        seekBar.setProgress(progress);
    }
}

 

源代码见:

http://easymorse.googlecode.com/svn/tags/videoplayer-0.5/

进一步可以做的是:

  • 在播放器中保存用户播放视频中止时的播放位置,再次播放的时候从中止的位置播放;
  • 设置menu项,用户可以清除保存的位置,这样可以从头播放;
  • 屏幕的适配,目前是按照视频原生大小播放,可以增加拉伸适配全屏功能,并通过menu项切换;
  • 视频的横屏和竖屏切花;
  • 设置intent,这样可以在用户播放视频的时候,可以出现应用列表,用户可以选择android内置播放器以外的应用播放;
  • 做一个比较好的动画,在单击暂停的时候播放。
PDF下載    发送文章为PDF   

1 Comment to “自定义VideoView的演进”

  1. By virginie, 2011年08月31日 @ 18:51

    請問直接extends VideoView的方法, 要如何access到private member “mMediaPlayer” 呢?
    我想要自訂一個video view可以export MediaPlayer.onSeekCompletedListener, 該怎麼做才好呢? 如果重新寫一個, 就會遇到你說的android.media.Metadata的問題…唉唉…

这篇文章上的评论的 RSS feed TrackBack URI

Leave a Reply