GestureDetector手势检测简单使用(音量、亮度、快进、快退)

  • Post author:
  • Post category:其他




GestureDetector手势检测简单使用(音量、亮度、快进、快退)



引言

在 android 开发过程中,我们经常需要对一些手势,如:单击、双击、长按、滑动、缩放等,进行监测。这时也就引出了手势监测的概念,所谓的手势监测,说白了就是对于 GestureDetector 的使用。

GestureDetector 是 Android 中,专门用来进行手势监听的一个对象,在他的监听器中,我们通过传入 MotionEvents 对象,就可以在各种事件的回调方法中各种手势进行监测。举个例子: GestureDetector 的 OnGestureListener 就是一种回调方法,就是说在获得了传入的这个 MotionEvents 对象之后,进行了处理,我们通过重写了其中的各种方法(单击事件、双击事件等等),就可以监听到单击,双击,滑动等事件,然后直接在这些方法内部进行处理。注:由于缩放手势独有的复杂性,在这里就先不讨论。

最近毕业设计涉及到了视频的播放,在这里刚好给大家说说GestureDetector来实现视频的快进、快退、音量、亮度的调节以及双击控制播放。项目采用谷歌提供的ExoPlayer来封装实现。



GestureDetector 介绍


构造函数

public GestureDetector(Context context, OnGestureListener listener) 
public GestureDetector(Context context, OnGestureListener listener, Handler handler) 
public GestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused){this(context, listener, handler);} 

GestureDetector中的数据是给GestureHandler内部类进行处理,这个类会使用Handler,由Handler的知识知道,创建Handler必须有Looper,但是在一些新开的线程中没有创建Looper,所以我们需要传入一个带了Looper的Handler变量(或者调用Loop.prepare()),否则,GestureDetector对象会创建失败。


  • OnDoubleTapListener

    :也就是双击事件,双击事件除了 onDoubleTapEvent 这个回调方法之外,还有 SingleTapConfirmed 和 DoubleTap 这两个回调方法


  • OnGestureListener

    :这里集合了众多手势的监听器:主要有:按下(Down)、 扔(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress) 和 单击抬起(SingleTapUp)


  • SimpleOnGestureListener

    :上述接口的空实现,用的频率比较多

//下面的6个方法继承自OnGestureListener
public boolean onSingleTapUp( MotionEvent e)
当用户单击时触发

public void onLongPress (MotionEvent e )
当用户手指在长按屏幕时触发

public boolean onScroll (MotionEvent e1 , MotionEvent e2 ,float distanceX , float distanceY)
当用户手指在屏幕上拖动时触发
后面两个变量时在X,Y上移动的距离

public boolean onFling ( MotionEvent e1 ,MotionEvent e2 , float velocityX , float velocityY)
当用户手指拖动后,手指离开屏幕时触发
这个方法常用来使手指离开后页面仍然可以滑动(速度慢慢变小)
后两个变量表示手指在X,Y两个方向上的速度

public void onShowPress (MotionEvent  e)
当用户手指按下,但没有移动时触发该方法

public boolean onDown ( MotionEvent e)  
当按下时触发该方法,所有手势第一个必定触发该方法

//下面的三个方法继承自OnDoubleTapListener
public boolean onDoubleTap (MotionEvent e)
当用户双击时触发

public boolean onDoubleTapEvent (MotionEvent e)
在双击事件确定发生时会对第二次按下产生的 MotionEvent 信息进行回调。

public boolean onSingleTapConfirmed ( MotionEvent e)
当单击事件确定后进行回调



单击

public boolean onSingleTapUp( MotionEvent e)

public boolean onSingleTapConfirmed ( MotionEvent e)

我们进行单击事件的时候,这几个方法响应的顺序是这样的:

onDown() -> onSingleTapUp() -> onSingleCofirmed()

首先onDown()必定是第一个执行的,但是会发现onSingleTapUp在onSingleComfirmed之前执行,我查阅了相关文档,发现他们虽然同样响应的是当手指离开屏幕的活动,但是onSingleTapUp是立即执行,而onSingleComfirmed却要在离开后300ms后才执行 ,这样的目的是确认我们进行的是单击事件(为了防止我们在300ms内再次进行单击事件),所以他们的名字分别是Up和Comfirmed.



双击

public boolean onDoubleTap (MotionEvent e)

