Android实现手势滑动多点触摸放大缩小图片效果

时间:2022-02-23 07:50:15

网上文章虽多,但是这种效果少之又少,我真诚的献上以供大家参考

实现原理:自定义imageview对此控件进行相应的layout(动态布局).

这里你要明白几个方法执行的流程:

首先imageview是继承自view的子类.
onlayout方法:是一个回调方法.该方法会在在view中的layout方法中执行,在执行layout方法前面会首先执行setframe方法.
setframe方法:判断我们的view是否发生变化,如果发生变化,那么将最新的l,t,r,b传递给view,然后刷新进行动态更新ui. 并且返回ture.没有变化返回false.
在介绍自定义控件之前,我们先要明白我们要获取哪些数据:屏幕的宽度,屏幕的高度.(这里其实你也可以对linerlayout进行viewtreeobserver监听获取其宽高度.),原始图片本身的宽度及高度.以及我们缩放的最大最小值.
首先我们要重写setimagebitmap方法.作用:获取图片的宽高,求出缩放极限值.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/***
   * 设置显示图片
   */
  @override
  public void setimagebitmap(bitmap bm) {
    super.setimagebitmap(bm);
    /** 获取图片宽高 **/
    bitmap_w = bm.getwidth();
    bitmap_h = bm.getheight();
 
    max_w = bitmap_w * 3;
    max_h = bitmap_h * 3;
 
    min_w = bitmap_w / 2;
    min_h = bitmap_h / 2;
 
  }

接着我们在onlayout方法中我们获取最初的l,t,r,b.

?
1
2
3
4
5
6
7
8
9
10
11
12
@override
  protected void onlayout(boolean changed, int left, int top, int right,
      int bottom) {
    super.onlayout(changed, left, top, right, bottom);
    if (start_top == -1) {
      start_top = top;
      start_left = left;
      start_bottom = bottom;
      start_right = right;
    }
 
  }

下面我们说下重点touch方法.其实原来大家都明白,要说难的话估计就是逻辑实现了.
说之前大家要明白单点与多点的区别:
单手指操作:action_down---action_move----action_up
多手指操作:action_down---action_pointer_down---action_move--action_pointer_up---action_up.

上面只是简单说下流程,详细请大家自行研究,这里只是讲解如果运用.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/***
   * touch 事件
   */
  @override
  public boolean ontouchevent(motionevent event) {
    /** 处理单点、多点触摸 **/
    switch (event.getaction() & motionevent.action_mask) {
    case motionevent.action_down:
      ontouchdown(event);
      break;
    // 多点触摸
    case motionevent.action_pointer_down:
      onpointerdown(event);
      break;
 
    case motionevent.action_move:
      ontouchmove(event);
      break;
    case motionevent.action_up:
      mode = mode.none;
      break;
 
    // 多点松开
    case motionevent.action_pointer_up:
      mode = mode.none;
      /** 执行缩放还原 **/
      if (isscaleanim) {
        doscaleanim();
      }
      break;
    }
 
    return true;
  }

这里的实现我都分开写了,利于大家的观看,在这里我顺便说一下自定义控件返回值:如果对于没有孩子的控件,如果要对touch处理最好return true.这样也是游戏开发中经常用的,如果该控件有孩子的话,可不要这么弄,不然孩子会监听不到touch事件.

下面我们一个一个方法的看:
ontouchdown:获取手指点击时候的起始坐标.

?
1
2
3
4
5
6
7
8
9
10
11
/** 按下 **/
  void ontouchdown(motionevent event) {
    mode = mode.drag;
 
    current_x = (int) event.getrawx();
    current_y = (int) event.getrawy();
 
    start_x = (int) event.getx();
    start_y = current_y - this.gettop();
 
  }

这里大家也要明白 event.getrawx()和event.getx(),不过我相信大家都明白的,我前面那篇listview拖拽也提到过.一个相对屏幕,一个相对父控件.

onpointerdown:两手指之间的距离.

?
1
2
3
4
5
6
7
/** 两个手指 只能放大缩小 **/
  void onpointerdown(motionevent event) {
    if (event.getpointercount() == 2) {
      mode = mode.zoom;
      beforelenght = getdistance(event);// 获取两点的距离
    }
  }

