Android GestureDetector★★★-程序员宅基地

技术标签: android  

1.GestureDetecor

用户触摸屏幕时会产生许多手势,一般通过重写View类的onTouch()方法可以处理一些触摸事件,但是这个方法太过简单,如果需要处理一些复杂的手势,用这个接口就会很麻烦,需要自己根据用户触摸的轨迹去判断是什么手势。

因此,Android sdk提供了GestureDetector类来监听手势发生,通过它的onTouchEvent(event)方法完成不同手势的识别。识别出手势后,不同的手势要怎么处理,再由开发者自己来实现。

GestureDetector类内部定义了三个接口和一个类,重写这些方法,就能实现传入触摸事件之后做出相应的回调:

①OnGestureListener接口

监听一些手势,如单击、滑动、长按等操作。

②OnDoubleTapListener接口

监听双击和单击事件。

③OnContextClickListener接口

当鼠标/触摸板,右键点击时候的回调。

④SimpleOnGestureListener 类

实现了上面三个接口的类,拥有上面三个的所有回调方法。

这些回调方法的返回值都是boolean类型,和View的事件传递机制一样,返回true表示消耗了事件,flase表示没有消耗。

其中,SimpleOnGestureListener是一个静态内部类,它实现了OnGestureListener、OnDoubleTapListener、OnContextClickListener三个接口。SimpleOnGestureListener类内重写了接口中的所有方法,但都是空实现,返回的布尔值都是false。这个类的主要作用就是方便继承这个类有选择的复写回调方法,而不是实现接口去重写所有的方法。

 

下面具体说说这几个接口:

①GesturetDetector.OnGestureListener 接口

public interface OnGestureListener {

    boolean onDown(MotionEvent e); // 由1个down触发,每按一下屏幕立即触发

    void onShowPress(MotionEvent e); //按下屏幕并且没有移动或松开,由1个down触发。注意和onDown()的区别,强调的是没有松开或者拖动的状态,而onDown()没有任何限制。主要是提供给用户一个可视化的反馈,告诉用户按下操作已经被捕捉到了。如果按下的速度很快只会调用onDown(),按下的速度稍慢一点会先调用onDown()再调用onShowPress()

  boolean onSingleTapUp(MotionEvent e); //一次单纯的轻击抬手动作时触发,由一个up触发。如果除了Down以外还有其他操作,比如触发了长按,这时候手抬起时,这个方法不执行

    boolean onScroll(MotionEvent e1, MotionEvent e2,float distanceX,float distanceY); // 屏幕拖动事件。手指按下并在屏幕上滑动时触发。这个事件由1个down,多个move触发。手指在屏幕上滑动时,会不断触发该方法。如果按下的时间过长,调用了onLongPress,再拖动屏幕不会触发onScroll。e1:开始拖动的第一次按下down操作,也就是第一个ACTION_DOWN;e2:触发当前onScroll方法的ACTION_MOVE,即当前滑动位置点;distanceX:当前x坐标与最后一次触发scroll方法的x坐标的差值,不是e1点到e2点的x轴距离;diastancY:当前y坐标与最后一次触发scroll方法的y坐标的差值,不是e1点到e2点的y轴距离

    void onLongPress(MotionEvent e); //长按。在down操作之后,过一个特定的时间触发 

    boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); //按下屏幕,在屏幕上快速滑动后松开,由一个down、多个move、一个up触发。当滑动较慢时,只会触发onScroll,而不会触发onFling。e1:开始快速滑动的第一次按下down操作,也就是第一个ACTION_DOWN,即拖动事件的起点;e2:触发当前onFling方法的move操作,也就是最后一个ACTION_MOVE,即onFling()调用时手指的位置;velocityX:X轴上的移动速度,像素/秒;velocityY:Y轴上的移动速度,像素/秒

}

快按屏幕抬起:onDown —> onSingleTapUp —> onSingleTapConfirmed

慢按屏幕抬起:onDown –> onShowPress —> onSingleTapUp —> onSingleTapConfirmed

按下屏幕等待一段时间:onDown –> onShowPress –> onLongPress

拖动屏幕:onDown –> onShowPress –> onScroll(多个) –> onFling

快速滑动:onDown–>onScroll(多个)–>onFling

注意:当拖动速率velocityX或velocityY超过ViewConfiguration.getMinimumFlingVelocity()最小拖动速率时,才会调用onFling(),也就是说如果只拖动一点,或者慢慢的拖动,是不会触发该方法的。

②GesttureDetector.OnDoubleTapListener 接口

public interface OnDoubleTapListener {

    boolean onSingleTapConfirmed(MotionEvent e); //单击事件。用来判定该次点击是单纯的SingleTap而不是DoubleTap。如果连续点击两次就是DoubleTap手势,如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发SingleTapConfirmed事件。触发顺序是:OnDown->OnSingleTapUp->OnSingleTapConfirmed。关于onSingleTapConfirmed和onSingleTapUp的区别:onSingleTapUp只要手抬起就会执行,而对于onSingleTapConfirmed来说,如果双击的话,则onSingleTapConfirmed不会执行。

    boolean onDoubleTap(MotionEvent e); //双击事件。在第二次点击的down时触发。参数表示双击发生时,第一次按下时的down事件

   boolean onDoubleTapEvent( MotionEvent e); //双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作,包含down、up和move事件

}

下图是双击一下的Log输出:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

从图中可以看出,在第二下点击时,先触发OnDoubleTap,然后再触发OnDown(第二次点击)。在触发OnDoubleTap以后,就开始触发onDoubleTapEvent了,onDoubleTapEvent后面的数字代表了当前的事件,0指ACTION_DOWN,1指ACTION_UP,2 指ACTION_MOVE。

双击顺序:onDown –> onSingleTapUp –> onDoubleTap –> onDoubleTapEvent –> onDown–> onDoubleTapEvent

③GesttureDetector.OnContextClickListener接口

public interface On context click Listener {

    boolean onContextClick(MotionEvent e);

}

④GestureDetector.SimpleOnGestureListener类

它与前面三个不同,这是一个类,并且实现了前三个接口,在它基础上新建类的话,要用extends派生而不是用implements继承。OnGestureListener和OnDoubleTapListener接口里的函数都是必须重写的,即使用不到也要重写一个空函数,但SimpleOnGestureListener类的实例或派生类中可以根据情况,用到哪个函数就重写哪个函数,因为SimpleOnGestureListener类本身已经实现了这两个接口的所有函数,只是里面全是空的而已。

 

2.GestureDetector使用方法

①第一步:根据需求创建onGestureListener、onDoubleTapListener或simpleOnGestureListener这三个类中的一个对象。比如只需要监听单击的话,那只需要得到实现了onGestureListener接口的对象;如果是双击,就只需要得到实现了onDoubleTapListener接口的对象;如果两种情况都需要考虑,就可以用SimpleOnGestureListener对象或者一个实现了上面两个接口的对象。

②第二步:创建一个GestureDetector对象,参数传入第一步的对象。

GestureDetector的构造函数如下:

public GestureDetector(Context context, OnGestureListener listener) {

    this(context, listener, null);

}

public GestureDetector(Context context, OnGestureListener listener, Handler handler) {

}

public GestureDetector(Context context, OnGestureListener listener, Handler handler,

        boolean unused) {

    this(context, listener, handler);

}

mGestureDetector = new GestureDetector( mContext, new MyGestureListener());

③第三步: 给view设置OnTouchListener监听,重写onTouch()方法,在return语句中写上mGestureDetector.onTouchEvent(event),将控制权交给GestureDector。

④第四步:绑定控件,给view设置setClickable(true)、setFocusable(true)、setLongClickable(true)。这一步非常重要,不然出不来效果。

注意:GestureDetector的构造方法中,有的构造函数需要多传递一个Handler作为参数,这个Handler主要是为了给GestureDetector提供一个Looper。带有handler参数,表示要在与该handler相关联的线程上,监听手势并处理延时事件。

通常情况下不需要这个Handler,因为它会在内部自动创建一个Handler用于处理数据。如果在主线程中创建GestureDetector,那么它内部创建的Handler会自动获得主线程的Looper;但是如果在一个没有创建Looper的子线程中创建 GestureDetector ,则需要传递一个带有Looper的Handler给它,否则就会因为无法获取到Looper导致创建失败。

下面是两种在子线程中创建GestureDetector的方法:

// 方式一:在主线程创建Handler

final Handler handler = new Handler();

new Thread(new Runnable() {

    @Override

    public void run() {

        final GestureDetector detector = new GestureDetector(this, new               GestureDetector.SimpleOnGestureListener() , handler);

        // ......

    }

}).start();

// 方式二:在子线程创建Handler,并指定Looper

new Thread(new Runnable() {

    @Override

    public void run() {

        final Handler handler = new Handler(Looper.getMainLooper());

        final GestureDetector detector = new GestureDetector(this, new               GestureDetector.SimpleOnGestureListener() , handler);

        // ......

    }

}).start();

使用其它创建Handler的方式也可以,重点是传递的Handler一定要有Looper。强调一下:重点是Handler中的Looper。假如子线程准备了Looper,则可以直接使用第 1 种构造函数进行创建:

new Thread(new Runnable() {

    @Override public void run() {

        Looper.prepare(); // <- 重点在这里

        final GestureDetector detector = new GestureDetector(this, new              GestureDetector.SimpleOnGestureListener());

        // ... 省略其它代码 ...

    }

}).start();

 

GestureDetector使用 完整代码:

public class MyActivity extends Activity implements View.OnTouchListener, GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener{

    private GestureDetector mGestureDetector;

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mGestureDetector = new GestureDetector(this);

        Text view textview = findViewById(R.id.tv);

        textview.setOnTouchListener(this);

        textview.setClickable(true);

        textview.setLongClickable(true);

        textview.setFocusable(true);             mGestureDetector.setIsLongpressEnabled(true);

    }

    @Override

    public boolean onTouch(View v, MotionEvent event) {

        return mGestureDetector. onTouchEvent(event);

    }

   @Override

    public boolean onDown(MotionEvent e) {

        return true;//注意要返回true,否则后续事件无法处理

    }

    @Override

      public void onShowPress(MotionEvent e) {

          Log.e(TAG, "onShowPress");

      }

    @Override

  public boolean onSingleTapUp(MotionEvent e) {

        Log.e(TAG, "onSingleTapUp");

          return true;

    }

    @Override

      public void onLongPress(MotionEvent e) {

            Log.e(TAG, "onLongPress");

        }     

    @Override

    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,float distanceY){

         Log.e(TAG, "onScroll");

          return true;

     }

    @Override

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

        Log.e(TAG, "onFling");

        final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCHTE = 200;

        if(e1.getX() - e2.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {

            Log.i(TAG, "Fling left");

        } else if(e2.getX() - e1.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) {

            Log.i(TAG, "Fling right");

        } else if(e1.getY() - e2.getY() > FLING_MIN_DISTANCE && Math.abs(velocityY) > FLING_MIN_VELOCITY) {

            Log.i(TAG, "Fling up");

        } else if(e2.getY() - e1.getY() > FLING_MIN_DISTANCE && Math.abs(velocityY) > FLING_MIN_VELOCITY) {

            Log.i(TAG, "Fling down");

        }

        return false;

    }

     @Override

   public boolean onDoubleTap(MotionEvent e) {

            Log.e(TAG, "onDoubleTap");

            return true;

        }

     @Override

    public boolean onDoubleTapEvent( MotionEvent e) {

            Log.e(TAG, "onDoubleTapEvent");

            return true;

        }

   @Override

   public boolean onSingleTapConfirmed( MotionEvent e) {

         Log.e(TAG, "onSingleTapConfirmed");

         return true;

        }

  @Override

  public boolean onContextClick(MotionEvent e) {

         Log.e(TAG, "onContextClick");

          return true;

      }   

    @override

    public boolean dispatchTouchEvent( MotionEvent event){

    if(mGestureDetector.onTouchEvent(event)){   //返回true说明手势处理了这个事件     

        event.setAction(MotionEvent. ACTION_CANCEL);

    }

    return super.dispatchTouchEvent(event);

    }                   

}

注意:最后这个dispatchTouchEvent方法必须要写,否则onScroll、onFling方法不执行。原因还在研究中。而且,要想触发onScroll、onFling方法,必须让监听器的onDown返回值是true。

 

3.GestureDetector源码

①初始化处理

从构造方法开始,目前在用的有三个:

public GestureDetector(Context context, OnGestureListener listener) {

    this(context, listener, null);

}

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

    this(context, listener, handler);

}

public GestureDetector(Context context, OnGestureListener listener, Handler handler) {

    if (handler != null) {

        mHandler = new GestureHandler(handler);//初始化Handler,用于处理延时消息

    } else {

        mHandler = new GestureHandler();

    }

    mListener = listener;//设置回调监听器

    if (listener instanceof OnDoubleTapListener) {

        setOnDoubleTapListener( (OnDoubleTapListener) listener);

    }

    if (listener instanceof OnContextClickListener){

        setContextClickListener( (OnContextClickListener) listener);

    }

    init(context);

}

private void init(Context context) {

    if (mListener == null) {

        throw new NullPointerException( "OnGestureListener must not be null");

    }

    mIsLongpressEnabled = true;

    int touchSlop, doubleTapSlop, doubleTapTouchSlop;

    if (context == null) {

        touchSlop = ViewConfiguration. getTouchSlop(); //滑动的时候,滑动数值大于这个值才认为滑动开始

        doubleTapTouchSlop = touchSlop;  //点击的时候,手指移动大于这个距离,就被认为不可能是双击

        doubleTapSlop = ViewConfiguration. getDoubleTapSlop();//双击位置之间的最大距离,大于这个值就不认为是双击

        mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();//手指抛的最小速度

        mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();//手指抛的最大速度

    } else {

        final ViewConfiguration configuration = ViewConfiguration.get(context);

        touchSlop = configuration. getScaledTouchSlop();

        doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();

        doubleTapSlop = configuration. getScaledDoubleTapSlop();

        mMinimumFlingVelocity = configuration. getScaledMinimumFlingVelocity();

        mMaximumFlingVelocity = configuration. getScaledMaximumFlingVelocity();

    }

    mTouchSlopSquare = touchSlop * touchSlop;//做平方好计算距离,后面的距离对比也是用平方

    mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;

    mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;

}

GestureDetector的创建就是初始化一些属性,获取一些数值作为阈值,然后把对应的Listener设置好。此外,注意到初始化了mHandler。mHandler是GestureHandler的一个实例,它是控制onShowPress()、onLongPress()、onSingleTapConfirmed()回调的关键:

private class GestureHandler extends Handler {

   //两个构造方法,一个带参数handler,一个不带

    GestureHandler() {

        super();

    }

    GestureHandler(Handler handler) {

        super(handler.getLooper());

    }

    @Override

    public void handleMessage(Message msg) {

        switch (msg.what) {

            case SHOW_PRESS:

                mListener.onShowPress( mCurrentDownEvent); //回调onShowPress方法

                break;

            case LONG_PRESS:

                dispatchLongPress();//处理长按消息

                break;

            case TAP:

                // 控制SingleTapConfirmed的回调

                if (mDoubleTapListener != null) {

                    if  (!mStillDown) { //此时手指已抬起,回调确认单击的方法

                        mDoubleTapListener. onSingleTapConfirmed(mCurrentDownEvent);

                    } else { //手指未抬起,那么就在手指抬起的时候回调确认单击的方法。也就是说如果处理Message时手指还没松开,就设置mDeferConfirmSingleTap为true,在UP事件的时候调用SingleTapConfirme

                        mDeferConfirmSingleTap = true;

                    }

                }

                break;

            default:

                throw new RuntimeException("Unknown message " + msg); 

        }

    }

}

在handler中调用了dispatchLongPress方法:

private void dispatchLongPress() { //长按处理

    mHandler.removeMessages(TAP);

    mDeferConfirmSingleTap = false;

    mInLongPress = true;

    mListener.onLongPress(mCurrentDownEvent);

 }

②onTouchEvent

这是GestureDetector的核心逻辑,也就是如何处理手势信息,并判断是具体的哪种手势:

public boolean onTouchEvent(MotionEvent ev) {

    if (mInputEventConsistencyVerifier != null) {

        mInputEventConsistencyVerifier. onTouchEvent(ev, 0); //检查事件输入的一致性,log出来一致性的信息,如:有事件只有up没有down

    }

    final int action = ev.getAction();

    if(mCurrentMotionEvent != null) {

        mCurrentMotionEvent.recycle();

    }

   mCurrentMotionEvent=MotionEvent.obtain(ev);

    if (mVelocityTracker == null) {

        mVelocityTracker = VelocityTracker.obtain();

    }

    mVelocityTracker.addMovement(ev); //初始化速度追踪器,并计算速度

   //检测action是否有非主要手指抬起来了,如果是的话,记录它的index,下面计算的时候忽略这个触点

    final boolean pointerUp = (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;

    final int skipIndex = pointerUp ? ev.getActionIndex() : -1;

    final boolean isGeneratedGesture = (ev.get flag & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;

    float sumX = 0, sumY = 0;

    final int count = ev.getPointerCount();

    //下面需要得出x、y方向上的中心焦点。如果是多点触碰,则把所有还在触摸的手指的位置x、y加起来,后面求平均数,算出中心焦点

    for (int i = 0; i < count; i++) {

        if (skipIndex == i) continue; // 非主要手指抬起的话会跳过

        sumX += ev.getX(i);

        sumY += ev.getY(i);

    }

    final int div = pointerUp ? count - 1 : count; //pointerUp为true,需要排除掉

    final float focusX = sumX / div; //中心焦点x

    final float focusY = sumY / div;//中心焦点y

    boolean handled = false;

    switch (action & MotionEvent.ACTION_MASK){

       case MotionEvent.ACTION_POINTER_DOWN:

            //...

            break;

        case MotionEvent.ACTION_POINTER_UP:

            //...

            break;

        case MotionEvent.ACTION_DOWN:

            //...

            break;

        case MotionEvent.ACTION_MOVE:

            //...

            break;

        case MotionEvent.ACTION_UP:

            //...

            break;

        case MotionEvent.ACTION_CANCEL:

            cancel();

            break;

    }

    //对未被处理的事件进行一次一致性检测

    if (!handled && mInputEventConsistencyVerifier != null) {

        mInputEventConsistencyVerifier. onUnhandledEvent(ev, 0);

    }

    return handled;

}

onTouchEvent()的主要思路就是先对输入事件做出统一处理,提取一些共有的信息,如多个点同时触摸时候的中心焦点和滑动速度等,然后根据事件的分类做出相应的处理。

注意:InputEventConsistencyVerifier对输入事件进行的一致性检测的结果并不影响GestureDetector的运行,如果检测到一致性不符合的事件(只有UP事件而前面没有DOWN事件),就只会输出log告诉开发者。

接下来看具体每个event是如何处理的。

③DOWN事件处理

case MotionEvent.ACTION_DOWN:

    if (mDoubleTapListener != null) { //设置了双击监听器

        //第一次点击的时候发送了TAP类型的消息,取消掉,防止出错

        boolean hadTapMessage = mHandler.hasMessages(TAP);

        if (hadTapMessage)

             mHandler.removeMessages(TAP);

       if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && isConsideredDoubleTap( mCurrentDownEvent, mPreviousUpEvent, ev)) { //判断到这是第二次点击

            mIsDoubleTapping = true;

            handled |= mDoubleTapListener.onDou bleTap(mCurrentDownEvent);  //回调双击方法,参数用的是双击里的第一次tap的down事件

            handled |= mDoubleTapListener.onDou bleTapEvent(ev); //回调双击方法,参数用的是双击里第二次tap的down事件

        } else {

            //延时发出单击事件,如果到了时间还没有取消的话就确认是TAP事件了

            mHandler.sendEmptyMessageDelayed( TAP, DOUBLE_TAP_TIMEOUT); //这是第一次的点击,先发送延时消息,如果时间到了(300ms)还没有取消的话(也就是没有再次点击),GestureDetector就会处理这个TAP类型的消息

        }

    }

    mDownFocusX = mLastFocusX = focusX;

    mDownFocusY = mLastFocusY = focusY;

    //重置mCurrentDownEvent

    if (mCurrentDownEvent != null) {

         mCurrentDownEvent.recycle();

    }

    mCurrentDownEvent = MotionEvent.obtain(ev);

    mAlwaysInTapRegion = true;

    mAlwaysInBiggerTapRegion = true;

    mStillDown = true;

    mInLongPress = false;

    mDeferConfirmSingleTap = false;   

    if (mIsLongpressEnabled) {  //允许长按

        mHandler.removeMessages(LONG_PRESS);

        mHandler.sendEmptyMessageAtTime( LONG_PRESS,mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT); //发送延时的长按消息,在TAP_TIMEOUT+ LONGPRESS_TIMEOUT这段时间内,如果没有其他手势操作(移动手指、抬起手指),就会认为是长按手势,并且回调LongPress方法
    } 

    mHandler.sendEmptyMessageAtTime( SHOW_PRESS,mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); //一小段时间后,延时发送showPress事件,反馈给用户

    handled |= mListener.onDown(ev);

    break;

down事件处理完了,里面有一个判断是不是双击的方法isConsideredDoubleTap,该方法用于判断第二次点击是否有效双击:

private boolean isConsideredDoubleTap( MotionEvent firstDown, MotionEvent firstUp, MotionEvent secondDown) {    

    if (!mAlwaysInBiggerTapRegion) { //第一次点击后,超出了限定范围,认为无效

        return false;

    }

    final long deltaTime = secondDown. getEventTime() - firstUp.getEventTime();

    if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) { //第一次点击的手指抬起和第二次点击的手指落下,中间时间间隔不符合要求,无效

        return false;

    }

    int deltaX = (int) firstDown.getX() - (int) secondDown.getX();

    int deltaY = (int) firstDown.getY() - (int) secondDown.getY();

    //判断第二次点击是否在附近,在附近才被认为是双击

    return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);

    }

对DOWN事件的处理逻辑:

1)单击判断:如果收到一次DOWN事件,而且前段时间没有DOWN事件的话,会发送一个延时的TAP信息,一段时间(300ms)之后没有被取消的话,就执行GestureHandler里面的TAP单击确认操作。

2)双击判断:如果前面也有一次DOWN事件,而且符合isConsideredDoubleTap()的条件(第一次点击后没有移动超出范围,第二次点击也在附近),就可以确认双击,执行onDoubleTap()和onDoubleTapEvent()的回调。

3)长按判断:先看用户是否允许检测长按,如果允许长按,在点击的时候就发送一个延时的长按消息,如果时间到了还没被取消的话就回调长按方法。

4)showPress判断:这个和长按差不多,就是时间(100ms)短了一点而已。

注意:handled是boolean变量,|=符号是用符号右边的值跟左边的值进行或运算再赋值给左边。a |= b等价于a = a | b,这里handled变量初始值为false,进行了多次|=操作,一旦有结果是true的话,handled最后的值就是true,所有结果都是false最后才会false,onTouchEvent()方法最后返回这个handled,就可以表示事件有没有被处理,实现了对事件处理的封装。

④MOVE事件处理

case MotionEvent.ACTION_MOVE:

    if (mInLongPress || mInContextClick) {

        break; //已经触发长按操作,一系列事件可以认为是长按,接下来不做其他处理

    }

    final float scrollX = mLastFocusX - focusX;

    final float scrollY = mLastFocusY - focusY;

    if (mIsDoubleTapping) { //双击过程中,会回调onDoubleTapEvent方法

        handled |= mDoubleTapListener. onDoubleTapEvent(ev);

    } else if (mAlwaysInTapRegion) { //在down事件里才会使mAlwaysInTapRegion为true

       final int deltaX=(int) (focusX-mDownFocusX);

       final int deltaY=(int) (focusY-mDownFocusY);

       int distance=(deltaX*deltaX)+(deltaY*deltaY);

        if (distance > mTouchSlopSquare) { //距离超过一个数值,可以认为是滑动,进入Scroll事件

            handled = mListener.onScroll( mCurrentDownEvent, ev, scrollX, scrollY);

            mLastFocusX = focusX;

            mLastFocusY = focusY;

            mAlwaysInTapRegion = false;

            mHandler.removeMessages(TAP);

            mHandler.removeMessages( SHOW_PRESS);

            mHandler.removeMessages( LONG_PRESS);

        }

       if(distance > mDoubleTapTouchSlopSquare){

            //如果移动距离超过允许范围,则不再可能认为移动事件是双击

            mAlwaysInBiggerTapRegion = false;

        }

    } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {

        //后续的Scroll移动,前面的是进入Scroll移动

        handled = mListener.onScroll( mCurrentDownEvent, ev, scrollX, scrollY);

        mLastFocusX = focusX;

        mLastFocusY = focusY;

    }

    break;

    //...

对MOVE事件涉及:

1)onDoubleTapEvent()回调:只要确认是双击之后,mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent()。

2)onScroll()回调:当MOVE不是长按,不是DoubleTapEvent之后,当移动距离大于一定距离之后,就会进入Scroll模式,然后两个MOVE事件的位移距离scrollX或者scrollY大于1px,都会调用onScroll()。

⑤UP事件的处理

case MotionEvent.ACTION_UP:

    mStillDown = false;

    MotionEvent currentUpEvent = MotionEvent.obtain(ev);

    if (mIsDoubleTapping) {

        //双击事件

        handled |= mDoubleTapListener. onDoubleTapEvent(ev);

    } else if (mInLongPress) {

        //长按结束

        mHandler.removeMessages(TAP);

        mInLongPress = false;

    } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {

        handled = mListener.onSingleTapUp(ev);

        //处理单击确认,具体逻辑看GestureHandler如何处理TAP事件

        if (mDeferConfirmSingleTap && mDoubleTapListener != null) {

            mDoubleTapListener.onSingleTapConf irmed(ev);

        }

    } else if (!mIgnoreNextUpEvent) {

        //处理Fling,如果速度大于定义的最小速度(50),就回调Fling

        // A fling must travel the minimum tap distance

        final VelocityTracker velocityTracker = mVelocityTracker;

        final int pointerId = ev.getPointerId(0);

        velocityTracker.computeCurrentVelocity( 1000, mMaximumFlingVelocity);

        final float velocityY = velocityTracker.getYVelocity(pointerId);

        final float velocityX = velocityTracker.getXVelocity(pointerId);

        if ((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity)){

            handled = mListener.onFling( mCurrentDownEvent, ev, velocityX, velocityY);

        }

    }

    //重置mPreviousUpEvent

    if (mPreviousUpEvent != null) {

        mPreviousUpEvent.recycle();

    }

    // Hold the event we obtained above - listeners may have changed the original.

    mPreviousUpEvent = currentUpEvent;

    //回收mVelocityTracker

    if (mVelocityTracker != null) {

        // This may have been cleared when we called out to the

        // application above.

        mVelocityTracker.recycle();

        mVelocityTracker = null;

    }

    mIsDoubleTapping = false;

    mDeferConfirmSingleTap = false;

    mIgnoreNextUpEvent = false;

    mHandler.removeMessages(SHOW_PRESS);

    mHandler.removeMessages(LONG_PRESS);

    break;

    //...

UP事件涉及:

1)onSingleTapUp()回调:DOWN事件之后没有MOVE,或者MOVE的距离没有超出范围,mAlwaysInTapRegion才不会变成false,回调onSingleTapUp()。

2)onSingleTapConfirmed()回调:从前面GestureHandler里面的TAP消息的实现可以看到:

case TAP:

    //这里控制SingleTapConfirmed的回调

    if (mDoubleTapListener != null) {

        if (!mStillDown) {

           //如果已经松开,就立刻调用SingleTapConfirmed

            mDoubleTapListener.onSingleTapConfi rmed( mCurrentDownEvent);

        } else {

            //如果处理Message的时候还没松开,就设置mDeferConfirmSingleTap为true,在UP事件的时候调用SingleTapConfirme

            mDeferConfirmSingleTap = true;

        }

    }

    break;

之前看过,TAP消息是延时(300ms)发送的,然而实际逻辑中,是抬起手指才算是点击,所以这里处理TAP的时候就不一定立刻调用onSingleTapConfirmed(),而是判断手指是否松开了,是松开的话就立刻回调。如果还未松开那就把标志位mDeferConfirmSingleTap设置为true,等到收到UP事件的时候再回调。

3)onFling()回调:当UP事件的速度大于一定速度时,就会回调onFling(),至于mIgnoreNextUpEvent参数,是只有鼠标右键点击的时候才会为true。

⑥多点触摸的处理

对于多个手指落下,前面的统一处理已经是把所有手指的坐标值加起来然后算平均值了,所以多手指触摸的时候,其实滑动的实际点是这些手指的中心焦点。回看上面的MOVE事件处理,中心焦点的位置值FocusX和FocusY决定onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)的后两个参数值,所以处理多点触控的话就使用它们比较方便。因为MotionEvent是使用数组装着当前屏幕上所有指针的动作的,使用前两个参数的话还要循环用getX(int pointerIndex)和getY(int pointerIndex)方法取出各个指针的值再自己处理。

case MotionEvent.ACTION_POINTER_DOWN:

    mDownFocusX = mLastFocusX = focusX;

    mDownFocusY = mLastFocusY = focusY;

    //如果有多根手指按下,取消长按和点击计时

    cancelTaps();

    break;

