溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android手勢ImageView三部曲 第三部

發(fā)布時(shí)間:2020-09-30 12:58:03 來源:腳本之家 閱讀:273 作者:vv_小蟲 欄目:移動(dòng)開發(fā)

接著上一節(jié) Android手勢ImageView三部曲(二)的往下走,我們講到了github上的GestureDetector框架,
先附上github鏈接:
https://github.com/Almeros/android-gesture-detectors
其實(shí)把這個(gè)框架的主體思想也是參考的Android自帶的ScaleGestureDetector工具類,ScaleGestureDetector估計(jì)是參考的GestureDetector工具類,不管誰參考誰的,既然被我們遇到了,我們就要變成自己的東西,真不能全變成自己的東西的話,至少

我們要了解下它的思想。

我們先了解一下android自帶的ScaleGestureDetector(縮放手勢監(jiān)測器):

ScaleGestureDetector跟GestureDetector構(gòu)造都差不多,但是ScaleGestureDetector只能用于監(jiān)測縮放的手勢,而GestureDetector監(jiān)測的手勢就比較多了,我們上一節(jié)內(nèi)容中有提到。

ScaleGestureDetector的一些用法跟api,小伙伴自行去查看官網(wǎng)文檔:
https://developer.android.google.cn/reference/android/view/ScaleGestureDetector.html

我們怎么使用它呢(我以第一節(jié)中最后一個(gè)demo為例)?

首先創(chuàng)建一個(gè)ScaleGestureDetector對象:

 private void initView() {
    ....
    mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    ....

  }

然后傳遞一個(gè)叫ScaleListener的回調(diào)接口給它,ScaleListener里面有三個(gè)回調(diào)方法:

 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
      mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
      // Don't let the object get too small or too large.
      mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
      changeMatrix();
      return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
      return super.onScaleBegin(detector);
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
      super.onScaleEnd(detector);
    }
  }

小伙伴應(yīng)該看得懂哈,就是onScale放縮時(shí)回調(diào),onScaleBegin縮放開始時(shí)回調(diào),onScaleEnd縮放完畢后回調(diào)。

最后在view的onTouchEvent方法中把事件給ScaleGestureDetector對象:

@Override
  public boolean onTouchEvent(MotionEvent event) {
    //把縮放事件給mScaleDetector
    mScaleDetector.onTouchEvent(event);
    return true;
  }
好啦~??!上

一節(jié)最后的時(shí)候,我寫了一個(gè)小demo去實(shí)現(xiàn)了圖片的位移,下面我們繼續(xù)加上圖片的縮放:

public class MatrixImageView extends ImageView {
  private Matrix currMatrix;
  private GestureDetector detector;
  private ScaleGestureDetector scaleDetector;
  private float currX;//當(dāng)前圖片的x坐標(biāo)值
  private float currY;//當(dāng)前圖片的y坐標(biāo)值
  private float scaleFactor=1f;//當(dāng)前圖片的縮放值
  public MatrixImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView();
    detector=new GestureDetector(context,onGestureListener);
    //創(chuàng)建一個(gè)縮放手勢監(jiān)測器
    scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
  }

  private void initView() {
    currMatrix = new Matrix();
    DisplayMetrics dm = getResources().getDisplayMetrics();
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
    bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
    setImageBitmap(bitmap);
  }
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    detector.onTouchEvent(event);
    //把事件給scaleDetector
    scaleDetector.onTouchEvent(event);
    return true;
  }
  private void setMatrix(){
    currMatrix.reset();
    currMatrix.postTranslate(currX,currY);
    currMatrix.postScale(scaleFactor,scaleFactor,getMeasuredWidth()/2,getMeasuredHeight()/2);
    setImageMatrix(currMatrix);
  }
  private GestureDetector.SimpleOnGestureListener onGestureListener=new GestureDetector.SimpleOnGestureListener(){
    @Override
    public boolean onDown(MotionEvent e) {
      return true;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
      currX-=distanceX;
      currY-=distanceY;
      setMatrix();
      return super.onScroll(e1, e2, distanceX, distanceY);
    }

  };
  private ScaleGestureDetector.SimpleOnScaleGestureListener onScaleGestureListener=new ScaleGestureDetector.SimpleOnScaleGestureListener(){
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
      scaleFactor*=detector.getScaleFactor();
      setMatrix();
      /**
       * 因?yàn)間etScaleFactor=當(dāng)前兩個(gè)手指之間的距離(preEvent)/手指按下時(shí)候兩個(gè)點(diǎn)的距離(currEvent)
       * 這里如果返回true的話,會(huì)在move操作的時(shí)候去更新之前的event,
       * 如果為false的話,不會(huì)去更新之前按下時(shí)候保存的event
       */
      return true;
    }
  };
}

