Android自定义视频播放器

在我的博客Android控件–VideoView中介绍了如何用VideoView去播放本地视频和网络视频,不过我们播放视频是调用的是系统给我们的UI界面,所以这篇博客是介绍如何去定制自己的播放器。

Android自定义视频播放器_第1张图片

我们的UI布局就是实现成这个样子,在横屏的时候有音量的显示,竖屏的时候则会隐藏。另外还可以在画面上滑动实现音量和亮度的变化。我们先来实现布局样式。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ht.videoplayerdemo.MainActivity" >

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

        <com.ht.videoplayerdemo.CustomVideoView
            android:id="@+id/video"
            android:layout_width="match_parent"
            android:layout_height="240dp" />

        <include layout="@layout/layout_progress"/>

        <LinearLayout
            android:id="@+id/controllerbar_layout"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:orientation="vertical" >

            <SeekBar
                android:id="@+id/play_seek"
                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_style2"
                android:thumb="@null" />

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

                <LinearLayout
                    android:id="@+id/left_layout"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:gravity="center_vertical"
                    android:orientation="horizontal" >

                    <ImageView
                        android:id="@+id/pause_img"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:src="@drawable/pause_btn_style" />

                    <TextView
                        android:id="@+id/time_current_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:text="00:00:00"
                        android:textColor="#ffffff"
                        android:textSize="14sp" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:text="/"
                        android:textColor="#4c4c4c"
                        android:textSize="14sp" />

                    <TextView
                        android:id="@+id/time_total_tv"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="5dp"
                        android:text="00:00:00"
                        android:textColor="#4c4c4c"
                        android:textSize="14sp" />
                LinearLayout>

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

                    <ImageView 
                        android:id="@+id/volume_img"
                        android:visibility="gone"
                        android:src="@drawable/sound"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"/>

                    <SeekBar 
                        android:id="@+id/volume_seek"
                        android:visibility="gone"
                        android:indeterminate="false"
                        android:thumb="@null"
                        android:progressDrawable="@drawable/seekbar_style"
                        android:progress="20"
                        android:max="100"
                        android:layout_width="100dp"
                        android:layout_height="5dp"/>

                    <View 
                        android:background="#1E1E1E"
                        android:layout_marginLeft="32dp"
                        android:layout_marginTop="5dp"
                        android:layout_marginBottom="5dp"
                        android:layout_width="1dp"
                        android:layout_height="match_parent"/>

                    <ImageView
                        android:id="@+id/screen_img"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="4dp"
                        android:layout_marginRight="4dp"
                        android:src="@drawable/expand" />

                LinearLayout>
            RelativeLayout>
        LinearLayout>
    RelativeLayout>
RelativeLayout>

最外层的RelativeLayout是整个布局,里面一层就是我们要完成的自定义播放器的layout,默认是竖屏所以起初高度是240dp,CustomVideoView是要自定义的VideoView,在显示上是和VideoView一样的。layout_progress是我们在触发滑动事件改变音量或亮度是出现的布局。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/progress_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:layout_marginTop="-50dp"
    android:alpha="0.5"
    android:background="#4e3d3e"
    android:orientation="vertical"
    android:visibility="gone">

    <ImageView
        android:id="@+id/operation_bg"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:layout_gravity="center"
        android:src="@drawable/v12" />

    <FrameLayout
        android:layout_marginTop="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:paddingBottom="25dp">

        <ImageView
            android:layout_width="120dp"
            android:layout_height="10dp"
            android:layout_gravity="left"
            android:scaleType="fitXY"
            android:src="#bcbcbc" />

        <ImageView
            android:id="@+id/operation_percent"
            android:layout_width="120dp"
            android:layout_height="10dp"
            android:layout_gravity="left"
            android:scaleType="fitXY"
            android:src="@drawable/operation_percent" />
    FrameLayout>
LinearLayout>

res/drawable/operation_percent.xml:


<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <corners android:radius="5dp"/>

    <solid android:color="#FFFFFF"/>

shape>

下面的LinearLayout就是我们的进度条和控制面板,seekBar是可拖动的进度条,下面的ImageView是控制暂停,几个TextView则是用来显示时间。右边的布局是音量和控制全屏的,在两者之间用View画了一条分隔线。

