全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

Android手势ImageView三部曲 第三部

接着上一节 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上大神已经实现了,但是如果没有的话,你能写出来么?

先写到这,未完待续。

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


# Android  # 手势  # ImageView  # Android手势ImageView三部曲 第二部  # Android手势ImageView三部曲 第一部  # Android自定义GestureDetector实现手势ImageView  # Android使用ImageView实现支持手势缩放效果  # Android ImageView随手势变化动态缩放图片  # Android手势滑动实现ImageView缩放图片大小  # Android实现手势控制ImageView图片大小  # Android通过手势实现的缩放处理实例代码  # android开发之为activity增加左右手势识别示例  # android使用gesturedetector手势识别示例分享  # 回调  # 自己的  # 按下  # 自带  # 小伙伴  # 创建一个  # 的是  # 实现了  # 多点  # 多个  # 中有  # 坐标值  # 会在  # 不太好  # 你能  # 大神  # 如果没有  # 我以  # 较多  # 写了 


相关文章: 网站设计制作公司地址,网站建设比较好的公司都有哪些?  如何有效防御Web建站篡改攻击?  制作网页的网站有哪些,电脑上怎么做网页?  如何挑选高效建站主机与优质域名?  如何用免费手机建站系统零基础打造专业网站?  建站上传速度慢?如何优化加速网站加载效率?  mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?  建站之星备案流程有哪些注意事项?  网站制作壁纸教程视频,电脑壁纸网站?  如何快速重置建站主机并恢复默认配置?  C#怎么使用委托和事件 C# delegate与event编程方法  建站之星代理如何获取技术支持?  如何高效完成独享虚拟主机建站?  东莞专业制作网站的公司,东莞大学生网的网址是什么?  建站之星在线客服如何快速接入解答?  如何在阿里云高效完成企业建站全流程?  建站之星安装失败:服务器环境不兼容?  如何快速生成高效建站系统源代码?  ,巨量百应是干嘛的?  公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?  c# 在高并发下使用反射发射(Reflection.Emit)的性能  电商平台网站制作流程,电商网站如何制作?  建站主机SSH密钥生成步骤及常见问题解答?  弹幕视频网站制作教程下载,弹幕视频网站是什么意思?  单页制作网站有哪些,朋友给我发了一个单页网站,我应该怎么修改才能把他变成自己的呢,请求高手指点迷津?  如何在万网开始建站?分步指南解析  C++如何编写函数模板?(泛型编程入门)  Android使用GridView实现日历的简单功能  婚礼视频制作网站,学习*后期制作的网站有哪些?  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?  淘宝制作网站有哪些,淘宝网官网主页?  建站之星导航配置指南:自助建站与SEO优化全解析  家庭建站与云服务器建站,如何选择更优?  专业网站制作企业网站,如何制作一个企业网站,建设网站的基本步骤有哪些?  如何用5美元大硬盘VPS安全高效搭建个人网站?  如何在Golang中使用replace替换模块_指定本地或远程路径  专业公司网站制作公司,用什么语言做企业网站比较好?  如何在Windows服务器上快速搭建网站?  如何使用Golang安装API文档生成工具_快速生成接口文档  网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?  网站制作知乎推荐,想做自己的网站用什么工具比较好?  网站制作软件免费下载安装,有哪些免费下载的软件网站?  如何零基础在云服务器搭建WordPress站点?  如何通过西部建站助手安装IIS服务器?  建站之星代理平台如何选择最佳方案?  建站主机选择指南:服务器配置与SEO优化实战技巧  黑客如何通过漏洞一步步攻陷网站服务器?  Android自定义控件实现温度旋转按钮效果  中山网站制作网页,中山新生登记系统登记流程? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。