zoukankan      html  css  js  c++  java
  • (三)多点触控之自由移动缩放后的图片

          在上一篇文章中,将图片的自由缩放功能基本上完成了。效果还不错。如果你还没读过,可以点击下面的链接:
    http://www.cnblogs.com/fuly550871915/p/4939954.html

          接下来这个项目要往前走,在自由缩放的基础上实现自由移动。要用的知识点就是OnTouchListener对移动手势的监控。在写代码之前我们应该考虑下面的几个问题:

    (1)什么时候可以移动?
    当图片比屏幕大时才需要移动,如果图片在屏幕内显示,没必要移动。
    (2)当移动的距离达到多少时才触发移动效果?
    在这里android提供了一个系统的临界值,直接使用即可。
    (3)怎么使图片移动?
    得到移动距离,利用Matrix.postTeanslate方法即可。

          这些问题都想清楚了,实现的逻辑就不难了。

          下面修改ZoomImageView,其中的代码为:

      1 package com.example.view;
      2 
      3 import android.annotation.SuppressLint;
      4 import android.content.Context;
      5 import android.graphics.Matrix;
      6 import android.graphics.RectF;
      7 import android.graphics.drawable.Drawable;
      8 import android.util.AttributeSet;
      9 import android.util.Log;
     10 import android.view.MotionEvent;
     11 import android.view.ScaleGestureDetector;
     12 import android.view.ScaleGestureDetector.OnScaleGestureListener;
     13 import android.view.View;
     14 import android.view.ViewConfiguration;
     15 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
     16 import android.view.View.OnTouchListener;
     17 import android.widget.ImageView;
     18 
     19 public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, 
     20 OnScaleGestureListener, OnTouchListener
     21 {
     22     private boolean mOnce = false;//是否执行了一次
     23     
     24     /**
     25      * 初始缩放的比例
     26      */
     27     private float initScale;
     28     /**
     29      * 缩放比例
     30      */
     31     private float midScale;
     32     /**
     33      * 可放大的最大比例
     34      */
     35     private float maxScale;
     36     /**
     37      * 缩放矩阵
     38      */
     39     private Matrix scaleMatrix;
     40     
     41     /**
     42      * 缩放的手势监控类
     43      */
     44     private ScaleGestureDetector mScaleGestureDetector;
     45     
     46     //==========================下面是自由移动的成员变量======================================
     47     /**
     48      * 上一次移动的手指个数,也可以说是多点个数
     49      */
     50     private int mLastPoint;
     51     /**
     52      * 上次的中心点的x位置
     53      */
     54     private float mLastX;
     55     /**
     56      * 上一次中心点的y位置
     57      */
     58     private float mLastY;
     59     /**
     60      * 一个临界值,即是否触发移动的临界值
     61      */
     62     private float mScaleSlop;
     63     /**
     64      * 是否可移动
     65      */
     66     private boolean isCanDrag = false;
     67     
     68     
     69 
     70     public ZoomImageView(Context context)
     71     {
     72         this(context,null);
     73     }
     74     public ZoomImageView(Context context, AttributeSet attrs) 
     75     {
     76         this(context, attrs,0);
     77 
     78     }
     79     public ZoomImageView(Context context, AttributeSet attrs, int defStyle)
     80     {
     81         super(context, attrs, defStyle);
     82         
     83         scaleMatrix = new Matrix();
     84         
     85         setScaleType(ScaleType.MATRIX);
     86         
     87         mScaleGestureDetector = new ScaleGestureDetector(context, this);
     88         //触摸回调
     89         setOnTouchListener(this);
     90         //获得系统给定的触发移动效果的临界值
     91         mScaleSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     92     }
     93     
     94     /**
     95      * 该方法在view与window绑定时被调用,且只会被调用一次,其在view的onDraw方法之前调用
     96      */
     97     protected void onAttachedToWindow()
     98     {
     99         super.onAttachedToWindow();
    100         //注册监听器
    101         getViewTreeObserver().addOnGlobalLayoutListener(this);
    102     }
    103     
    104     /**
    105      * 该方法在view被销毁时被调用
    106      */
    107     @SuppressLint("NewApi") protected void onDetachedFromWindow() 
    108     {
    109         super.onDetachedFromWindow();
    110         //取消监听器
    111         getViewTreeObserver().removeOnGlobalLayoutListener(this);
    112     }
    113     
    114     /**
    115      * 当一个view的布局加载完成或者布局发生改变时,OnGlobalLayoutListener会监听到,调用该方法
    116      * 因此该方法可能会被多次调用,需要在合适的地方注册和取消监听器
    117      */
    118     public void onGlobalLayout() 
    119     {
    120         if(!mOnce)
    121         {
    122             //获得当前view的Drawable
    123             Drawable d = getDrawable();
    124             
    125             if(d == null)
    126             {
    127                 return;
    128             }
    129             
    130             //获得Drawable的宽和高
    131             int dw = d.getIntrinsicWidth();
    132             int dh = d.getIntrinsicHeight();
    133             
    134             //获取当前view的宽和高
    135             int width = getWidth();
    136             int height = getHeight();
    137             
    138             //缩放的比例,scale可能是缩小的比例也可能是放大的比例,看它的值是大于1还是小于1
    139             float scale = 1.0f;
    140             
    141             //如果仅仅是图片宽度比view宽度大,则应该将图片按宽度缩小
    142             if(dw>width&&dh<height)
    143             {
    144                 scale = width*1.0f/dw;
    145             }
    146             //如果图片和高度都比view的大,则应该按最小的比例缩小图片
    147             if(dw>width&&dh>height)
    148             {
    149                 scale = Math.min(width*1.0f/dw, height*1.0f/dh);
    150             }
    151             //如果图片宽度和高度都比view的要小,则应该按最小的比例放大图片
    152             if(dw<width&&dh<height)
    153             {
    154                 scale = Math.min(width*1.0f/dw, height*1.0f/dh);
    155             }
    156             //如果仅仅是高度比view的大,则按照高度缩小图片即可
    157             if(dw<width&&dh>height)
    158             {
    159                 scale = height*1.0f/dh;
    160             }
    161             
    162             //初始化缩放的比例
    163             initScale = scale;
    164             midScale = initScale*2;
    165             maxScale = initScale*4;
    166             
    167             //移动图片到达view的中心
    168             int dx = width/2 - dw/2;
    169             int dy = height/2 - dh/2;
    170             scaleMatrix.postTranslate(dx, dy);
    171             
    172             //缩放图片
    173             scaleMatrix.postScale(initScale, initScale, width/2, height/2);    
    174             
    175             setImageMatrix(scaleMatrix);
    176             mOnce = true;
    177         }
    178         
    179     }
    180     /**
    181      * 获取当前已经缩放的比例
    182      * @return  因为x方向和y方向比例相同,所以只返回x方向的缩放比例即可
    183      */
    184     private float getDrawableScale()
    185     {
    186         
    187         float[] values = new float[9];
    188         scaleMatrix.getValues(values);
    189         
    190         return values[Matrix.MSCALE_X];
    191         
    192     }
    193 
    194     /**
    195      * 缩放手势进行时调用该方法
    196      * 
    197      * 缩放范围:initScale~maxScale
    198      */
    199     public boolean onScale(ScaleGestureDetector detector)
    200     {
    201         
    202         if(getDrawable() == null)
    203         {
    204             return true;//如果没有图片,下面的代码没有必要运行
    205         }
    206         
    207         float scale = getDrawableScale();
    208         //获取当前缩放因子
    209         float scaleFactor = detector.getScaleFactor();
    210         
    211         if((scale<maxScale&&scaleFactor>1.0f)||(scale>initScale&&scaleFactor<1.0f))
    212         {
    213             //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
    214             if(scale*scaleFactor<initScale&&scaleFactor<1.0f)
    215             {
    216                 scaleFactor = initScale/scale;
    217             }
    218             //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
    219             if(scale*scaleFactor>maxScale&&scaleFactor>1.0f)
    220             {
    221                 scaleFactor = maxScale/scale;
    222             }
    223             
    224 //            scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth()/2, getHeight()/2);
    225             scaleMatrix.postScale(scaleFactor, scaleFactor,detector.getFocusX(), 
    226                     detector.getFocusY());
    227             
    228             checkBoderAndCenter();//处理缩放后图片边界与屏幕有间隙或者不居中的问题
    229             
    230             
    231             setImageMatrix(scaleMatrix);//千万不要忘记设置这个,我总是忘记
    232         }
    233         
    234         
    235     
    236         return true;
    237     }
    238     /**
    239      * 处理缩放后图片边界与屏幕有间隙或者不居中的问题
    240      */
    241     private void checkBoderAndCenter()
    242     {
    243        RectF rectf = getDrawableRectF();
    244        
    245        int width = getWidth();
    246        int height = getHeight();
    247        
    248        float delaX =0;
    249        float delaY = 0;
    250        
    251        if(rectf.width()>=width)
    252        {
    253            if(rectf.left>0)
    254            {
    255              delaX = - rectf.left;  
    256            }
    257            
    258            if(rectf.right<width)
    259            {
    260                delaX = width - rectf.right;
    261            }   
    262        }
    263        
    264        if(rectf.height()>=height)
    265        {
    266            if(rectf.top>0)
    267            {
    268                delaY = -rectf.top;
    269            }
    270            if(rectf.bottom<height)
    271            {
    272                delaY = height - rectf.bottom;
    273            }
    274        }
    275        
    276        if(rectf.width()<width)
    277        {
    278            delaX = width/2 - rectf.right+ rectf.width()/2;
    279        }
    280        
    281        if(rectf.height()<height)
    282        {
    283            delaY =  height/2 - rectf.bottom+ rectf.height()/2;
    284        }
    285        
    286        scaleMatrix.postTranslate(delaX, delaY);
    287     }
    288     /**
    289      * 获取图片根据矩阵变换后的四个角的坐标,即left,top,right,bottom
    290      * @return 
    291      */
    292     private RectF getDrawableRectF()
    293     {
    294         Matrix matrix = scaleMatrix;
    295         RectF rectf = new RectF();
    296         Drawable d = getDrawable();
    297         if(d != null)
    298         {
    299             
    300             rectf.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
    301         }
    302         
    303         matrix.mapRect(rectf);
    304         return  rectf;
    305     }
    306     /**
    307      * 缩放手势开始时调用该方法
    308      */
    309     public boolean onScaleBegin(ScaleGestureDetector detector) 
    310     {    
    311         //返回为true,则缩放手势事件往下进行,否则到此为止,即不会执行onScale和onScaleEnd方法
    312         return true;
    313     }
    314     /**
    315      * 缩放手势完成后调用该方法
    316      */
    317     public void onScaleEnd(ScaleGestureDetector detector)
    318     {
    319         
    320         
    321     }
    322 
    323     /**
    324      * 监听触摸事件
    325      */
    326     public boolean onTouch(View v, MotionEvent event)
    327     {
    328     
    329         if(mScaleGestureDetector != null)
    330         {
    331             //将触摸事件传递给手势缩放这个类
    332             mScaleGestureDetector.onTouchEvent(event);
    333         }
    334         
    335         
    336         //获得多点个数,也叫屏幕上手指的个数
    337         int pointCount = event.getPointerCount();
    338         
    339         float x =0;
    340         float y =0;//中心点的x和y
    341         
    342         for(int i=0;i<pointCount;i++)
    343         {
    344             x+=event.getX(i);
    345             y+=event.getY(i);
    346         }
    347         
    348         //求出中心点的位置
    349         x/= pointCount;
    350         y/= pointCount;
    351         
    352         //如果手指的数量发生了改变,则不移动
    353         if(mLastPoint != pointCount)
    354         {
    355             isCanDrag = false;
    356             mLastX = x;
    357             mLastY = y;
    358             
    359         }
    360         mLastPoint = pointCount;
    361         
    362         
    363         switch(event.getAction())
    364         {
    365         case MotionEvent.ACTION_MOVE:
    366             
    367             //求出移动的距离
    368             float dx = x - mLastX;
    369             float dy = y- mLastY;
    370             
    371             if(!isCanDrag)
    372             {
    373                 isCanDrag = isCanDrag(dx,dy);
    374             }
    375             
    376             if(isCanDrag)
    377             {
    378                 //如果图片能正常显示,就不需要移动了
    379                 RectF rectf = getDrawableRectF();
    380                 if(rectf.width()<=getWidth())
    381                 {
    382                     dx = 0;
    383                 }
    384                 if(rectf.height()<=getHeight())
    385                 {
    386                     dy = 0;
    387                 }
    388                 
    389                 
    390                 //开始移动
    391                 scaleMatrix.postTranslate(dx, dy);
    392                 //处理移动后图片边界与屏幕有间隙或者不居中的问题
    393                 checkBoderAndCenterWhenMove();
    394                 setImageMatrix(scaleMatrix);
    395             }
    396             
    397             mLastX = x;
    398             mLastY = y;
    399             
    400             
    401             break;
    402         case MotionEvent.ACTION_UP:
    403         case MotionEvent.ACTION_CANCEL: 
    404                     mLastPoint = 0;    
    405             break;
    406         
    407         }
    408 
    409         return true;
    410     }
    411     /**
    412      * 处理移动后图片边界与屏幕有间隙或者不居中的问题
    413      * 这跟我们前面写的代码很像
    414      */
    415     private void checkBoderAndCenterWhenMove() {
    416         
    417         RectF rectf = getDrawableRectF();
    418         
    419         float delaX = 0;
    420         float delaY = 0;
    421         int width = getWidth();
    422         int height = getHeight();
    423         
    424         if(rectf.width()>width&&rectf.left>0)
    425         {
    426             delaX = - rectf.left;
    427         }
    428         if(rectf.width()>width&&rectf.right<width)
    429         {
    430             delaX = width - rectf.right;
    431         }
    432         if(rectf.height()>height&&rectf.top>0)
    433         {
    434             delaY = - rectf.top;
    435         }
    436         if(rectf.height()>height&&rectf.bottom<height)
    437         {
    438             delaY = height - rectf.bottom;
    439         }
    440         
    441         scaleMatrix.postTranslate(delaX, delaY);
    442     }
    443     /**
    444      * 判断是否触发移动效果
    445      * @param dx
    446      * @param dy
    447      * @return  
    448      */
    449     private boolean isCanDrag(float dx, float dy) {
    450         
    451         return Math.sqrt(dx*dx+dy*dy)>mScaleSlop;
    452     }
    453 
    454     
    455     
    456 
    457 }

           红色部分就是增加的代码了。在这里简单解释一下。首先拿到系统给定的判定是否触发移动效果的临界值,即91行获得mScaleSlop,然后在isCanDrag方法中根据移动的距离,判定是否足够触发移动效果。在onTouch方法中,编写具体的移动逻辑。由于是多点触控,即屏幕上可能不止一个手指。因此通过计算得到中心触控的位置x和y,即第349行和350行所做的事情。另外通过mLastX和mLastY保存之前移动的中心位置。通过计算当前中心位置与之前保存的中心位置的差值,就可以得到需要图片移动的距离了,即第368和第369行代码所做的事情。之后就可以判断是否触发移动了,如果触发了那就移动呗。移动完成后,再做些善后处理。大体逻辑就是这样子。很简单,代码也很详实。不再解释了。哦,对了,因为移动过程中,也可能会出现与屏幕间有空隙,因此需要checkBoderAndCenterWhenMove一下。这里面的代码跟之前写的的差不多了,就不多余解释了,原理一样。

          好了,运行程序吧,效果如下:

           效果达到了。是不是发现代码越写越少,越简单呢?因为这个项目快要完成了,基本没有什么大的逻辑了。下面就 快马加鞭,实现双击放大与缩小图片的功能吧:《(四)双击放大与缩小图片》

  • 相关阅读:
    cs231n--详解卷积神经网络
    Spring 2017 Assignments2
    深度神经网络基础
    cs231n官方note笔记
    Spring 2017 Assignments1
    问题
    win7下解决vs2015新建项目,提示“未将对象引用设置到引用实例“的问题
    项目二:人脸识别
    ubutu强制关闭应用程序的方法
    将caj文件转化为pdf文件进行全文下载脚本(ubuntu下亲测有用)
  • 原文地址:https://www.cnblogs.com/fuly550871915/p/4940103.html
Copyright © 2011-2022 走看看