public boolean onDoubleTapEvent (MotionEvent e)

onDoubleTap: 可以确认这是一个双击事件的时候回调

onDoubleTapEvent(MotionEvent e):onDoubleTap()回调之后的输入事件(DOWN、MOVE、UP)都会回调这个方法(这个方法可以实现一些双击后的控制,如让View双击后变得可拖动等)。



长按

public void onShowPress (MotionEvent e)

public void onLongPress (MotionEvent e)

响应顺序如下:

onDown -> onShowPress -> onLongPress

也就是说,在长按时,onShowPress在onLongPress前面执行



滑动/拖动

public void onScroll (MotionEvent e)

public void onFling (MotionEvent e)

响应顺序:

onDown -> onScroll -> 不定量个onScroll ->onScroll -> onFiling

由此可见,在滑动/拖动过程中,不断调用onScroll,最后调用onFiling



使用案例

在这里插入图片描述



引入播放UI布局

首先来引入播放页面的布局。

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/root_layout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <RelativeLayout
            android:id="@+id/gesture_volume_layout"
            android:layout_width="120dp"
            android:layout_height="100dp"
            android:layout_centerInParent="true"
            android:background="@drawable/player_gesture_bg"
            android:gravity="center"
            android:visibility="gone">

            <ImageView
                android:id="@+id/gesture_iv_player_volume"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/player_volume" />

            <TextView
                android:id="@+id/gesture_tv_volume_percentage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/gesture_iv_player_volume"
                android:layout_centerHorizontal="true"
                android:gravity="right"
                android:text="80%"
                android:textColor="#ffececec" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/gesture_bright_layout"
            android:layout_width="120dp"
            android:layout_height="100dp"
            android:layout_centerInParent="true"
            android:background="@drawable/player_gesture_bg"
            android:gravity="center"
            android:visibility="gone">

            <ImageView
                android:id="@+id/gesture_iv_player_bright"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/player_bright" />

            <TextView
                android:id="@+id/gesture_tv_bright_percentage"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/gesture_iv_player_bright"
                android:layout_centerHorizontal="true"
                android:gravity="right"
                android:text="80%"
                android:textColor="#ffececec" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/gesture_progress_layout"
            android:layout_width="120dp"
            android:layout_height="100dp"
            android:layout_centerInParent="true"
            android:background="@drawable/player_gesture_bg"
            android:gravity="center"
            android:visibility="gone">

            <ImageView
                android:id="@+id/gesture_iv_progress"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_centerHorizontal="true"
                android:src="@drawable/player_backward" />

            <TextView
                android:id="@+id/gesture_tv_progress_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/gesture_iv_progress"
                android:layout_centerHorizontal="true"
                android:gravity="right"
                android:text="00:35/24:89"
                android:textColor="#ffececec" />
        </RelativeLayout>
    </RelativeLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/lightGrayBg"
        android:orientation="horizontal">

        <ImageView
            android:id="@id/exo_play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:src="@drawable/video_play" />

        <ImageView
            android:id="@id/exo_pause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:src="@drawable/video_pause" />

        <com.google.android.exoplayer2.ui.DefaultTimeBar
            android:id="@id/exo_progress"
            android:layout_width="0dp"
            android:layout_height="26dp"
            android:layout_weight="1"
            app:buffered_color="@android:color/darker_gray"
            app:layout_constraintBottom_toBottomOf="@id/exo_position"
            app:layout_constraintLeft_toRightOf="@id/exo_position"
            app:layout_constraintRight_toLeftOf="@id/exo_duration"
            app:layout_constraintTop_toTopOf="@id/exo_position"
            app:played_color="@color/tabActive"
            app:unplayed_color="@android:color/black" />

        <TextView
            android:id="@id/exo_position"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="2:00"

            android:textColor="@color/textColor" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="/"

            android:textColor="@color/textColor" />

        <TextView
            android:id="@id/exo_duration"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="12:00"
            android:textColor="@color/textColor"

            />

        <ImageView
            android:id="@+id/exo_fullscreen"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:src="@drawable/video_fullscreen" />


    </LinearLayout>

</LinearLayout>



初始化view

        root_layout = findViewById(R.id.root_layout);
        root_layout.setLongClickable(true);//需要实现OnTouchListener监听
        root_layout.setOnTouchListener(this);

        gesture_volume_layout = findViewById(R.id.gesture_volume_layout);
        gesture_iv_player_volume = findViewById(R.id.gesture_iv_player_volume);
        gesture_tv_volume_percentage = findViewById(R.id.gesture_tv_volume_percentage);

        gesture_bright_layout = findViewById(R.id.gesture_bright_layout);
        gesture_iv_player_bright = findViewById(R.id.gesture_iv_player_bright);
        gesture_tv_bright_percentage = findViewById(R.id.gesture_tv_bright_percentage);

        gesture_progress_layout = findViewById(R.id.gesture_progress_layout);
        gesture_iv_progress = findViewById(R.id.gesture_iv_progress);
        gesture_tv_progress_time = findViewById(R.id.gesture_tv_progress_time);



获取播放窗口的宽高、初始化数据

         /** 获取视频播放窗口的尺寸 */
        ViewTreeObserver viewObserver = root_layout.getViewTreeObserver();
        viewObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                root_layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                playerWidth = root_layout.getWidth();
                playerHeight = root_layout.getHeight();
            }
        });

        audiomanager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        maxVolume = audiomanager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); // 获取系统最大音量
        currentVolume = audiomanager.getStreamVolume(AudioManager.STREAM_MUSIC); // 获取当前值



播放倍速

        //倍速
        PlaybackParameters playbackParameters = new PlaybackParameters(speed, 1.0F);
        simpleExoPlayer.setPlaybackParameters(playbackParameters);



重写onTouch和SimpleOnGestureListener

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            GESTURE_FLAG = 0;// 手指离开屏幕后,重置调节音量或进度的标志
            gesture_volume_layout.setVisibility(View.GONE);
            gesture_bright_layout.setVisibility(View.GONE);
            gesture_progress_layout.setVisibility(View.GONE);
        }
        Log.i("onTouch", v.toString());
        Log.i("onTouch", "onTouch");
        return gestureDetector.onTouchEvent(event);
    }
    GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onDown(MotionEvent e) {
                firstScroll = true;// 设定是触摸屏幕后第一次scroll的标志
                Log.i("onTouch", "onDown");
                return false;
            }

            @Override
            public boolean onDoubleTapEvent(MotionEvent e) {
                Log.i("onTouch", "onDoubleTapEvent");
                switch (e.getActionMasked()) {
                    case MotionEvent.ACTION_UP:
                        if (playing) {
                            simpleExoPlayer.setPlayWhenReady(false);
                            playing = false;
                        } else {
                            simpleExoPlayer.setPlayWhenReady(true);
                            playing = true;
                        }
                        break;
                }

                firstScroll = false;// 第一次scroll执行完成,修改标志

                return super.onDoubleTapEvent(e);
            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                Log.i("onTouch", "onScroll");
                float mOldX = e1.getX(), mOldY = e1.getY();
                int y = (int) e2.getRawY();
                if (firstScroll) {// 以触摸屏幕后第一次滑动为标准,避免在屏幕上操作切换混乱
                    // 横向的距离变化大则调整进度,纵向的变化大则调整音量
                    if (Math.abs(distanceX) >= Math.abs(distanceY)) {
                        gesture_progress_layout.setVisibility(View.VISIBLE);
                        gesture_volume_layout.setVisibility(View.GONE);
                        gesture_bright_layout.setVisibility(View.GONE);
                        GESTURE_FLAG = GESTURE_MODIFY_PROGRESS;
                    } else {
                        if (mOldX > playerWidth * 3.0 / 5) {// 音量
                            gesture_volume_layout.setVisibility(View.VISIBLE);
                            gesture_bright_layout.setVisibility(View.GONE);
                            gesture_progress_layout.setVisibility(View.GONE);
                            GESTURE_FLAG = GESTURE_MODIFY_VOLUME;
                        } else if (mOldX < playerWidth * 2.0 / 5) {// 亮度
                            gesture_bright_layout.setVisibility(View.VISIBLE);
                            gesture_volume_layout.setVisibility(View.GONE);
                            gesture_progress_layout.setVisibility(View.GONE);
                            GESTURE_FLAG = GESTURE_MODIFY_BRIGHT;
                        }
                    }
                }

                // 如果每次触摸屏幕后第一次scroll是调节进度,那之后的scroll事件都处理音量进度,直到离开屏幕执行下一次操作
                if (GESTURE_FLAG == GESTURE_MODIFY_PROGRESS) {
                    // distanceX=lastScrollPositionX-currentScrollPositionX,因此为正时是快进
                    if (Math.abs(distanceX) > Math.abs(distanceY)) {// 横向移动大于纵向移动
                        if (distanceX >= DensityUtil.dip2px(VideoPlayActivity.this, STEP_PROGRESS)) {// 快退,用步长控制改变速度,可微调
                            gesture_iv_progress.setImageResource(R.drawable.player_backward);
                            if (playingTime > 3000) {// 避免为负
                                playingTime -= 3000;// scroll方法执行一次快退3秒
                            } else {
                                playingTime = 0;
                            }
                        } else if (distanceX <= -DensityUtil.dip2px(VideoPlayActivity.this, STEP_PROGRESS)) {// 快进
                            gesture_iv_progress.setImageResource(R.drawable.player_forward);
                            if (playingTime < videoTotalTime - 16000) {// 避免超过总时长
                                playingTime += 3000;// scroll执行一次快进3秒
                            } else {
                                playingTime = videoTotalTime - 10000;
                            }
                        }
                        if (playingTime < 0) {
                            playingTime = 0;
                        }

                        simpleExoPlayer.seekTo(playingTime);

                        gesture_tv_progress_time.setText(sdf.format(playingTime) + "/" + sdf.format(videoTotalTime));
                    }
                }

                // 如果每次触摸屏幕后第一次scroll是调节音量,那之后的scroll事件都处理音量调节,直到离开屏幕执行下一次操作
                else if (GESTURE_FLAG == GESTURE_MODIFY_VOLUME) {
                    currentVolume = audiomanager.getStreamVolume(AudioManager.STREAM_MUSIC); // 获取当前值
                    if (Math.abs(distanceY) > Math.abs(distanceX)) {// 纵向移动大于横向移动
                        if (distanceY >= DensityUtil.dip2px(VideoPlayActivity.this, STEP_VOLUME)) {// 音量调大,注意横屏时的坐标体系,尽管左上角是原点,但横向向上滑动时distanceY为正
                            if (currentVolume < maxVolume) {// 为避免调节过快,distanceY应大于一个设定值
                                currentVolume++;
                            }
                            gesture_iv_player_volume.setImageResource(R.drawable.player_volume);
                        } else if (distanceY <= -DensityUtil.dip2px(VideoPlayActivity.this, STEP_VOLUME)) {// 音量调小
                            if (currentVolume > 0) {
                                currentVolume--;
                                if (currentVolume == 0) {// 静音,设定静音独有的图片
                                    gesture_iv_player_volume.setImageResource(R.drawable.player_silence);
                                }
                            }
                        }
                        int percentage = (currentVolume * 100) / maxVolume;
                        gesture_tv_volume_percentage.setText(percentage + "%");
                        audiomanager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
                    }
                }

                // 如果每次触摸屏幕后第一次scroll是调节亮度,那之后的scroll事件都处理亮度调节,直到离开屏幕执行下一次操作
                else if (GESTURE_FLAG == GESTURE_MODIFY_BRIGHT) {
                    gesture_iv_player_bright.setImageResource(R.drawable.player_bright);
                    if (mBrightness < 0) {
                        mBrightness = getWindow().getAttributes().screenBrightness;
                        if (mBrightness <= 0.00f)
                            mBrightness = 0.50f;
                        if (mBrightness < 0.01f)
                            mBrightness = 0.01f;
                    }
                    WindowManager.LayoutParams lpa = getWindow().getAttributes();
                    lpa.screenBrightness = mBrightness + (mOldY - y) / playerHeight;
                    if (lpa.screenBrightness > 1.0f)
                        lpa.screenBrightness = 1.0f;
                    else if (lpa.screenBrightness < 0.01f)
                        lpa.screenBrightness = 0.01f;
                    getWindow().setAttributes(lpa);
                    gesture_tv_bright_percentage.setText((int) (lpa.screenBrightness * 100) + "%");
                }

                firstScroll = false;// 第一次scroll执行完成,修改标志
                return false;
            }
        };

        gestureDetector = new GestureDetector(VideoPlayActivity.this, gestureListener);
        gestureDetector.setIsLongpressEnabled(true);


Demo下载地址:



版权声明:本文为weixin_37267014原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。