第13章 CustomView控件高级属性

一、GestureDetector手势检测

概述:

当用户触摸屏幕的时候,会产生许多手势,如 down、up 、scroll 、fling等。

GestureDetector(手势检测)类,通过这个类可以识别很多手势。在识别出于势之后,具体的事务处理则交 由程序员自己来实现。

 

GestureDetector.OnGestureListener接口:

1.基本讲解

如果我们写一个类并继承自OnGestureListener,则会提示有几个必须重写的函数。

private class gesturelistener implements GestureDetector.OnGestureListener {

    @Override
    public boolean onDown(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        // TODO Auto-generated method stub
        return false;
    }
}

这里重写 了 6 个函数,这些函数在什么情况下才会被触发呢?
• onDown(MotionEvent e):用户按下屏幕就会触发该函数。
• onShowPress(MotionEvent e):如果按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,该函数就会被触发。
• onLongPress(MotionEvent e):长接触摸屏,超过一定时长,就会触发这个函数。

触发顺序:onDown→onShowPress→onLongPress

• onSingleTapUp(MotionEvent e): 从名字中也可 以看出,一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会触发这个函数。当然,如果除down以外还有其他操作,就不再算是单独操作了,也就不会触发这个函数了。
单击一下非常快的(不滑动)Touchup,触发顺序:onDown→onSingleTapUp→onSingleTapConfirmed

单击一下稍微慢一点的(不滑动) Touchup,触发顺序 :onDown→onShowPress→onSingleTapUp→onSingleTapConfirmed

• onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY):滑屏,用户按下触摸屏、快速移动后松开,由一个MotionEvent ACTION_DOWN、多个ACTION_MOVE、一个ACTION_UP触发。
• onScroll(MotionEvent el, MotionEvent e2,float distanceX, float distanceY):在屏幕上拖动事件。无论是用手拖动View,还是以抛的动作滚动,都会多次触发这个函数,在ACTION_MOVE动作发生时就会触发该函数。

滑屏,即手指触动屏幕后,稍微滑动后立即松开,触发顺序:onDown→onScroll→onScroll→onScroll→...→onFling

拖动,触发顺序:onDown→onScroll→onScroll→onFling

可见,无论是滑屏还是拖动,影响的只是中间onScroll被触发的数量而己,最终都会触发onFling事件。

2.示例

要使用 GestureDetector,有四步要走 。
(1) 创建 OnGestureListener()监听函数。

GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener() {
};
或构造类:
private class gestureListener implements GestureDetector.OnGestureListener {
}

(2) 创建GestureDetector实例mGestureDetector。

GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener};
GestureDetector gestureDetector=new GestureDetector(Context context, GestureDetector.OnGestureListener listener};
GestureDetector gestureDetector=new GestureDetector(Context context, GestureDetector.SimpleOnGestureListener listener);

(3) 在onTouch(View v, MotionEvent event)中进行拦截。

public boolean onTouch(View v, MotionEvent event} {
    return mGestureDetector.onTouchEvent(event);
}

(4) 绑定控件。

TextView tv = (TextView) findViewByid(R.id.tv);
tv.setOnTouchListener(this);

示例:

<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.example.gesturedetectorinterface.MainActivity">
	<TextView
		android:id="@+id/tv"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:layoutmargin="50dip"
		android:background="#ff00ff"
		android:text="@string/hello_world"/>
</RelativeLayout>
public class MainActivity extends Activity implements View.OnTouchListener {
    private GestureDetector mGestureDetector;

    @Override
    protected void onCreate(Bundle savedinstanceState) {
        super.onCreate(savedinstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = (TextView) findViewById(R.id.tv);
        tv.setOnTouchListener(this);
        tv.setFocusable(true);
        tv.setClickable(true);
        tv.setLongClickable(true);

        mGestureDetector = new GestureDetector(new MyGestureListener());
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }

    private class MyGestureListener implements GestureDetector.OnGestureListener {
        @Override
        public boolean onDown(MotionEvent e) {
            Log.d("TAG", "onDown");
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            Log.d("TAG", "onShowPress");
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            Log.d("TAG", "onSingleTapUp");
            return true;
        }

        @Override
        public boolean onScroll(MotionEvent el, MotionEvent e2, float distanceX, float distanceY) {
            Log.d("TAG2", "onScroll:" + (e2.getX() - el.getX()) + "" + distanceX);
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            Log.d("TAG", "onLongPress");
        }

        @Override
        public boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY) {
            Log.d("TAG", "onFling");
            return true;
        }
    }
}