控制播放和暂停的Drawable分别是:


<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/ic_play_media_pressed" android:state_pressed="true">item>
    <item android:drawable="@drawable/ic_play_media_disable" android:state_enabled="false">item>
    <item android:drawable="@drawable/ic_play_media">item>

selector>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/ic_stop_media_pressed" android:state_pressed="true">item>
    <item android:drawable="@drawable/ic_stop_media_disable" android:state_enabled="false">item>
    <item android:drawable="@drawable/ic_stop_media">item>

selector>

进度条的Drawable如下:


<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">
        <shape>
            <solid android:color="#707070"/>
            <size android:height="5dp"/>
        shape>
    item>

    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid 
                    android:color="#ffb97244"/>
                <size 
                    android:height="5dp"/>
            shape>
        clip>
    item>

layer-list>

音量的seekBar的Drawable:


<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">
        <shape>
            <solid android:color="#101010"/>
        shape>
    item>

    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid 
                    android:color="#ffb97244"/>
            shape>
        clip>
    item>

layer-list>

这些图片的素材就大家自己去找吧,在网上是很好找的。

CustomVideoView的重写很简单,我们只要适配自己的手机重写onMeasure()方法即可。

public class CustomVideoView extends VideoView{

    int defaultWidth = 1920;
    int defaultHeight = 1080;

    public CustomVideoView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public CustomVideoView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    public CustomVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getDefaultSize(defaultWidth, widthMeasureSpec);
        int height = getDefaultSize(defaultHeight, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }
}

我的手机分辨率是1920 * 1080,所以设置全屏时宽高是这个大小。

public class MainActivity extends Activity {

    private CustomVideoView videoView;
    private LinearLayout controller_layout;
    private ImageView play_controller_img, screen_img, volume_img;
    private TextView time_current_tv, time_total_tv;
    private SeekBar play_seek, volume_seek;
    private int screen_width, screen_height;
    private RelativeLayout videoLayout;
    private AudioManager mAudioManager;
    private ImageView operation_bg, operation_percent;  
    private LinearLayout progress_layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        initUI();

        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "ht.mp4";

        videoView.setVideoPath(path);
        videoView.start();
    }

    private void initUI() {

        PixelUtil.initContext(this);
        videoView = (CustomVideoView) findViewById(R.id.video);
        controller_layout = (LinearLayout) findViewById(R.id.controllerbar_layout);
        play_controller_img = (ImageView) findViewById(R.id.pause_img);
        screen_img = (ImageView) findViewById(R.id.screen_img);
        volume_img = (ImageView) findViewById(R.id.volume_img);
        time_total_tv = (TextView) findViewById(R.id.time_total_tv);
        time_current_tv = (TextView) findViewById(R.id.time_current_tv);
        play_seek = (SeekBar) findViewById(R.id.play_seek);
        volume_seek = (SeekBar) findViewById(R.id.volume_seek);
        videoLayout = (RelativeLayout) findViewById(R.id.videoLayout);
        screen_width = getResources().getDisplayMetrics().widthPixels;
        screen_height = getResources().getDisplayMetrics().heightPixels;
        operation_bg = (ImageView) findViewById(R.id.operation_bg);
        operation_percent = (ImageView) findViewById(R.id.operation_percent);
        progress_layout = (LinearLayout) findViewById(R.id.progress_layout);

        /*
         * 当前设备的最大音量
         */
        int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        /*
         * 获取设备当前的音量
         */
        int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
        volume_seek.setMax(streamMaxVolume);
        volume_seek.setProgress(streamVolume);
    }
}

initUI()就是将我们在xml中定义的那些控件初始化,播放视频和设置音量。

private void updateTextViewWithTimeFormat(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) {
        str = String.format("%02d:%02d:%02d", hh, mm, ss);
    } else {
        str = String.format("%02d:%02d", mm, ss);
    }
    textView.setText(str);
}

从前面我们对布局的解析,我们知道有两个TextView,一个显示总时间,一个显示当前的播放时间。updateTextViewWithTimeFormat(TextView textView, int millisecond)就是我们对时间显示格式的处理,millisecond就是要转换格式的时间。

我们都知道播放时间是要随着播放进度变化而变化的,所以我们要有一个实时的方法来刷新我们的UI界面。我们这里是通过一个handler来反复的刷新界面的更新。

public static final int UPDATE_UI = 1;

private Handler UIhandler = new Handler() {

    public void handleMessage(android.os.Message msg) {
        super.handleMessage(msg);

        if (msg.what == UPDATE_UI) {
            // 获取视频当前的播放时间
            int currentPosition = videoView.getCurrentPosition();
            // 获取视频播放的总时间
            int totalDuration = videoView.getDuration();

            // 格式化视频播放时间
            updateTextViewWithTimeFormat(time_current_tv, currentPosition);
            updateTextViewWithTimeFormat(time_total_tv, totalDuration);

            play_seek.setMax(totalDuration);
            play_seek.setProgress(currentPosition);

            UIhandler.sendEmptyMessageDelayed(UPDATE_UI, 300);
        }
    };
};

这个handler就是实现当前时间的刷新,UPDATE_UI就是我们设置的msg.what,用来回调sendEmptyMessageDelayed()。当然我们还要在onCreate中调用一下UIhandler:

UIhandler.sendEmptyMessage(UPDATE_UI);

并且在我们暂停或者离开这个界面的时候应该把视频暂停,handler也停止才对,这里我们添加一个方法设置这个播放器的事件。

private void setPlayerEvent() {

        videoView.setOnCompletionListener(new OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mp) {
                // TODO Auto-generated method stub
                play_controller_img.setImageResource(R.drawable.play_btn_style);

            }
        });

        /*
         * 控制视频的暂停与播放
         */
        play_controller_img.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                if (videoView.isPlaying()) {
                    play_controller_img.setImageResource(R.drawable.play_btn_style);
                    // 暂停播放
                    videoView.pause();
                    UIhandler.removeMessages(UPDATE_UI);
                } else {
                    play_controller_img.setImageResource(R.drawable.pause_btn_style);
                    // 继续播放
                    videoView.start();
                    UIhandler.sendEmptyMessage(UPDATE_UI);
                }
            }
        });
}

我们先为VideoView设置了一个完成的触发事件,视频播放完后把暂停的那个图片设置为三角形的播放按钮。

后面设置的就是这个按钮的点击事件,如果正在播放就暂停移除handler,否则再重新调用handler,不要忘记在onCreate中调用哦。

如果切换了界面触发了onPause()方法,就要把视频变为暂停状态:

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
    play_controller_img.setImageResource(R.drawable.play_btn_style);
    // 暂停播放
    videoView.pause();
    UIhandler.removeMessages(UPDATE_UI);
}

处理完UI的更新,我们想到我们的进度条是一个seekBar,所以我们要为它的拖动写一个监听事件,我们在setPlayerEvent()方法中给它加上:

play_seek.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                // TODO Auto-generated method stub
                int progress = seekBar.getProgress();
                // 令视频播放进度遵循seekBar停止拖动这一刻的进度
                videoView.seekTo(progress);
                play_controller_img.setImageResource(R.drawable.pause_btn_style);
                videoView.start();
                UIhandler.sendEmptyMessage(UPDATE_UI);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                // TODO Auto-generated method stub
                UIhandler.removeMessages(UPDATE_UI);
            }

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                // TODO Auto-generated method stub
                updateTextViewWithTimeFormat(time_current_tv, progress);
            }
        });

它会让我们重写这三个方法,分别是开始拖动,进度条变化,停止拖动的时候触发。我们在开始的时候移除handler,在拖动的时候动态改变TextView的当前播放时间的值,结束时就将视频播放到拖动到的进度,让handler重新开始,这样我们的seekBar就可以在拖动的改变播放进度了。

是否全屏就是控制屏幕的横竖屏显示,因为我们在竖屏的时候为布局的宽高不是设置成自适应就是确切的值,那么在横屏的时候就会留下许多空白,所以我们要为横竖屏做宽高处理,这里我就不用两个布局文件啦。

首先我们在Manifest中设置configChanges:

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

