欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android手势ImageView三部曲 第三部

程序员文章站 2023-12-03 08:23:28
接着上一节 android手势imageview三部曲(二)的往下走,我们讲到了github上的gesturedetector框架, 先附上github链接: ht...

接着上一节 android手势imageview三部曲(二)的往下走,我们讲到了github上的gesturedetector框架,
先附上github链接:
https://github.com/almeros/android-gesture-detectors
其实把这个框架的主体思想也是参考的android自带的scalegesturedetector工具类,scalegesturedetector估计是参考的gesturedetector工具类,不管谁参考谁的,既然被我们遇到了,我们就要变成自己的东西,真不能全变成自己的东西的话,至少

我们要了解下它的思想。

我们先了解一下android自带的scalegesturedetector(缩放手势监测器):

scalegesturedetector跟gesturedetector构造都差不多,但是scalegesturedetector只能用于监测缩放的手势,而gesturedetector监测的手势就比较多了,我们上一节内容中有提到。

scalegesturedetector的一些用法跟api,小伙伴自行去查看官网文档:
https://developer.android.google.cn/reference/android/view/scalegesturedetector.html

我们怎么使用它呢(我以第一节中最后一个demo为例)?

首先创建一个scalegesturedetector对象:

 private void initview() {
    ....
    mscaledetector = new scalegesturedetector(getcontext(), new scalelistener());
    ....

  }

然后传递一个叫scalelistener的回调接口给它,scalelistener里面有三个回调方法:

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

小伙伴应该看得懂哈,就是onscale放缩时回调,onscalebegin缩放开始时回调,onscaleend缩放完毕后回调。

最后在view的ontouchevent方法中把事件给scalegesturedetector对象:

@override
  public boolean ontouchevent(motionevent event) {
    //把缩放事件给mscaledetector
    mscaledetector.ontouchevent(event);
    return true;
  }
好啦~!!上

一节最后的时候,我写了一个小demo去实现了图片的位移,下面我们继续加上图片的缩放:

public class matriximageview extends imageview {
  private matrix currmatrix;
  private gesturedetector detector;
  private scalegesturedetector scaledetector;
  private float currx;//当前图片的x坐标值
  private float curry;//当前图片的y坐标值
  private float scalefactor=1f;//当前图片的缩放值
  public matriximageview(context context, attributeset attrs) {
    super(context, attrs);
    initview();
    detector=new gesturedetector(context,ongesturelistener);
    //创建一个缩放手势监测器
    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();
      /**
       * 因为getscalefactor=当前两个手指之间的距离(preevent)/手指按下时候两个点的距离(currevent)
       * 这里如果返回true的话,会在move操作的时候去更新之前的event,
       * 如果为false的话,不会去更新之前按下时候保存的event
       */
      return true;
    }
  };
}

尴尬了,模拟器不太好用于两个手指缩放的录制,所以效果小伙伴自己拿到代码运行一下哈~!!!

下面一起撸一撸scalegesturedetector的源码:
我们知道,scalegesturedetector核心代码也就是ontouchevent,于是我们点开ontouchevent:

@suppresslint("newapi")
  public boolean ontouchevent(motionevent event) {
    //获取当前event事件
    mcurrtime = event.geteventtime();

    final int action = event.getactionmasked();

    // forward the event to check for double tap gesture
    if (mquickscaleenabled) {
      mgesturedetector.ontouchevent(event);
    }
    //获取手指个数
    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;
    //手指按下的时候
    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;


    //处理多点之间距离
    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;
  }

一堆代码,数学不太好的看起来还真比较艰难,大概就是根据多个触碰点的x坐标算出一个x轴平均值,然后y轴也一样,然后通过math.hypot(spanx, spany);算出斜边长,斜边长即为两点之间的距离,然后保存当前的span跟移动过后的span。

最后当我们调用getscalefactor获取缩放比例的时候,即用现在的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;
  }

这数学渣真的是硬伤啊~~~

有了android自带的scalegesturedetector作为参考,我们能自己实现scalegesturedetector吗?? 当然github上大神已经实现了,但是如果没有的话,你能写出来么?

先写到这,未完待续。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。