ontouchmove:移动的处理.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/** 移动的处理 **/
void ontouchmove(motionevent event) {
  int left = 0, top = 0, right = 0, bottom = 0;
  /** 处理拖动 **/
  if (mode == mode.drag) {
 
    /** 在这里要进行判断处理,防止在drag时候越界 **/
 
    /** 获取相应的l,t,r ,b **/
    left = current_x - start_x;
    right = current_x + this.getwidth() - start_x;
    top = current_y - start_y;
    bottom = current_y - start_y + this.getheight();
 
    /** 水平进行判断 **/
    if (iscontrol_h) {
      if (left >= 0) {
        left = 0;
        right = this.getwidth();
      }
      if (right <= screen_w) {
        left = screen_w - this.getwidth();
        right = screen_w;
      }
    } else {
      left = this.getleft();
      right = this.getright();
    }
    /** 垂直判断 **/
    if (iscontrol_v) {
      if (top >= 0) {
        top = 0;
        bottom = this.getheight();
      }
 
      if (bottom <= screen_h) {
        top = screen_h - this.getheight();
        bottom = screen_h;
      }
    } else {
      top = this.gettop();
      bottom = this.getbottom();
    }
    if (iscontrol_h || iscontrol_v)
      this.setposition(left, top, right, bottom);
 
    current_x = (int) event.getrawx();
    current_y = (int) event.getrawy();
 
  }
  /** 处理缩放 **/
  else if (mode == mode.zoom) {
 
    afterlenght = getdistance(event);// 获取两点的距离
 
    float gaplenght = afterlenght - beforelenght;// 变化的长度
 
    if (math.abs(gaplenght) > 5f) {
      scale_temp = afterlenght / beforelenght;// 求的缩放的比例
 
      this.setscale(scale_temp);
 
      beforelenght = afterlenght;
    }
  }
 
}

处理的逻辑比较繁多,但上诉代码大部分都已注释,我相信大家都看得懂,大家可以掌握原理后可以进行自己的逻辑处理.

下面我们看下缩放处理,因为考虑到越界与否.
setscale方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/** 处理缩放 **/
  void setscale(float scale) {
    int disx = (int) (this.getwidth() * math.abs(1 - scale)) / 4;// 获取缩放水平距离
    int disy = (int) (this.getheight() * math.abs(1 - scale)) / 4;// 获取缩放垂直距离
 
    // 放大
    if (scale > 1 && this.getwidth() <= max_w) {
      current_left = this.getleft() - disx;
      current_top = this.gettop() - disy;
      current_right = this.getright() + disx;
      current_bottom = this.getbottom() + disy;
 
      this.setframe(current_left, current_top, current_right,
          current_bottom);
      /***
       * 此时因为考虑到对称,所以只做一遍判断就可以了。
       */
      if (current_top <= 0 && current_bottom >= screen_h) {
        log.e("jj", "屏幕高度=" + this.getheight());
        iscontrol_v = true;// 开启垂直监控
      } else {
        iscontrol_v = false;
      }
      if (current_left <= 0 && current_right >= screen_w) {
        iscontrol_h = true;// 开启水平监控
      } else {
        iscontrol_h = false;
      }
 
    }
    // 缩小
    else if (scale < 1 && this.getwidth() >= min_w) {
      current_left = this.getleft() + disx;
      current_top = this.gettop() + disy;
      current_right = this.getright() - disx;
      current_bottom = this.getbottom() - disy;
      /***
       * 在这里要进行缩放处理
       */
      // 上边越界
      if (iscontrol_v && current_top > 0) {
        current_top = 0;
        current_bottom = this.getbottom() - 2 * disy;
        if (current_bottom < screen_h) {
          current_bottom = screen_h;
          iscontrol_v = false;// 关闭垂直监听
        }
      }
      // 下边越界
      if (iscontrol_v && current_bottom < screen_h) {
        current_bottom = screen_h;
        current_top = this.gettop() + 2 * disy;
        if (current_top > 0) {
          current_top = 0;
          iscontrol_v = false;// 关闭垂直监听
        }
      }
 
      // 左边越界
      if (iscontrol_h && current_left >= 0) {
        current_left = 0;
        current_right = this.getright() - 2 * disx;
        if (current_right <= screen_w) {
          current_right = screen_w;
          iscontrol_h = false;// 关闭
        }
      }
      // 右边越界
      if (iscontrol_h && current_right <= screen_w) {
        current_right = screen_w;
        current_left = this.getleft() + 2 * disx;
        if (current_left >= 0) {
          current_left = 0;
          iscontrol_h = false;// 关闭
        }
      }
 
      if (iscontrol_h || iscontrol_v) {
        this.setframe(current_left, current_top, current_right,
            current_bottom);
      } else {
        this.setframe(current_left, current_top, current_right,
            current_bottom);
        isscaleanim = true;// 开启缩放动画
      }
 
    }
 
  }