尷尬了,模擬器不太好用于兩個(gè)手指縮放的錄制,所以效果小伙伴自己拿到代碼運(yùn)行一下哈~!??!

下面一起擼一擼ScaleGestureDetector的源碼:
我們知道,ScaleGestureDetector核心代碼也就是onTouchEvent,于是我們點(diǎn)開onTouchEvent:

@SuppressLint("NewApi")
  public boolean onTouchEvent(MotionEvent event) {
    //獲取當(dāng)前event事件
    mCurrTime = event.getEventTime();

    final int action = event.getActionMasked();

    // Forward the event to check for double tap gesture
    if (mQuickScaleEnabled) {
      mGestureDetector.onTouchEvent(event);
    }
    //獲取手指個(gè)數(shù)
    final int count = event.getPointerCount();
    final boolean isStylusButtonDown =
        (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;

    final boolean anchoredScaleCancelled =
        mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
    final boolean streamComplete = action == MotionEvent.ACTION_UP ||
        action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
    //手指按下的時(shí)候
    if (action == MotionEvent.ACTION_DOWN || streamComplete) {
      // Reset any scale in progress with the listener.
      // If it's an ACTION_DOWN we're beginning a new event stream.
      // This means the app probably didn't give us all the events. Shame on it.
      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 (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()
        && !streamComplete && isStylusButtonDown) {
      // Start of a button scale gesture
      mAnchoredScaleStartX = event.getX();
      mAnchoredScaleStartY = event.getY();
      mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
      mInitialSpan = 0;
    }

    final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
        action == MotionEvent.ACTION_POINTER_UP ||
        action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;

    final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
    final int skipIndex = pointerUp ? event.getActionIndex() : -1;


    //處理多點(diǎn)之間距離
    float sumX = 0, sumY = 0;
    final int div = pointerUp ? count - 1 : count;
    final float focusX;
    final float focusY;
    if (inAnchoredScaleMode()) {
      // In anchored scale mode, the focal pt is always where the double tap
      // or button down gesture started
      focusX = mAnchoredScaleStartX;
      focusY = mAnchoredScaleStartY;
      if (event.getY() < focusY) {
        mEventBeforeOrAboveStartingGestureEvent = true;
      } else {
        mEventBeforeOrAboveStartingGestureEvent = false;
      }
    } 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;

      // Convert the resulting diameter into a radius.
      devSumX += Math.abs(event.getX(i) - focusX);
      devSumY += Math.abs(event.getY(i) - focusY);
    }
    final float devX = devSumX / div;
    final float devY = devSumY / div;

    final float spanX = devX * 2;
    final float spanY = devY * 2;
    final float span;
    if (inAnchoredScaleMode()) {
      span = spanY;
    } else {
      span = (float) Math.hypot(spanX, spanY);
    }

    // Dispatch begin/end events as needed.
    // If the configuration changes, notify the app to reset its current state by beginning
    // a fresh scale event stream.
    final boolean wasInProgress = mInProgress;
    mFocusX = focusX;
    mFocusY = focusY;
    if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
      mListener.onScaleEnd(this);
      mInProgress = false;
      mInitialSpan = span;
    }
    if (configChanged) {
      mPrevSpanX = mCurrSpanX = spanX;
      mPrevSpanY = mCurrSpanY = spanY;
      mInitialSpan = mPrevSpan = mCurrSpan = span;
    }

    final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
    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);
    }

    // Handle motion; focal point and span/scale factor are changing.
    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;
  }

一堆代碼,數(shù)學(xué)不太好的看起來還真比較艱難,大概就是根據(jù)多個(gè)觸碰點(diǎn)的x坐標(biāo)算出一個(gè)x軸平均值,然后y軸也一樣,然后通過Math.hypot(spanX, spanY);算出斜邊長,斜邊長即為兩點(diǎn)之間的距離,然后保存當(dāng)前的span跟移動(dòng)過后的span。

最后當(dāng)我們調(diào)用getScaleFactor獲取縮放比例的時(shí)候,即用現(xiàn)在的span/之前的span:

public float getScaleFactor() {
    if (inAnchoredScaleMode()) {
      // Drag is moving up; the further away from the gesture
      // start, the smaller the span should be, the closer,
      // the larger the span, and therefore the larger the scale
      final boolean scaleUp =
          (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||
              (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));
      final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
      return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
    }
    return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
  }

這數(shù)學(xué)渣真的是硬傷啊~~~

有了android自帶的ScaleGestureDetector作為參考,我們能自己實(shí)現(xiàn)ScaleGestureDetector嗎?? 當(dāng)然github上大神已經(jīng)實(shí)現(xiàn)了,但是如果沒有的話,你能寫出來么?

先寫到這,未完待續(xù)。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI