在上一篇文章中,初步实现了自定义ImageView的多点触控。但是从最终效果来看,却发现自由缩放时,图片与屏幕出现了空白间隙,这当然是让人十分厌恶的。在这篇文章中,就来解决这个问题。如果你还没读过上一篇文章,可以点击下面的链接:

http://www.cnblogs.com/fuly550871915/p/4939629.html

 

      先贴出上一篇文章所实现的最终效果图吧,如下:

      从上图中,可以看到,图片缩放后,与屏幕之间形成了很大的空白间隙。现在来解决这个问题,主要是两个方面:

(1)图片自由缩放后,它的中心点不应该移动,即依旧是屏幕的中心点。
(2)图片自由缩放后,不应该与屏幕有空白间隙,主要是指宽度上的间隙。

     

一、分析

      下面就从这两个方面着手进行分析。那么缩放后,与屏幕的宽高相比较,有几种情况呢?很简单,只有四种,缩放后的宽比屏幕的宽要大,缩放的宽比屏幕的宽要小,缩放后的高度比屏幕的高要大,缩放后的高比屏幕的高要下。其实仔细想想,因为我们前面写代码时,最下缩放比例都要求宽度为屏幕的宽,所以缩放后的宽度是不可能比屏幕宽的,不过为了完整,我们写上也无妨。以缩放后的宽比屏幕的宽要大为例,仔细分析,看下面一张图片:

 

     如果缩放后,图片为图示A的状态,此时其宽度大于屏幕宽度,与屏幕右侧出现了间隙,那么它应该往右移动,而移动的距离就是:屏幕宽度与右坐标的差值。同理,如果缩放后为B的状态,则图片应该左移,移动的距离应该是:0与图片左坐标的差值。同样的道理,如果图片缩放后比屏幕高,你应该也会分析了。就是将宽度改为了高度而已。

    再看一张图片:

      缩放后,A,B,C,D四种情况已经在上面讨论过,这样子只要缩放后出现上面四种情况,我们对其进行相应移动,图片的中心点就会依旧与原来的图片中心点重合,即与屏幕中心点重合。那么如果是E那种情况呢?针对情况E,再画一张图。如下:

      从图上可以看出,显然此时需要移动的距离是,x方向就是黄线的长度,可以这样子计算:屏幕宽度/2 - 图片右坐标+图片缩放后的宽度/2。在y方向上,同理,只是将宽度改为高度即可。

     好了,通过上面的分析,所有情况下应该移动的算法,我们都清楚了。下面就可以将它们写进代码中了。移动嘛,利用的依旧是Matrix.postTranslate方法了。

 