首先我们先看下放大方法:这里面我们要时时监听水平或垂直是否已经铺满(该其实应说成布局),如果铺满或超过那么对应的水平或垂直方向就可以进行托移.代码注释很清晰大家可以看上面注释.

接下来我们看下缩小,这个相对复杂了一点。首先我们要考虑到放大后的托移,这样的话我们在进行缩小的时候肯定l,t,r,b她们不会同时缩到屏幕边界,因此此时就要进行处理,如果一方先缩到屏幕边界的话,那么你就停下来等等你的对面(记住此时你对面缩放的速率是原来的2倍,不然图片会变形的.大家自己想想看是不是),等到你对面也缩到屏幕边界的话,此时要关闭监听.然后你们两个在一起缩.原理就是这样.
不太明白的话,大家可以看上述代码,我相信大家都看的明白.
最后我们还要实现缩放回缩效果(比较人性化.)
刚开始我想到了scaleanimation,可是实现一半问题出现了,我回缩动画完毕后她又自动回到最初大小,也许你会说你少加了setfillafter(true); 可是加了后会出现诡异现象:又会重新覆盖一层,原因不明,大家可以试试看.既然api给的动画实现不了,那我就自己做吧.下面看具体实现.
myasynctask异步类.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/***
   * 回缩动画執行
   */
  class myasynctask extends asynctask<void, integer, void> {
    private int screen_w, current_width, current_height;
 
    private int left, top, right, bottom;
 
    private float scale_wh;// 宽高的比例
 
    /** 当前的位置属性 **/
    public void setltrb(int left, int top, int right, int bottom) {
      this.left = left;
      this.top = top;
      this.right = right;
      this.bottom = bottom;
    }
 
    private float step = 5f;// 步伐
 
    private float step_h, step_v;// 水平步伐,垂直步伐
 
    public myasynctask(int screen_w, int current_width, int current_height) {
      super();
      this.screen_w = screen_w;
      this.current_width = current_width;
      this.current_height = current_height;
      scale_wh = (float) current_height / current_width;
      step_h = step;
      step_v = scale_wh * step;
    }
 
    @override
    protected void doinbackground(void... params) {
 
      while (current_width <= screen_w) {
 
        left -= step_h;
        top -= step_v;
        right += step_h;
        bottom += step_v;
 
        current_width += 2 * step_h;
 
        left = math.max(left, start_left);
        top = math.max(top, start_top);
        right = math.min(right, start_right);
        bottom = math.min(bottom, start_bottom);
 
        onprogressupdate(new integer[] { left, top, right, bottom });
        try {
          thread.sleep(10);
        } catch (interruptedexception e) {
          e.printstacktrace();
        }
      }
 
      return null;
    }
 
    @override
    protected void onprogressupdate(final integer... values) {
      super.onprogressupdate(values);
      mactivity.runonuithread(new runnable() {
        @override
        public void run() {
          setframe(values[0], values[1], values[2], values[3]);
        }
      });
 
    }
 
  }

这个我就不详细讲解了,大家要注意的是水平和垂直方向的速率.

最后我们看下布局,调用也相当简单,也有助于我们添加辅助ui,千万不要忘记写 android:scaletype="fitxy"这句话,不然有时候会出现诡异现象.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:gravity="center" >
 
  <com.jj.drag.dragimageview
    android:id="@+id/div_main"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaletype="fitxy"
    />
 
</linearlayout>

在acitivity中调用:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** 测量状态栏高度 **/
    viewtreeobserver = dragimageview.getviewtreeobserver();
    viewtreeobserver
        .addongloballayoutlistener(new ongloballayoutlistener() {
 
          @override
          public void ongloballayout() {
            if (state_height == 0) {
              // 获取状况栏高度
              rect frame = new rect();
              getwindow().getdecorview()
                  .getwindowvisibledisplayframe(frame);
              state_height = frame.top;
              dragimageview.setscreen_h(window_height-state_height);
              dragimageview.setscreen_w(window_width);
            }
 
          }
        });

以上就是全部实现.最后我们看下实现的效果吧.

原图大小  

     Android实现手势滑动多点触摸放大缩小图片效果

放大后拖拽到左上角  

     Android实现手势滑动多点触摸放大缩小图片效果

缩小后(松开会回缩)  

Android实现手势滑动多点触摸放大缩小图片效果

长大于宽

Android实现手势滑动多点触摸放大缩小图片效果

感觉运行的效果还行,和腾讯新浪的差不多.至于辅助ui元素,大家可以自行修改添加,这里我只是把这种形式的实现献给大家.