GestureDetector.OnDoubleTapListener接口:

1.构建

方法一:新建一个类,同时派生OnGestureListener和OnDoubleTapListener

private class gestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
}

方法二:使用GestureDetector.setOnDoubleTapListener()函数设置双击监听

// 构建 GestureDetector 实例
mGestureDetector = new GestureDetector(new MyGestureListener());
private class MyGestureListener implements GestureDetector.OnGestureListener {
}
// 设置双击监听
mGestureDetector.setOnDoubleTapListener(new MyDoubleTapListener());
private class MyDoubleTapListener implements GestureDetector.OnDoubleTapListener {
}

可以看到,无论是在方法一还是在方法二中,都需要派生自GestureDetector.OnGestureListener。

2.函数讲解

先来看一下OnDoubleTapListener接口必须重写的三个函数。

private class doubleTapListener implements GestureDetector.OnDoubleTapListener {

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        // TODO Auto-generated method stub
        return false;
    }
}

• onSingleTapConfirmed(MotionEvente):单击事件,用来判定该次单击是SingleTap,而不是DoubleTap。如果连续单击两次,就是DoubleTap手势;如果只单击一次,系统等待一段时间后没有收到第二次单击,则判定该次单击为SingleTap,而不是DoubleTap,然后触发SingleTapConfinned事件。触发顺序:onDown→onSingleTapUp→onSingleTapConfinned。OnGestureListener有这样一个函数onSingleTapUp(),它和onSingleTapConfinned()函数容易混淆。二者的区别是:对于onSingleTapUp()函数来说,只要手抬起就会被触发;而对于onSingleTapConfinned0函数来说,如果双击,则该函数就不会被触发。
• onDoubleTap(MotionEvente):双击事件。
• onDoubleTapEvent(MotionEvente):双击间隔中发生的动作。指在触发onDoubleTap以后,在双击之间发生的其他动作,包含down、up和move事件。

GestureDetector.SimpleOnGestureListener类:

SimpleOnGestureListener类与OnG巳stureListener和OnDoubleTapListener接口的不同之处在于:
(1)这是一个类,在它的基础上新建类,要用巳xtends派生,而不能用implements继承。
(2)OnGestureListener和OnDoubleTapListen巳r接口里的函数都是被强制重写的,即使用不到也要重写出来一个空函数:而在SimpleOnGestureListener类的实例或派生类中不必如此,可以根据情况,用到哪个函数就重写哪个函数,因为SimpleOnGestureListener类本身已经实现了这两个接口中的所有函数,只是里面全是空的而已。

SimpleOnGestureListener类内部的函数与OnGestureListener和OnDoubleTapListener接口中的函数是完全相同的。唯一不同的就是 SimpleOnGestureListener 类内部的函数不必被强制全部重写,用到哪个函数就重写哪个函 数;而OnGestureListener和OnDoubleTapListener是接口,它们内部的函数是必须被重写的。

onFling()函数的应用——识别是向左滑还是向右滑

boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY)
• el:第一个ACTION_DOWN MotionEvent
• e2:最后一个ACTION_MOVE MotionEvent
• velocityX:X轴上的移动速度,单位为像素/秒
• velocityY:Y轴上的移动速度,单位为像素/秒

实现的功能:当用户向左滑动距离超过100像素,且滑动速度超过100像素/秒时,即判断为向左滑动:向右同理。核心代码是在onFling()函数中判断当前的滑动方向及滑动速度是不是达到指定值。代码如下:

private class simpleGestureListener extends GestureDetector.SimpleOnGestureListener {
    /* ******OnGestureListener的函数****** */
    final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCITY = 200;
    // 触发条件:
    // X轴的坐标位移大于FLING_MIN_DISTANCE,且移动速度大于FLING_MIN_VELOCITY像素/秒
    public boolean onFling(MotionEvent el, MotionEvent e2, float velocityX, float velocityY) {
        if (el.getX() - e2.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {// 向左滑
            Log.d("TAG", "Fling left");
        } else if (e2.getX() - el.getX () > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {// 向右滑
            Log.i("TAG", "Fling right" );
        }
        return true;
    }
}

二、Window与WindowManager

Window表示窗口,在某些特殊的时候,比如需要在桌面或者锁屏上显示一些类似悬浮窗的效果,就需要用到Window。Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的。而WindowManager则提供了对这些Window的统一管理功能。

Window与WindowManager的联系:

使用WindowManager来添加一个Window:

WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(width, height, type, flags, format);
manager.addView(btn, layoutParams);
◆ flags:
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
表示此Window不需要获取焦点,不接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,
最终事件会直接传递给下层具有焦点的Window。
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
自己Window区域内的事件自己处理;自己Window区域外的事件传递给底层Window处理。
一般这个选项会默认开启,否则其他Window无法接收事件。
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
可以让此Window显示在锁屏上。

◆ type:
type参数也是int类型的,表示Window的类型。
Window有三种类型:应用Window、子Window、系统Window。
应用Window:对应着一个Activity。
子Window:不能独立存在,它需要附属在特定的父Window中,比如Dialog就是一个子Window。
系统Window:需要声明权限才能创建,比如Toast和系统状态栏都是系统Window。
Window是分层的,层级大的Window会覆盖在层级小的Window上面。
● 应用Window层级范围:1~99
● 子Window层级范围:1000~1999
● 系统Window层级范围:2000~2999
type参数就对应以上这些数字。如果想让Window置于顶层,则采用较大的层级即可。

如果是系统类型的Window,则需要在AndroidManifest.xml中配置如下权限声明:

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

WindowManager提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View。这三个方法定义在ViewManager中,而 WindowManager继承自ViewManager。

public interface WindowManager extends ViewManager {
}
public interface ViewManager {
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManager操作Window的过程更像在操作Window中的View。

示例:腾讯手机管家悬浮窗的小火箭效果

<?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">
	<Button
		android:id="@+id/add_btn"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="add view"/>
	<Button
		android:id="@+id/rmv_btn"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="remove view"/>
</LinearLayout>
public class MainActivity extends Activity implements View.OnTouchListener, View.OnClickListener {
    private Button mCreateWndBtn, mRmvWndBtn;
    private ImageView mImageView;
    private WindowManager.LayoutParams mLayoutParams;
    private WindowManager mWindowManager;

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

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Intent myIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            startActivityForResult(myIntent, 100);
        } else {
            initView();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100) {
            initView();
        }
    }

    private void initView() {
        mCreateWndBtn = (Button) findViewById(R.id.add_btn);
        mRmvWndBtn = (Button) findViewById(R.id.rmv_btn);
        mCreateWndBtn.setOnClickListener(this);
        mRmvWndBtn.setOnClickListener(this);

        mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int rawX = (int) event.getRawX();
        int rawY = (int) event.getRawY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
                mLayoutParams.x = rawX;
                mLayoutParams.y = rawY;
                mWindowManager.updateViewLayout(mImageView, mLayoutParams);
                break;
            }
        }
        return false;// return false:会执行onClick方法
    }

    @Override
    protected void onDestroy() {
        try {
            mWindowManager.removeView(mImageView);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.add_btn) {
            mImageView = new ImageView(this);
            mImageView.setBackgroundResource(R.mipmap.ic_launcher);

            mLayoutParams = new WindowManager.LayoutParams(
                    WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 2099,
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    ,
                    PixelFormat.TRANSPARENT);
            mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
            mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
            mLayoutParams.x = 0;
            mLayoutParams.y = 300;
            mImageView.setOnTouchListener(this);
            mWindowManager.addView(mImageView, mLayoutParams);
        } else if (v.getId() == R.id.rmv_btn) {
            mWindowManager.removeViewImmediate(mImageView);
        }
    }
}
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

需要注意的是,在 SDK API>=23(Android6.0)时,不仅需要在AndroidManifest.xml中添加权限申请,也需要在代码中动态申请。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值