Demo:双向滑动的 ScalableImageView

初始页面

1.实现图片放大

两种情况,通过view和图片的宽高比来判断

public void scale (float sx, float sy) ;//以(0,0)为中心点,将画布长宽分别变为原来的sx/sy倍
public final void scale (float sx, float sy, float px, float py); //以(px,py)为中心点,将画布长宽分别变为原来的sx/sy倍


代码

smallScale效果

2.实现双击放大

2.1使用GestureDetector

private gestureDetector = GestureDetectorCompat(context,gestureListener)

⽤于在点击和⻓按之外,增加其他⼿势的监听,例如双击、滑动。通过在View.onTouchEvent() ⾥调⽤ GestureDetector.onTouchEvent() ,以代理的形式来实现:

override fun onTouchEvent(event: MotionEvent): Boolean {
 return gestureDetector.onTouchEvent(event)
}

GestureDetector的几个回调方法


\

2.2通过GestureDetector.setOnDoubleTapListener(OnDoubleTapListener)来配置双击功能:

gestureDetector.setOnDoubleTapListener(doubleTapListener);

(但其实因为GestureDetector的内部实现导致不用实现该接口)
所以总体代码为:

  private val gestureDetector: GestureDetector = GestureDetector(
    context, object :GestureDetector.OnGestureListener {
      override fun onDown(e: MotionEvent?): Boolean {
        return true
      }

      override fun onShowPress(e: MotionEvent?) {

      }

      override fun onSingleTapUp(e: MotionEvent?): Boolean {
        return true
      }


// distanceX: Float为当前事件的点 - 上次事件的点
      override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent?,
        distanceX: Float,
        distanceY: Float
      ): Boolean {
        return true
      }

      override fun onLongPress(e: MotionEvent?) {

      }

      override fun onFling(
        e1: MotionEvent?,
        e2: MotionEvent?,
        velocityX: Float,
        velocityY: Float
      ): Boolean {
        return true
      }

    }).apply {
      setOnDoubleTapListener(object :GestureDetector.OnDoubleTapListener{
        override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
          
        }

        override fun onDoubleTap(e: MotionEvent?): Boolean {
          return true
        }

        override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
           
        }
      })
  }

3.实现放大时可以移动

先定义手指滑动时的偏移量

  //大图模式下移动时的偏移量
  private var extraOffsetX = 0f
  private var extraOffsetY = 0f

onDraw中实现偏移效果



onScroll中修改偏移量


偏移量 = 当前偏移量 + 滑动偏移量,但应该写成-

4.实现惯性移动

OverScroller⽤于⾃动计算滑动的偏移。常⽤于 onFling() ⽅法中,调⽤ OverScroller.fling() ⽅法来启动惯性滑动的计算:

//模板
val overScroller = OverScroller(context);
override fun onFling(downEvent: MotionEvent,
currentEvent: MotionEvent, velocityX: Float, velocityY:
Float) {
 // 初始化滑动
   scroller.fling(startX, startY, velocityX, velocityY,
    minX, maxX, minY, maxY)
     // 下⼀帧刷新
     ViewCompat.postOnAnimation(this, this)
     return false
}
...
override fun run() {
   // 计算此时的位置,并且如果滑动已经结束,就停⽌
   if (scroller.computeScrollOffset()) {
   // 把此时的位置应⽤于界⾯
   offsetX = scroller.currX.toFloat()
   offsetY = scroller.currY.toFloat()
   invalidate()
   // 下⼀帧刷新
   ViewCompat.postOnAnimation(this, this)
 }
}
  override fun onFling(
    e1: MotionEvent?,
    e2: MotionEvent?,
    velocityX: Float,
    velocityY: Float
  ): Boolean {
    if (big){
      overScroller.fling(extraOffsetX.toInt(), extraOffsetY.toInt(), velocityX.toInt(), velocityY.toInt(),
              (- (bitmap.width * bigScale - width) / 2).toInt(),
              ((bitmap.width * bigScale - width) / 2).toInt(),
              (- (bitmap.height * bigScale - height) / 2).toInt(),
              ((bitmap.height * bigScale - height) / 2).toInt(),
              40.dp.toInt(), 40.dp.toInt())
      //下一帧到来时执行Runnable
      ViewCompat.postOnAnimation(this,this)
    }

    return false
  }

  override fun run() {
    //计算当前的位置,返回值是惯性运动是否还在进行
    if(overScroller.computeScrollOffset()){
      //取当前位置
      extraOffsetX = overScroller.currX.toFloat()
      extraOffsetY = overScroller.currY.toFloat()
      invalidate()
      ViewCompat.postOnAnimation(this,this)
    }
  }