二、代码

      还剩下一个问题,就是怎么获得图片根据矩阵变换后的新的坐标呢?这个问题,代码中有答案。修改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.ViewTreeObserver.OnGlobalLayoutListener;
 15 import android.view.View.OnTouchListener;
 16 import android.widget.ImageView;
 17 
 18 public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, 
 19 OnScaleGestureListener, OnTouchListener
 20 {
 21     private boolean mOnce = false;//是否执行了一次
 22     
 23     /**
 24      * 初始缩放的比例
 25      */
 26     private float initScale;
 27     /**
 28      * 缩放比例
 29      */
 30     private float midScale;
 31     /**
 32      * 可放大的最大比例
 33      */
 34     private float maxScale;
 35     /**
 36      * 缩放矩阵
 37      */
 38     private Matrix scaleMatrix;
 39     
 40     /**
 41      * 缩放的手势监控类
 42      */
 43     private ScaleGestureDetector mScaleGestureDetector;
 44     
 45 
 46     public ZoomImageView(Context context)
 47     {
 48         this(context,null);
 49     }
 50     public ZoomImageView(Context context, AttributeSet attrs) 
 51     {
 52         this(context, attrs,0);
 53 
 54     }
 55     public ZoomImageView(Context context, AttributeSet attrs, int defStyle)
 56     {
 57         super(context, attrs, defStyle);
 58         
 59         scaleMatrix = new Matrix();
 60         
 61         setScaleType(ScaleType.MATRIX);
 62         
 63         mScaleGestureDetector = new ScaleGestureDetector(context, this);
 64         //触摸回调
 65         setOnTouchListener(this);
 66         
 67     }
 68     
 69     /**
 70      * 该方法在view与window绑定时被调用,且只会被调用一次,其在view的onDraw方法之前调用
 71      */
 72     protected void onAttachedToWindow()
 73     {
 74         super.onAttachedToWindow();
 75         //注册监听器
 76         getViewTreeObserver().addOnGlobalLayoutListener(this);
 77     }
 78     
 79     /**
 80      * 该方法在view被销毁时被调用
 81      */
 82     @SuppressLint("NewApi") protected void onDetachedFromWindow() 
 83     {
 84         super.onDetachedFromWindow();
 85         //取消监听器
 86         getViewTreeObserver().removeOnGlobalLayoutListener(this);
 87     }
 88     
 89     /**
 90      * 当一个view的布局加载完成或者布局发生改变时,OnGlobalLayoutListener会监听到,调用该方法
 91      * 因此该方法可能会被多次调用,需要在合适的地方注册和取消监听器
 92      */
 93     public void onGlobalLayout() 
 94     {
 95         if(!mOnce)
 96         {
 97             //获得当前view的Drawable
 98             Drawable d = getDrawable();
 99             
100             if(d == null)
101             {
102                 return;
103             }
104             
105             //获得Drawable的宽和高
106             int dw = d.getIntrinsicWidth();
107             int dh = d.getIntrinsicHeight();
108             
109             //获取当前view的宽和高
110             int width = getWidth();
111             int height = getHeight();
112             
113             //缩放的比例,scale可能是缩小的比例也可能是放大的比例,看它的值是大于1还是小于1
114             float scale = 1.0f;
115             
116             //如果仅仅是图片宽度比view宽度大,则应该将图片按宽度缩小
117             if(dw>width&&dh<height)
118             {
119                 scale = width*1.0f/dw;
120             }
121             //如果图片和高度都比view的大,则应该按最小的比例缩小图片
122             if(dw>width&&dh>height)
123             {
124                 scale = Math.min(width*1.0f/dw, height*1.0f/dh);
125             }
126             //如果图片宽度和高度都比view的要小,则应该按最小的比例放大图片
127             if(dw<width&&dh<height)
128             {
129                 scale = Math.min(width*1.0f/dw, height*1.0f/dh);
130             }
131             //如果仅仅是高度比view的大,则按照高度缩小图片即可
132             if(dw<width&&dh>height)
133             {
134                 scale = height*1.0f/dh;
135             }
136             
137             //初始化缩放的比例
138             initScale = scale;
139             midScale = initScale*2;
140             maxScale = initScale*4;
141             
142             //移动图片到达view的中心
143             int dx = width/2 - dw/2;
144             int dy = height/2 - dh/2;
145             scaleMatrix.postTranslate(dx, dy);
146             
147             //缩放图片
148             scaleMatrix.postScale(initScale, initScale, width/2, height/2);    
149             
150             setImageMatrix(scaleMatrix);
151             mOnce = true;
152         }
153         
154     }
155     /**
156      * 获取当前已经缩放的比例
157      * @return  因为x方向和y方向比例相同,所以只返回x方向的缩放比例即可
158      */
159     private float getDrawableScale()
160     {
161         
162         float[] values = new float[9];
163         scaleMatrix.getValues(values);
164         
165         return values[Matrix.MSCALE_X];
166         
167     }
168 
169     /**
170      * 缩放手势进行时调用该方法
171      * 
172      * 缩放范围:initScale~maxScale
173      */
174     public boolean onScale(ScaleGestureDetector detector)
175     {
176         
177         if(getDrawable() == null)
178         {
179             return true;//如果没有图片,下面的代码没有必要运行
180         }
181         
182         float scale = getDrawableScale();
183         //获取当前缩放因子
184         float scaleFactor = detector.getScaleFactor();
185         
186         if((scale<maxScale&&scaleFactor>1.0f)||(scale>initScale&&scaleFactor<1.0f))
187         {
188             //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
189             if(scale*scaleFactor<initScale&&scaleFactor<1.0f)
190             {
191                 scaleFactor = initScale/scale;
192             }
193             //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
194             if(scale*scaleFactor>maxScale&&scaleFactor>1.0f)
195             {
196                 scaleFactor = maxScale/scale;
197             }
198             
199 //            scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth()/2, getHeight()/2);
200             scaleMatrix.postScale(scaleFactor, scaleFactor,detector.getFocusX(), 
201                     detector.getFocusY());
202             
203             checkBoderAndCenter();//处理缩放后图片边界与屏幕有间隙或者不居中的问题
204             
205             
206             setImageMatrix(scaleMatrix);//千万不要忘记设置这个,我总是忘记
207         }
208         return true;
209     }
210     /**
211      * 处理缩放后图片边界与屏幕有间隙或者不居中的问题
212      */
213     private void checkBoderAndCenter()
214     {
215        RectF rectf = getDrawableRectF();
216        
217        int width = getWidth();
218        int height = getHeight();
219        
220        float delaX =0;
221        float delaY = 0;
222        
223        if(rectf.width()>=width)
224        {
225            if(rectf.left>0)
226            {
227              delaX = - rectf.left;  
228            }
229            
230            if(rectf.right<width)
231            {
232                delaX = width - rectf.right;
233            }   
234        }
235        
236        if(rectf.height()>=height)
237        {
238            if(rectf.top>0)
239            {
240                delaY = -rectf.top;
241            }
242            if(rectf.bottom<height)
243            {
244                delaY = height - rectf.bottom;
245            }
246        }
247        
248        if(rectf.width()<width)
249        {
250            delaX = width/2 - rectf.right+ rectf.width()/2;
251        }
252        
253        if(rectf.height()<height)
254        {
255            delaY =  height/2 - rectf.bottom+ rectf.height()/2;
256        }
257        
258        scaleMatrix.postTranslate(delaX, delaY);
259     }
260     /**
261      * 获取图片根据矩阵变换后的四个角的坐标,即left,top,right,bottom
262      * @return 
263      */
264     private RectF getDrawableRectF()
265     {
266         Matrix matrix = scaleMatrix;
267         RectF rectf = new RectF();
268         Drawable d = getDrawable();
269         if(d != null)
270         {
271             
272             rectf.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
273         }
274         
275         matrix.mapRect(rectf);
276         return  rectf;
277     }
278     /**
279      * 缩放手势开始时调用该方法
280      */
281     public boolean onScaleBegin(ScaleGestureDetector detector) 
282     {    
283         //返回为true,则缩放手势事件往下进行,否则到此为止,即不会执行onScale和onScaleEnd方法
284         return true;
285     }
286     /**
287      * 缩放手势完成后调用该方法
288      */
289     public void onScaleEnd(ScaleGestureDetector detector)
290     {
291         
292         
293     }
294 
295     /**
296      * 监听触摸事件
297      */
298     public boolean onTouch(View v, MotionEvent event)
299     {
300     
301         if(mScaleGestureDetector != null)
302         {
303             //将触摸事件传递给手势缩放这个类
304             mScaleGestureDetector.onTouchEvent(event);
305         }
306         return true;
307     }
308 
309     
310   
311 
312 }

 

          红色部分是我们增加的核心代码。第203行,我们增加了checkBoderAndCenter方法用来处理缩放后出现间隙空白的问题。而在getDrawableRectF方法中,通过一个Rectf拿到图片缩放后的新的坐标,这个方法需要牢牢掌握。其他的代码,都是按照上面我们分析的逻辑来编写的,相信你一定能看的懂了。注意第248行到第251行的代码可要可不要,因为我们最小的缩放比例就要求宽度必须等于屏幕宽度,因此不可能比屏幕宽度小,做这个判断没意义。好了,要修改的就这么多了。现在运行下程序,看看是否真的解决问题了呢?效果如下:

 

      依旧是在真机上录制的gif,效果还是不错的,完美解决了这个出现空隙的问题。

三、总结

      获得图片缩放后的坐标的方法,必须要掌握。如下:

/**
     * 获取图片根据矩阵变换后的四个角的坐标,即left,top,right,bottom
     * @return 
     */
    private RectF getDrawableRectF()
    {
        Matrix matrix = scaleMatrix;
        RectF rectf = new RectF();
        Drawable d = getDrawable();
        if(d != null)
        {
            
            rectf.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        }
        
        matrix.mapRect(rectf);
        return  rectf;
    }

 

      自由缩放算是告一段落了,下面我们要实现的是图片的自由移动。保存好代码,快进入下一节吧:《(三)多点触控之自由移动缩放后的图片》

posted on 2015-11-05 16:24  fuly  阅读(1366)  评论(0编辑  收藏  举报