在屏幕方向改变的时候,系统会调用onConfigurationChanged(),我们为它添加调用时的逻辑,isFullScreen为我们的全屏设置一个标记来控制我们点击图标时屏幕的状态,:

private boolean isFullScreen = false;

@Override
public void onConfigurationChanged(Configuration newConfig) {
    // TODO Auto-generated method stub
    super.onConfigurationChanged(newConfig);

    /*
     * 当屏幕方向为横屏的时候
     */
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {

        setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        volume_img.setVisibility(View.VISIBLE);
        volume_seek.setVisibility(View.VISIBLE);
        isFullScreen = true;
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }
    /*
     * 当屏幕方向为竖屏的时候
     */
    else {
        setVideoViewScale(ViewGroup.LayoutParams.MATCH_PARENT, PixelUtil.dp2px(240));
        volume_img.setVisibility(View.GONE);
        volume_seek.setVisibility(View.GONE);
        isFullScreen = false;
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }
}

setVideoViewScale(int width, int height)方法就是用来为控件设置宽高:

private void setVideoViewScale(int width, int height) {
    ViewGroup.LayoutParams layoutParams = videoView.getLayoutParams();
    layoutParams.width = width;
    layoutParams.height = height;
    videoView.setLayoutParams(layoutParams);

    ViewGroup.LayoutParams layoutParams2 = videoLayout.getLayoutParams();
    layoutParams2.width = width;
    layoutParams2.height = height;
    videoLayout.setLayoutParams(layoutParams2);
}

我们分别为VideoView和布局的那一层RelativeLayout设置LayoutParams。

PixelUtil就是进行单位转换的工具类。

public class PixelUtil {

    private static Context mContext;

    public static void initContext(Context context) {
        mContext = context;
    }

    public static int dp2px(float value) {
        final float scale = mContext.getResources().getDisplayMetrics().densityDpi;

        return (int) (value * (scale / 160) + 0.5f);
    }

    public static int dp2px(float value, Context context) {
        final float scale = mContext.getResources().getDisplayMetrics().densityDpi;

        return (int) (value * (scale / 160) + 0.5f);
    }

    public static int px2dp(float value) {
        final float scale = mContext.getResources().getDisplayMetrics().densityDpi;
        return (int) ((value * 160) / scale + 0.5f);
    }

    public static int px2dp(float value, Context context) {
        final float scale = context.getResources().getDisplayMetrics().densityDpi;
        return (int) ((value * 160) / scale + 0.5f);
    }

    public static int sp2px(float value) {
        Resources resources;
        if (mContext == null) {
            resources = Resources.getSystem();
        } else {
            resources = mContext.getResources();
        }
        float spvalue = value * resources.getDisplayMetrics().scaledDensity;
        return (int) (spvalue + 0.5f);
    }

    public static int sp2px(float value, Context context) {
        Resources resources;
        if (mContext == null) {
            resources = Resources.getSystem();
        } else {
            resources = context.getResources();
        }
        float spvalue = value * resources.getDisplayMetrics().scaledDensity;
        return (int) (spvalue + 0.5f);
    }

    public static int px2sp(float value) {
        final float scale = mContext.getResources().getDisplayMetrics().scaledDensity;
        return (int) (value / scale + 0.5f); 
    }

    public static int px2sp(float value, Context context) {
        final float scale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (value / scale + 0.5f); 
    }
}

这个工具类封装了px和dp,px和sp之间的转换方法。

因为变为横屏,宽度够了我们就让音量显示,同时将窗口变为全屏,在变为竖屏,就把这个标志移除在设为不全屏。

现在我们为音量的seekBar添加拖动事件,在setPlayerEvent()方法中添加:

volume_seek.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        // TODO Auto-generated method stub
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        // TODO Auto-generated method stub
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        // TODO Auto-generated method stub
        /*
         * 设置当前设备的的音量
         */
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
    }
});

拖动的过程中让音量变化,很容易的实现。

再为screen_img设置转换屏幕方向的点击事件:

screen_img.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        if (isFullScreen) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            screen_img.setImageResource(R.drawable.expand);
        } else {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            screen_img.setImageResource(R.drawable.collapse);
        }
    }
});