目前效果

5.实现放大移动后缩小恢复到原位置

      override fun onDoubleTap(e: MotionEvent): Boolean {
        big = !big
        if (big){

          //实现放大移动后缩小恢复到原位置
          extraOffsetX = (1- bigScale / smallScale) * (e.x - width / 2)
          extraOffsetY = (1- bigScale / smallScale) * (e.y - height / 2)

          extraOffsetX = min(extraOffsetX,(bitmap.width * bigScale - width)/2)
          extraOffsetX = max(extraOffsetX,- (bitmap.width * bigScale - width)/2)
          extraOffsetY = min(extraOffsetY,(bitmap.height * bigScale - height)/2)
          extraOffsetY = max(extraOffsetY,- (bitmap.height * bigScale - height)/2)
          
          scaleAnimator.start()
        }else{
          scaleAnimator.reverse()
        }

        return true
      }
当前效果

6.实现双指缩放

ScaleGestureDetector 和ScaleGestureListener

private scaleGestureDetector =ScaleGestureDetector(context, scaleGestureListener)
class HenScaleGestureListener : OnScaleGestureListener {
 override fun onScaleBegin(detector:
ScaleGestureDetector): Boolean {
 // 捏撑开始
 return true
 }

 override fun onScaleEnd(detector:
ScaleGestureDetector) {
 // 捏撑结束
 }

 override fun onScale(detector: ScaleGestureDetector):Boolean {
   // 新的捏撑事件
   currentScale *= detector.scaleFactor
    
   // 这个返回值表示「事件是否消耗」,即「这个事件算不算数」
    //return true 则scaleFactor表示当前状态放缩系数和前一个状态的比值
  //return false则scaleFactor表示当前状态放缩系数和初始状态的比值
   return true
     }
}
  private val scaleGestureDetector = ScaleGestureDetector(context,object : ScaleGestureDetector.OnScaleGestureListener{
    override fun onScale(detector: ScaleGestureDetector): Boolean {
      val tempCurrentScale = currentScale * detector.scaleFactor
      if (tempCurrentScale < smallScale || tempCurrentScale > bigScale){
        return false
      }else{
        currentScale *= detector.scaleFactor
        return true
      }

      // 这个返回值表示「事件是否消耗」,即「这个事件算不算数」
      //return true 则scaleFactor表示当前状态放缩系数和前一个状态的比值
      //return false则scaleFactor表示当前状态放缩系数和初始状态的比值
      return true
    }

    override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
      //实现放大移动后缩小恢复到原位置
      extraOffsetX = (1- bigScale / smallScale) * (detector.focusX - width / 2)
      extraOffsetY = (1- bigScale / smallScale) * (detector.focusY - height / 2)
      return true
    }

    override fun onScaleEnd(detector: ScaleGestureDetector?) {

    }

  })

  override fun onTouchEvent(event: MotionEvent?): Boolean {
    scaleGestureDetector.onTouchEvent(event)
    if (!scaleGestureDetector.isInProgress){
      gestureDetector.onTouchEvent(event)
    }
    return true
  }

7.最终效果:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容