博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第13章 CustomView控件高级属性
阅读量:672 次
发布时间:2019-03-16

本文共 12686 字,大约阅读时间需要 42 分钟。

一、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);

示例:

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~2999type参数就对应以上这些数字。如果想让Window置于顶层,则采用较大的层级即可。

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

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。

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

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);        }    }}

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

转载地址:http://wsmqz.baihongyu.com/

你可能感兴趣的文章