如果对setRequestedOrientation()方法,configChanges属性不了解的可以看我的博客Android横竖屏解析。

现在我们来处理在屏幕上滑动触发音量和亮度的变化的事件:

private boolean isAdjust = false;
private int threshold = 108;

videoView.setOnTouchListener(new OnTouchListener() {

    private float lastX;
    private float lastY;
    private float x;
    private float y;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        if(isFullScreen)
            switch (event.getAction()) {

                // 手指落下屏幕的那一刻,只会调用一次
                case MotionEvent.ACTION_DOWN: {
                    x = event.getRawX();
                    y = event.getRawY();
                    lastX = x;
                    lastY = y;
                    break;
                }
                // 手指在屏幕上移动,调用多次
                case MotionEvent.ACTION_MOVE: {
                    x = event.getRawX();
                    y = event.getRawY();
                    float detlaX = x - lastX;
                    float detlaY = y - lastY;
                    float absdetlaX = Math.abs(detlaX);
                    float absdetlaY = Math.abs(detlaY);
                    if (absdetlaX > threshold && absdetlaY > threshold) {
                        if (absdetlaX < absdetlaY) {
                            isAdjust = true;
                        } else {
                            isAdjust = false;
                        }
                    } else if (absdetlaX < threshold && absdetlaY > threshold) {
                        isAdjust = true;
                    } else if (absdetlaX > threshold && absdetlaY < threshold) {
                        isAdjust = false;
                    }

                    if (isAdjust) {
                        /*
                         * 在判断好当前手势事件已经合法的前提下,去区分此时手势应该调节亮度还是调节声音
                         */
                        if (x < screen_height / 2) {

                            changeBrightness(-detlaY);
                        } else {
                            // 调节声音
                            changeVolume(-detlaY);
                        }
                    }
                    break;
                }
                // 手指从屏幕上抬起
                case MotionEvent.ACTION_UP: {

                    progress_layout.setVisibility(View.GONE);

                    break;
                }
            }
        return true;
    }
});

isAdjust是我们用来判断手势操作的合法性,也就是可否被执行。threshold就是我们设定的偏差值,如果移动的距离小于这个,我们的操作就不执行。

我们在用app播放视频去做手势操作的时候,向上向下滑动改变这两个设置的值,如果偏移到左右两边那是不会有效果的,而且一般是全屏时起到这个手势修改的作用,我们就依照这个去设计。当然Y轴的值是越往下值越大,所以依照偏移量改变的值应该是相反的。

接下来是实现改变音量和亮度的逻辑:

private void changeVolume(float detalY) {
    int max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
    int current = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    int index = (int) (detalY / screen_height * max * 3);
    int volume = Math.max(current + index, 0);
    mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
    if (progress_layout.getVisibility() == View.GONE) {
        progress_layout.setVisibility(View.VISIBLE);
    }
    if (volume > max) {
        volume = max;
    }
    operation_bg.setImageResource(R.drawable.v12);
    ViewGroup.LayoutParams layoutParams = operation_percent.getLayoutParams();
    layoutParams.width = (int) (PixelUtil.dp2px(120)*(float)volume/max);
    operation_percent.setLayoutParams(layoutParams);
    volume_seek.setProgress(volume);
}

private void changeBrightness(float detlaY) {
    WindowManager.LayoutParams attributes = getWindow().getAttributes();
    float mBrightness = attributes.screenBrightness;
    float index = detlaY / screen_height / 3;
    mBrightness += index;
    if (mBrightness > 1.0f) {
        mBrightness = 1.0f;
    }
    if (mBrightness < 0.01f) {
        mBrightness = 0.01f;
    }
    attributes.screenBrightness = mBrightness;
    if (progress_layout.getVisibility() == View.GONE) {
        progress_layout.setVisibility(View.VISIBLE);
    }
    operation_bg.setImageResource(R.drawable.brightness);
    ViewGroup.LayoutParams layoutParams = operation_percent.getLayoutParams();
    layoutParams.width = (int) (PixelUtil.dp2px(120)*mBrightness);
    operation_percent.setLayoutParams(layoutParams);
    getWindow().setAttributes(attributes);
}