case MotionEvent.ACTION_POINTER_UP:

    mDownFocusX = mLastFocusX = focusX;

    mDownFocusY = mLastFocusY = focusY;

    //计算每一秒钟的滑动像素

    mVelocityTracker.computeCurrentVelocity( 1000, mMaximumFlingVelocity);

    final int upIndex = ev.getActionIndex();

    final int id1 = ev.getPointerId(upIndex);

    final float x1 = mVelocityTracker. getXVelocity(id1);

    final float y1 = mVelocityTracker. getYVelocity(id1);

    //如果剩下的手指速度方向是和抬起那根手指的速度相反方向的,就说明不是fling,清空速度监听

    for (int i = 0; i < count; i++) {

        if (i == upIndex) continue;

        final int id2 = ev.getPointerId(i);

        final float x = x1 * mVelocityTracker. getXVelocity(id2);

        final float y = y1 * mVelocityTracker. getYVelocity(id2);

        final float dot = x + y;

        if (dot < 0) {

            mVelocityTracker.clear();

            break;

        }

    }

    break;

    //...

private void cancelTaps() {

    mHandler.removeMessages(SHOW_PRESS);

    mHandler.removeMessages(LONG_PRESS);

    mHandler.removeMessages(TAP);

    mIsDoubleTapping = false;

    mAlwaysInTapRegion = false;

    mAlwaysInBiggerTapRegion = false;

    mDeferConfirmSingleTap = false;

    mInLongPress = false;

    mInContextClick = false;

    mIgnoreNextUpEvent = false;

}   

这是对多个手指的UP和DOWN事件处理,其实就是做一些取消操作而让多点触摸不影响单点触摸的应用,例如在多个手指落下的时候取消点击信息等。

⑦ContextClick的处理

ContextClick的事件是由onGenericMotionEvent()传入:

public boolean onGenericMotionEvent( MotionEvent ev) {

    if (mInputEventConsistencyVerifier != null) {

        mInputEventConsistencyVerifier. onGenericMotionEvent(ev, 0);

    }

    final int actionButton = ev.getActionButton();

    switch (ev.getActionMasked()) {

        case MotionEvent.ACTION_BUTTON_PRESS:

            //按下触控笔首选按钮或者鼠标右键

            if (mContextClickListener != null && !mInContextClick && !mInLongPress && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY || actionButton == MotionEvent.BUTTON_SECONDARY)) {

                if (mContextClickListener. onContextClick(ev)) {

                    mInContextClick = true;

                    mHandler.removeMessages( LONG_PRESS);

                    mHandler.removeMessages(TAP);

                    return true;

                }

            }

            break;

        case MotionEvent.ACTION_BUTTON_RELEASE:

            if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY || actionButton == MotionEvent.BUTTON_SECONDARY)) {

                mInContextClick = false;

                //无视下一个UP事件,因为它是由鼠标右键或者触控笔键带起的

                mIgnoreNextUpEvent = true;

            }

            break;

    }

    return false;

}

由此可以,当按键按下(ACTION_BUTTON_PRESS)的时候已经回调onContextClick()。

 

4.ScaleGestureDetector

ScaleGestureDetector是用于处理缩放的工具类,用法与GestureDetector类似,都是通过onTouchEvent()关联相应的MotionEvent事件。

ScaleGestureDetector类提供了OnScaleGestureListener接口,处理缩放过程中的一些回调。

public interface OnScaleGestureListener {

    //缩放进行中,返回值表示是否下次缩放需要重置,如果返回ture,那么detector就会重置缩放事件,如果返回false,detector会在之前的缩放上继续进行计算

    public boolean onScale( ScaleGestureDetector detector);

    //缩放开始,返回值表示是否受理后续的缩放事件

    public boolean onScaleBegin( ScaleGestureDetector detector);

    //缩放结束

    public void onScaleEnd(ScaleGestureDetector detector);

}

①onScaleBegin

当一个缩放手势开始时触发该事件,即双指按下屏幕时。该方法的返回值为boolean,主要用来判断探测器是否应该继续识别处理这个手势。例如,如果在屏幕上的某个区域内不需要识别缩放手势,当手势的焦点在该区域内时,onScaleBegin()可以返回false忽略不处理该手势的接下来的一系列事件。

②onScale

当一个缩放手势正在进行中时持续触发该事件,即双指正在屏幕上滑动。该方法的返回值为boolean,主要用来判断是否结束当前缩放手势事件,即本次缩放事件是否已被处理。如果已被处理,那么detector就会重置缩放事件;如果未被处理,detector会继续进行计算,修改getScaleFactor()的返回值,直到被处理为止。当返回true时,则结束当前缩放手势的一系列事件,缩放因子将会初始化为1.0,当返回false时,当前缩放手势的一系列事件继续进行,缩放因子不会被初始化,而是根据手势不断的持续变化。返回值可以用来限制缩放值的最大比例上限和最小比例下限。

③onScaleEnd

当一个缩放手势结束后触发该事件,即双指抬起离开屏幕时。

 

在缩放过程中,要通过ScaleGestureDetector来获取一些缩放信息。ScaleGestureDetector类提供了常用函数:

public boolean isInProgress(); //缩放是否正处在进行中

public float getFocusX(); //返回组成缩放手势(两个手指)中点x的位置

public float getFocusY(); //返回组成缩放手势(两个手指)中点y的位置

public float getCurrentSpan();组成缩放手势的两个触点的跨度(两个触点间的距离)

public float getCurrentSpanX(); //同上,x的距离

public float getCurrentSpanY(); //同上,y的距离

public float getPreviousSpan();组成缩放手势的两个触点的前一次缩放的跨度(两个触点间的距离)

public float getPreviousSpanX(); //同上,x的距离

public float getPreviousSpanY(); //同上,y的距离

public float getScaleFactor(); //获取本次缩放事件的缩放因子,缩放事件以onScale()返回值为基准,一旦该方法返回true,代表本次事件结束,重新开启下次缩放事件。