我们为音量和亮度的百分比的进度条设置的宽为120dp,所以在这里把120dp转换为px,去设置当前进度的百分比。

接下来最后的部分是为我们的视频添加快进倒退功能,这个实现因为要有新的图片在屏幕上显示,所以又要有一个布局:


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:visibility="gone"
    android:id="@+id/speed_layout"
    android:layout_centerInParent="true"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

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

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

FrameLayout>

在activity_main中添加include。

在代码中如下所示:

private FrameLayout speed_layout;
private boolean isSpeed = false;
private ImageView fast, rewind;

speed_layout = (FrameLayout) findViewById(R.id.speed_layout);
fast = (ImageView) findViewById(R.id.fast);
rewind = (ImageView) findViewById(R.id.rewind);

videoView.setOnTouchListener(new OnTouchListener() {

    private float lastX;
    private float lastY;
    private float x;
    private float y;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        if(isFullScreen)
            switch (event.getAction()) {

                    // 手指落下屏幕的那一刻,只会调用一次
                case MotionEvent.ACTION_DOWN: {
                    x = event.getRawX();
                    y = event.getRawY();
                    lastX = x;
                    lastY = y;
                    break;
                }
                // 手指在屏幕上移动,调用多次
                case MotionEvent.ACTION_MOVE: {
                    x = event.getRawX();
                    y = event.getRawY();
                    float detlaX = x - lastX;
                    float detlaY = y - lastY;
                    float absdetlaX = Math.abs(detlaX);
                    float absdetlaY = Math.abs(detlaY);
                    if (absdetlaX > threshold && absdetlaY > threshold) {
                        if (absdetlaX < absdetlaY) {
                            isAdjust = true;
                            isSpeed = false;
                        } else {
                            isAdjust = false;
                            isSpeed =  true;
                        }
                    } else if (absdetlaX < threshold && absdetlaY > threshold) {
                        isAdjust = true;
                        isSpeed = false;
                    } else if (absdetlaX > threshold && absdetlaY < threshold) {
                        isAdjust = false;
                        isSpeed = true;
                    }

                    if (isAdjust) {
                        /*
                         * 在判断好当前手势事件已经合法的前提下,去区分此时手势应该调节亮度还是调节声音
                         */
                        if (x < screen_height / 2) {

                            changeBrightness(-detlaY);
                        } else {
                            // 调节声音
                            changeVolume(-detlaY);
                        }
                    }

                    else if (isSpeed && isFullScreen) {
                        int position = videoView.getCurrentPosition();

                        if (detlaX > 300) {
                            if (speed_layout.getVisibility() == View.GONE)
                                speed_layout.setVisibility(View.VISIBLE);
                            fast.setVisibility(View.VISIBLE);
                            rewind.setVisibility(View.GONE);
                            position = Math.min(position + 2000, videoView.getDuration());
                        } else if (detlaX < -300) {
                            if (speed_layout.getVisibility() == View.GONE)
                                speed_layout.setVisibility(View.VISIBLE);
                            rewind.setVisibility(View.VISIBLE);
                            fast.setVisibility(View.GONE);
                            position = Math.max(position - 2000, 0);
                        }
                        videoView.seekTo(position);
                        play_seek.setProgress(position);
                    }

                    break;
                }
                // 手指从屏幕上抬起
                case MotionEvent.ACTION_UP: {

                    progress_layout.setVisibility(View.GONE);
                    speed_layout.setVisibility(View.GONE);
                    rewind.setVisibility(View.GONE);
                    fast.setVisibility(View.GONE);
                    isSpeed = false;
                    isAdjust = false;

                    break;
                }
            }
        return true;
    }
});

初始化控件,还有一个手势操作合法性判断的标记,然后我们再修改手势事件,确定滑动多少距离开始快进或者倒退,快进多少有自己决定。

以上我们的自定义视频播放器就实现完毕了,其中有叙述不清楚的地方还请大家多多包涵。下面是最后的演示结果:

Android自定义视频播放器_第2张图片

结束语:本文仅用来学习记录,参考查阅。

你可能感兴趣的:(android控件,android)