getScaleFactor返回从之前的缩放手势和当前的缩放手势两者的比例因子,即缩放值,默认1.0。当手指做缩小操作时,该值小于1.0,当手指做放大操作时,该值大于1.0。在每一个缩放事件中都会从1.0开始变化,如果需要做持续性操作,则需要保存上一次的伸缩值,然后当前的伸缩值*上一次的伸缩值,得到连续变化的伸缩值,例如有3次事件发生,没连续性则是缩小0.9->缩小0.9->缩小0.9,如果做了连续性处理即保存上一次的伸缩值,则是缩小0.9->缩小0.9*0.9=0.81->缩小0.81*0.9=0.72,有了逐渐变小的连续性。

public long getTimeDelta(); //返回上次缩放事件结束时到当前的时间间隔

public long getEventTime(); //获取当前motion事件的时间

 

5.ScaleGestureDetector使用

ScaleGestureDetector的使用很简单:

①定义ScaleGestureDetector类。

②将touch事件交给Scale GestureDetector,即在onTouchEvent函数里面调用ScaleGestureDetector的onTouchEvent函数。

③处理OnScaleGestureListener的各个回调。

注意:

①重写的三个回调方法里,如果在onScaleBegin里返回false,则永远不会回调onScale方法。

②如果onScaleBegin返回true,onScale返回false,则一次缩放手势里会回调一次onScaleBegin,回调多次onScale。

 

6.ScaleGestureDetector源码分析

ScaleGestureDetector的源码比GestureDetector的源码要稍微复杂点,因为ScaleGestureDetector的事件涉及到多个手指。

想要缩放的值,所有的MotionEvent事件都要交给ScaleGestureDetector的onTouchEvent()函数,所以直接来看onTouchEvent()函数大概的逻辑。

public boolean onTouchEvent(MotionEvent event) {

    ...

    // 缩放的手势需要多个手指来完成,count表示 手指的个数

    final int count = event.getPointerCount();

    ...

    // streamComplete表示当前事件流是否完成

    final boolean streamComplete = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;

    if (action == MotionEvent.ACTION_DOWN || streamComplete) {

        // mInProgress表示是否进行缩放,这里是停掉上一次的缩放调用onScaleEnd()

        if (mInProgress) {

            mListener.onScaleEnd(this);

            mInProgress = false;

            mInitialSpan = 0;

            mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;

        } else if (inAnchoredScaleMode() && streamComplete) {

            mInProgress = false;

            mInitialSpan = 0;

            mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;

        }

        if (streamComplete) {

            return true;

        }

    }

    ...

    if (inAnchoredScaleMode()) {

        ...

    } else {

        // 所有手指的距离相加

        for (int i = 0; i < count; i++) {

            if (skipIndex == i) continue;

            sumX += event.getX(i);

            sumY += event.getY(i);

        }

        // 所有手指的中心点

        focusX = sumX / div;

        focusY = sumY / div;

    }

    // Determine average deviation from focal point

    float devSumX = 0, devSumY = 0;

    for (int i = 0; i < count; i++) {

        if (skipIndex == i) continue;

        // 所有手指相对于中心点(所有手指的中心点)的距离之和

        devSumX += Math.abs(event.getX(i) - focusX);

        devSumY += Math.abs(event.getY(i) - focusY);

    }

    // 所有手指相对于中心点的平均值

    final float devX = devSumX / div;

    final float devY = devSumY / div;

    // *2 相当于是两个手指之间的距离跨度

    final float spanX = devX * 2;

    final float spanY = devY * 2;

    final float span;

    if (inAnchoredScaleMode()) {

        span = spanY;

    } else {

        span = (float) Math.hypot(spanX, spanY);

    }

    ...

    final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;

    // 回调onScaleBegin(),返回值表示是否开始缩放

    if (!mInProgress && span >= minSpan &&(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {

        mPrevSpanX = mCurrSpanX = spanX;

        mPrevSpanY = mCurrSpanY = spanY;

        mPrevSpan = mCurrSpan = span;

        mPrevTime = mCurrTime;

        mInProgress = mListener.onScaleBegin(this);

    }

    // 回调onScale(),如果onScale()返回true,则重新保存mPrevSpanX,mPrevSpanY,mPrevSpan,mPrevTime

    if (action == MotionEvent.ACTION_MOVE) {

        mCurrSpanX = spanX;

        mCurrSpanY = spanY;

        mCurrSpan = span;

        boolean updatePrev = true;

        if (mInProgress) {

            updatePrev = mListener.onScale(this);

        }

        if (updatePrev) {

            mPrevSpanX = mCurrSpanX;

            mPrevSpanY = mCurrSpanY;

            mPrevSpan = mCurrSpan;

            mPrevTime = mCurrTime;

        }

    }

    return true;

}

onTouchEvent()函数里面,要注意onScale()的返回值为true的时候mPrevSpanX,mPrevSpanY,mPrevSpan,mPrevTime这些才会改变。

再看下缩放过程中的缩放因子是怎么计算到的。getScaleFactor()函数:

public float getScaleFactor() {

    ...

    return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;

}

用当前两个手指之间的跨度除以上一次记录的两个手指之间的跨度。同时也注意到上面讲到的onTouchEvent()函数里面onScale()返回true的时候mPrevSpan才会重新赋值。比如两个手指放在屏幕上,手指慢慢的拉开。假设回调过程中onScale()函数每次返回的是true,每次onScale()之后getScaleFactor()会重新去计算缩放因子,但是如果onScale()函数返回的是false,getScaleFactor()的返回值是一直增大的。

 

7.同时使用ScaleGestureDetector.SimpleOnScaleGestureListener和GestureDetector.SimpleOnGestureListener 

@Override

public boolean onTouchEvent(MotionEvent event) {

    boolean res = mScaleGestureDetector.onTouchEvent(event);

    if (!mScaleGestureDetector.isInProgress()) {

        res = mGestureDetector.onTouchEvent(event);

    }

    return res;

}

思路就是首先判断是不是在缩放过程中,如果不是缩放,才去执行mGestureDetector.onTouchEvent(event);

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zenmela2011/article/details/123155093

智能推荐

在移动硬盘中安装win10和macos双系统-程序员宅基地

文章浏览阅读1.1k次,点赞22次,收藏23次。本文通过在SSD移动硬盘中安装win10和macos双系统,实现操作系统随身携带小慢哥的原创文章,欢迎转载目录 目标 准备工作 Step1. 清空分区,转换为GPT Step2. 安装win10 Step3. 压缩win10分区容量 Step4. 创建2个分区 Step5. 将bootcamp驱动放置到exFAT分区中 Step6. 将macos分区..._mac移动硬盘装双机系统

TransmittableThreadLocal解决线程池本地变量问题,原来我一直理解错了-程序员宅基地

文章浏览阅读14次。theme: cyanosishighlight: a11y-dark前言自从上次TransmittableThreadLocal框架作者评论我之后,我重新去看了下源码,终于在这个周天,我才把TransmittableThreadLocal解决线程池变量丢失的问题搞明白,而且发现我之前的认识有问题,久久孩子我之前是觉得,InheritableThreadLocal解决父子线...

Exchange 2016部署实施案例篇-03.Exchange部署篇(上)-程序员宅基地

文章浏览阅读366次。  距离上一篇《Exchange 2016部署实施案例篇-02.活动目录部署篇》博文更新已经过去快一周了,最近一直在忙项目上的事情和软考,整的真心有点身心俱疲啊,最近看了下上一篇博文不知道为什么访问量一直上不去,真心有点心寒啊。希望大家能多多提出宝贵意见,看看如何能让访问量上去。  废话就不多说了,开始今天的话题,Exchange的部署篇,我原定计划是把部署篇分上、下2个篇幅来写的,但最近发现好..._解决exchange2016部署先决条件

[译]使用MVI打造响应式APP(四):独立性UI组件-程序员宅基地

文章浏览阅读130次。原文:REACTIVE APPS WITH MODEL-VIEW-INTENT - PART4 - INDEPENDENT UI COMPONENTS作者:Hannes Dorfmann译者:却把清梅嗅这篇博客中,我们将针对如何 如何构建独立组件 进行探讨,我将阐述为什么在我看来 父子关系会导致坏味道的代码,以及为何这种关系是没有意义的。有这样一个问题时不时涌现在我的脑海中—— MVI...

tensorflow经过卷积及池化层后特征图的大小计算_池化层后特征图尺寸-程序员宅基地

文章浏览阅读662次。https://blog.csdn.net/qq_32466233/article/details/81075288_池化层后特征图尺寸

使用vue-echarts异步数据加载,不能重新渲染页面问题。_vue echart初始化渲染过后无法重新渲染-程序员宅基地

文章浏览阅读3.3k次。一、问题说明我是用的是官方示例中的这个饼状图。结果在应用到项目中后发现利用axios请求到的数据无法渲染到页面中去。并且其中value值已经改变。二、解决办法用$set改变value的值,并且重新绘制一遍表格。$set是全局 Vue.set 的别名。$set用法:向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为..._vue echart初始化渲染过后无法重新渲染

随便推点

Dev-C++ “to_string is not a member of std” error- 已解决_devc++ [error] 'to_string' is not a member of 'std-程序员宅基地

文章浏览阅读3.7k次。今天在用Dev-C++ 的时候遇到一个错误“to_string is not a member of std” error解决方法:设置编译语言为ISO C++11 在菜单栏的Tool -> Compiler Option_devc++ [error] 'to_string' is not a member of 'std

python的10款最好的IDE_pydea兼容的-程序员宅基地

文章浏览阅读1.1k次。Python 非常易学,强大的编程语言。Python 包括高效高级的数据结构,提供简单且高效的面向对象编程。Python 的学习过程少不了 IDE 或者代码编辑器,或者集成的开发编辑器(IDE)。这些 Python 开发工具帮助开发者加快使用 Python 开发的速度,提高效率。高效的代码编辑器或者 IDE 应该会提供插件,工具等能帮助开发者高效开发的特性。这篇文章收集了一些对开发者非常有_pydea兼容的

python translate函数_Python:内置函数makestrans()、translate()-程序员宅基地

文章浏览阅读287次。一、makestrans()格式: str.maketrans(intab,outtab);功能:用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。注:两个字符串的长度必须相同,为一一对应的关系。注:Python3.6中已经没有string.maketrans()了,取而代之的是内建函数:bytearray...._python maketrance

Set集合详解-程序员宅基地

文章浏览阅读5.7k次,点赞9次,收藏14次。set集合的简介,它的特点和遍历方式。介绍了HashSet重复元素存储底层原理,LinkedHashSet,TreeSet排序方法,SortedSet获取集合值的方法_set集合

详解智慧城市排水管理系统整体方案_污水处理智慧管理系统案列-程序员宅基地

文章浏览阅读3.6k次,点赞3次,收藏29次。随着城市规模的不断扩大和现代化程度的日益提高,城市排水管网越来越复杂,一些城市相继发生大雨内涝、管线泄漏爆炸、路面塌陷等事件,严重影响了人民群众生命财产安全和城市运行秩序。因此,摸清排水管网设施资产家底、建立排水管网地理信息系统,用现代化的技术手段对排水系统进行科学管理显得迫在眉睫。以时空信息为基础,充分利用感知监测网、物联网、云计算、移动互联网、工业控制和水力模型等新一代信息技术,全方位感..._污水处理智慧管理系统案列

详解NTFS文件系统_ntfs文件系统中,磁盘上的所有数据包括源文件都是以什么的形式存储-程序员宅基地

文章浏览阅读5.7k次,点赞4次,收藏13次。上篇在详解FAT32文件系统中介绍了FAT32文件系统存储数据的原理,这篇就来介绍下NTFS文件系统。NTFS、用过Windows系统的人都知道,它是一个很强大的文件系统,支持的功能很多,存储的原理也很复杂。目前绝大多数Windows用户都是使用NTFS文件系统,它主要以安全性和稳定性而闻名,下面是它的一些主要特点。安全性高:NTFS支持基于文件或目录的ACL,并且支持加密文件系统(E_ntfs文件系统中,磁盘上的所有数据包括源文件都是以什么的形式存储

推荐文章

热门文章

相关标签