手把手教你打造支持手勢放大縮小的ImageView

寫在前面

最近有了新的任務(wù)洲脂,學(xué)習(xí)的時(shí)間比以前少了不少,Java回爐的文估計(jì)是得緩緩了剧包,不過每周一篇盡量保質(zhì)保量恐锦。最近感覺我文寫的有點(diǎn)不好,因?yàn)槲覍憱|西除非必要疆液,不然概念性的東西我基本上都是一筆帶過……最近感覺這對看我文的人好像不是很友好踩蔚,恩,我決定改一改枚粘,盡量寫的詳細(xì)而有趣一些馅闽。


1.jpg

好了廢話時(shí)間過了,前面也說了最近有了新任務(wù)馍迄,我現(xiàn)在是搞定用戶信息這一塊福也。一般來說現(xiàn)在用戶都會有個(gè)頭像什么的,光有個(gè)頭像還不夠攀圈,你還得能點(diǎn)擊看個(gè)大圖吧暴凑?光看個(gè)大圖也不夠啊,不說多的赘来,你最起碼得支持用戶手勢放大縮小什么的吧现喳?當(dāng)時(shí)腦海里第一個(gè)想到的是PhotoView,不過整個(gè)項(xiàng)目好像也只有這一塊涉及到用戶手勢放大縮小犬辰,算了嗦篱,自己實(shí)現(xiàn)一個(gè)吧。當(dāng)然了幌缝,經(jīng)常刷hongyang大神博客的我自然知道hongyang大神博客里有寫過這東西灸促。所以趁周末有空果斷刷之~

實(shí)現(xiàn)思路

做一個(gè)東西之前我們肯定要分析需求,分析完之后我們就可以利用我們會的,或者知道可以實(shí)現(xiàn)但是現(xiàn)在不會的去嘗試解決這個(gè)需求浴栽。放大縮小圖片荒叼,腦子里第一個(gè)反應(yīng)就是矩陣,Android里貌似有個(gè)可以通過矩陣處理圖像的東西典鸡,不過說真的被廓,以前也沒有用過幾次,不過好歹有個(gè)想法了萝玷。至于讓圖片跟隨用戶手勢放大縮小伊者,肯定是需要支持手勢檢測了。恩间护,我的思路暫時(shí)就是這樣了亦渗,接下來先去了解一下手勢檢測。

手勢檢測

當(dāng)用戶觸摸屏幕時(shí)汁尺,會產(chǎn)生許多手勢法精,down、up痴突、scroll搂蜓、fling等。一般情況下我們通過實(shí)現(xiàn)OnTouchListener是可以滿足我們處理一般手勢的需求的辽装,說實(shí)話帮碰,實(shí)現(xiàn)手勢放大縮小的ImageView是可以通過自己在OnTouch方法里面處理距離,滑動(dòng)什么的去算縮放的拾积。但是人總是要對自己好一點(diǎn)殉挽,如果有更簡單的實(shí)現(xiàn)方式為什么不用呢?Android中提供了GestureDetector給程序員去判斷不同的手勢拓巧。另外也提供了** ScaleGestureDetector **來檢測縮放手勢斯碌。雖然后者很像前者的子類,但事實(shí)上并不是肛度,后者也是一個(gè)獨(dú)立的類傻唾。下面用一個(gè)簡單的demo來演示一下這兩者的觸發(fā)。

package com.example.luo_pc.view;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements GestureDetector.OnGestureListener,
        View.OnClickListener, ScaleGestureDetector.OnScaleGestureListener {

    //定義手勢檢測
    GestureDetector detector = null;
    //縮放檢測
    ScaleGestureDetector scDetector = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button testGet = (Button) findViewById(R.id.bt_test_ges);
        Button testScges = (Button) findViewById(R.id.bt_test_scges);
        testGet.setOnClickListener(this);
        testScges.setOnClickListener(this);
        detector = new GestureDetector(this, this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_test_ges:
                detector = new GestureDetector(this, this);
                scDetector = null;
                break;
            case R.id.bt_test_scges:
                scDetector = new ScaleGestureDetector(this, this);
                detector = null;
                break;
        }
    }

    //-------------------------implement OnGestureListener's method-----------------------//

    @Override
    public boolean onTouchEvent(MotionEvent me) {
        if (detector != null)
            return detector.onTouchEvent(me);
        else
            return scDetector.onTouchEvent(me);
    }

    //用戶按下屏幕就會觸發(fā)
    @Override
    public boolean onDown(MotionEvent arg0) {
        Toast.makeText(this, "onDown", Toast.LENGTH_SHORT).show();
        return false;
    }

    //用戶按下觸摸屏承耿、快速移動(dòng)后松開,由1個(gè)MotionEvent ACTION_DOWN,    
    //多個(gè)ACTION_MOVE, 1個(gè)ACTION_UP觸發(fā)    
    //e1:第1個(gè)ACTION_DOWN MotionEvent    
    //e2:最后一個(gè)ACTION_MOVE MotionEvent    
    //velocityX:X軸上的移動(dòng)速度冠骄,像素/秒    
    //velocityY:Y軸上的移動(dòng)速度,像素/秒    
    @Override
    public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2,float arg3) {
        Toast.makeText(this, "onFling", Toast.LENGTH_SHORT).show();
        return false;
    }

    //用戶長按觸摸屏加袋,由多個(gè)MotionEvent ACTION_DOWN觸發(fā)   
    @Override
    public void onLongPress(MotionEvent arg0) {
        Toast.makeText(this, "onLongPress", Toast.LENGTH_SHORT).show();
    }

    //用戶按下觸摸屏凛辣,并拖動(dòng),由1個(gè)MotionEvent ACTION_DOWN, 多個(gè)ACTION_MOVE觸發(fā)   
    @Override
    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2,
                            float arg3) {
        Toast.makeText(this, "onScroll", Toast.LENGTH_SHORT).show();
        return false;
    }

    //如果是按下的時(shí)間超過瞬間锁荔,而且在按下的時(shí)候沒有松開或者是拖動(dòng)的蟀给,
    // 那么onShowPress就會執(zhí)行 
    @Override
    public void onShowPress(MotionEvent arg0) {
        Toast.makeText(this, "onShowPress", Toast.LENGTH_SHORT).show();
    }

    //用戶(輕觸觸摸屏后)松開蝙砌,由一個(gè)1個(gè)MotionEvent ACTION_UP觸發(fā)    
    @Override
    public boolean onSingleTapUp(MotionEvent arg0) {
        Toast.makeText(this, "onSingleTapUp", Toast.LENGTH_SHORT).show();
        return true;
    }

    //-----------------------implement OnScaleGestureListener's method----------------------//

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        Toast.makeText(MainActivity.this, "onScale", Toast.LENGTH_SHORT).show();
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        Toast.makeText(MainActivity.this, "onScaleBegin", Toast.LENGTH_SHORT).show();
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        Toast.makeText(MainActivity.this, "onScaleEnd", Toast.LENGTH_SHORT).show();
    }
}

圖方便阳堕,我將整個(gè)MainActivity搬上來了跋理,你可以直接復(fù)制,然后加上對應(yīng)的布局和導(dǎo)包就行了恬总,接下來看一下運(yùn)行現(xiàn)象前普。

GestureDetector

上面測試的是GestureDetector,接下來測試一下ScaleGestureDetector

ScaleGestureDetector

如果你想要測試更多壹堰,比如GestureDetector里另外一個(gè)接口可以把我的代碼復(fù)制一下改一改就好了拭卿,這了就不作過多的贅述了,代碼會說話贱纠。

Matrix

這里只對Matrix作簡單的介紹峻厚。Android中Matrix是一個(gè)3 x 3的矩陣(說到矩陣都是二維的,不要看到3 x 3就想到3維去了)谆焊。先看一下Matrix的getValues和setValues方法:

    /** Copy 9 values from the matrix into the array.
    */
    public void getValues(float[] values) {
        if (values.length < 9) {
            throw new ArrayIndexOutOfBoundsException();
        }
        native_getValues(native_instance, values);
    }

    /** Copy 9 values from the array into the matrix.
        Depending on the implementation of Matrix, these may be
        transformed into 16.16 integers in the Matrix, such that
        a subsequent call to getValues() will not yield exactly
        the same values.
    */
    public void setValues(float[] values) {
        if (values.length < 9) {
            throw new ArrayIndexOutOfBoundsException();
        }
        native_setValues(native_instance, values);
    }

得到或者設(shè)置一個(gè)有9個(gè)元素的數(shù)組惠桃,繼續(xù)往下看發(fā)現(xiàn)調(diào)用的是個(gè)native修飾方法,好吧辖试,不繼續(xù)看了辜王,了解以上也差不多夠了。其內(nèi)部有

Matrix

Matrix的對圖像的處理可分為四類基本變換:
Translate 平移變換
Rotate 旋轉(zhuǎn)變換
Scale 縮放變換
Skew 錯(cuò)切變換

從字面上理解罐孝,矩陣中的MSCALE用于處理縮放變換呐馆,MSKEW用于處理錯(cuò)切變換,MTRANS用于處理平移變換莲兢,MPERSP用于處理透視變換汹来。實(shí)際中當(dāng)然不能完全按照字面上的說法去理解Matrix。

從字面上理解那9個(gè)量改艇,什么X軸縮放俗慈,什么扭曲,什么X軸偏移量遣耍,還帶不認(rèn)識的闺阱,沒關(guān)系,我們現(xiàn)在做的操作比較簡單舵变,不需要用到那么多的參數(shù)酣溃。比如我們現(xiàn)在想設(shè)置偏移量(200,200)
我們可以

Matrix matrix = new Matrix();
martrix.postTranslate(200,200);

實(shí)踐

寫完上面的東西,我已經(jīng)差不多是個(gè)廢人了……


我已經(jīng)差不多是個(gè)廢人了
我已經(jīng)差不多是個(gè)廢人了

畢竟當(dāng)年線性代數(shù)學(xué)的不咋滴纪隙,加上之前雖然有用過Matrix但是并不是很多赊豌,接下來進(jìn)入喜聞樂見的實(shí)戰(zhàn)時(shí)間。首先是不加任何限制绵咱,直接實(shí)現(xiàn)

package com.example.luo_pc.view.CustomView;

/**
 * Created by Luo_xiasuhuei321@163.com on 2016/9/24.
 * desc:
 */

import android.content.Context;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.ImageView;

public class ZZoomImageView extends ImageView implements View.OnTouchListener, ScaleGestureDetector.OnScaleGestureListener {
    //suppress the unused warning because maybe it will be used sometime later
    @SuppressWarnings("unused")
    private static final String TAG = "ZZoomImageView";

    /**
     * 最大放大倍數(shù)
     */
//    public static final float SCALE_MAX = 4.0f;

    /**
     * 默認(rèn)縮放
     */
//    private float initScale = 1.0f;

    /**
     * 手勢檢測
     */
    ScaleGestureDetector scaleGestureDetector = null;

    Matrix scaleMatrix = new Matrix();

    /**
     * 處理矩陣的9個(gè)值
     */
//    float[] martixValue = new float[9];

    public ZZoomImageView(Context context) {
        this(context, null);
    }

    public ZZoomImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ZZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setScaleType(ScaleType.MATRIX);
        scaleGestureDetector = new ScaleGestureDetector(context, this);
        this.setOnTouchListener(this);
    }

    /**
     * 獲取當(dāng)前縮放比例
     */
//    public float getScale() {
//        scaleMatrix.getValues(martixValue);
//        return martixValue[Matrix.MSCALE_X];
//    }

    //--------------------------implement OnTouchListener----------------------------//

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return scaleGestureDetector.onTouchEvent(event);
    }

    //----------------------implement OnScaleGestureListener------------------------//

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
//        float scale = getScale();
        float scaleFactor = detector.getScaleFactor();
        if (getDrawable() == null)
            return true;
//        Log.e(TAG,"君甚咸碘饼,此魚何能及君也?");
//        if (scaleFactor * scale < initScale)
//            scaleFactor = initScale / scale;
//        if (scaleFactor * scale > SCALE_MAX)
//            scaleFactor = SCALE_MAX / scale;
        //設(shè)置縮放比例
        scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth() / 2, getHeight() / 2);
        setImageMatrix(scaleMatrix);
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }
}

看一下跑起來是啥樣的

GIF.gif

將圖片放到中心

嗯,我要是把這個(gè)用在項(xiàng)目里艾恼,老大要是看到了估計(jì)我就沒有以后了……首先住涉,是沒有限制,可以無限縮小放大钠绍,第二是縮放中心點(diǎn)舆声,默認(rèn)都是ImageView中心,最后是剛開始加載出來我的圖片有部分沒加載柳爽,而且圖片不在imageview的中心媳握!我ImageView設(shè)置的可是倆match_parent啊。

坑爹.png

有問題沒事磷脯,我們一樣一樣蛾找,慢慢解決。首先是圖片位置赵誓,圖片位置的設(shè)定我們可以在圖片加載的時(shí)候?qū)⑺诺絀mageView的中心去打毛,同樣在這個(gè)過程中,我們可以判斷圖片的大小架曹,如果圖片大于ImageView尺寸則將其大小調(diào)整至ImageView的大小隘冲。首先我們在ImageView的構(gòu)造器中可能是無法獲取到ImageView和圖片的真實(shí)尺寸的,我們可以通過ViewTreeObserver在布局完成可以獲取真實(shí)尺寸的時(shí)候完成對圖片的調(diào)整绑雄。而OnGlobalLayoutListener是ViewTreeObserver的內(nèi)部接口展辞,當(dāng)一個(gè)視圖樹的布局發(fā)生改變時(shí),可以被ViewTreeObserver監(jiān)聽到万牺。所以新增代碼如下:

/**
 * 首先讓我們的類實(shí)現(xiàn)OnGlobalLayoutListener接口
public class ZZoomImageView extends ImageView implements View.OnTouchListener, ScaleGestureDetector.OnScaleGestureListener,
        ViewTreeObserver.OnGlobalLayoutListener

然后我們在此控件的onAttachedToWindow中設(shè)置監(jiān)聽罗珍,在onDetachedFromWindow移除這個(gè)監(jiān)聽:

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    //suppress deprecate warning because i have dealt with it 
    @Override
    @SuppressWarnings("deprecation")
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

最后是最重要的,在回調(diào)中對圖片進(jìn)行處理:

    @Override
    public void onGlobalLayout() {
        if (!once)
            return;
        Drawable d = getDrawable();
        if (d == null)
            return;
        //獲取imageview寬高
        int width = getWidth();
        int height = getHeight();

        //獲取圖片寬高
        int imgWidth = d.getIntrinsicWidth();
        int imgHeight = d.getIntrinsicHeight();

        float scale = 1.0f;

        //如果圖片的寬或高大于屏幕脚粟,縮放至屏幕的寬或者高
        if (imgWidth > width && imgHeight <= height)
            scale = (float) width / imgWidth;
        if (imgHeight > height && imgWidth <= width)
            scale = (float) height / imgHeight;
        //如果圖片寬高都大于屏幕覆旱,按比例縮小
        if (imgWidth > width && imgHeight > height)
            scale = Math.min((float) imgWidth / width, (float) imgHeight / height);
        Log.e(TAG, "scale" + scale);
        //將圖片移動(dòng)至屏幕中心
        scaleMatrix.postTranslate((width - imgWidth) / 2, (height - imgHeight) / 2);
        scaleMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
        setImageMatrix(scaleMatrix);
        once = false;
    }

對圖片的處理核心思想就是判斷圖片尺寸和當(dāng)前控件尺寸,圖片尺寸比控件大核无,就對圖片進(jìn)行縮放處理扣唱,并且最后將圖片移動(dòng)至控件中心處。代碼上的注釋寫的都很詳細(xì)了团南,各位看官可以自行閱讀≡肷常現(xiàn)在來看看變成啥樣了

ZZ-改

限制縮放

很好圖是到中間去了,那現(xiàn)在的問題就是無限縮小和放大的問題吐根。這個(gè)問題解決思路是很簡單的正歼,做個(gè)限制就行了。

嗯拷橘,新增如下幾個(gè)變量:

    /**
     * 最大放大倍數(shù)
     */
    public static final float SCALE_MAX = 4.0f;

    /**
     * 默認(rèn)縮放
     */
    private float initScale = 1.0f;

    /**
     * 處理矩陣的9個(gè)值
     */
    float[] martixValue = new float[9];

上面費(fèi)了那么多口水講到的matrix的九個(gè)值啥的局义,終于要出現(xiàn)了喜爷,是不是很激動(dòng)~(才怪),接下來搞個(gè)方法獲取縮放比例

    /**
     * 獲取當(dāng)前縮放比例
     */
    public float getScale() {
        scaleMatrix.getValues(martixValue);
        return martixValue[Matrix.MSCALE_X];
    }

之后為了獲取正確的初始縮放比例萄唇,在我們剛剛寫的** onGlobalLayout **中加句話:

initScale = scale;

當(dāng)然了檩帐,得是在獲取了scale值之后再添,因?yàn)槲覀冸m然設(shè)置了初始縮放比例穷绵,但是實(shí)際中可能因?yàn)閳D片大小發(fā)生了縮放行為轿塔,所以我們需要再次確定初始縮放比例特愿。接下來就是對縮放行為進(jìn)行限制了仲墨,修改onScale代碼如下:

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float scale = getScale();
        Log.e(TAG, "matrix scale---->" + scale);
        float scaleFactor = detector.getScaleFactor();
        Log.e(TAG, "scaleFactor---->" + scaleFactor);
        if (getDrawable() == null)
            return true;
//        Log.e(TAG,"君甚咸,此魚何能及君也揍障?");
        if ((scale < SCALE_MAX && scaleFactor > 1.0f)
                || (scale > initScale && scaleFactor < 1.0f)) {
            if (scaleFactor * scale < initScale)
                scaleFactor = initScale / scale;
            if (scaleFactor * scale > SCALE_MAX)
                scaleFactor = SCALE_MAX / scale;
            Log.e(TAG, "scaleFactor2---->" + scaleFactor);
            //設(shè)置縮放比例
            scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth() / 2, getHeight() / 2);
            setImageMatrix(scaleMatrix);
        }
        return true;
    }

對于以上的代碼目养,你可能會對兩個(gè)scale有所疑惑,一個(gè)scale是從matrix中獲得的毒嫡,一個(gè)是從縮放檢測中獲得的癌蚁。開始我看到hongyang大神的這段代碼我也是有所疑惑的,但是之后我自己寫了一遍兜畸,打了一下log努释,發(fā)現(xiàn)前一個(gè)在到達(dá)我們設(shè)置的最大值時(shí),值便會固定為4咬摇,后一個(gè)值會在1左右伐蒂。那么很明顯前一個(gè)值是圖片相對于初始尺寸的縮放,后一個(gè)是每一次縮放的實(shí)際比例肛鹏。理解了這個(gè)之后便容易解決了逸邦,使用如上代碼便可以限制縮放了。如果你對于縮放比例不滿意在扰,嗯缕减,自己設(shè)置就是了,反正也不復(fù)雜芒珠。效果圖就等下一個(gè)功能一起實(shí)現(xiàn)再放了桥狡。

以上一個(gè)簡單,還算能用的縮放ImageView就完成了皱卓,現(xiàn)在的問題是縮放中心是控件的中心裹芝,如果我想設(shè)置縮放中心是我按下去的地方呢?很簡單改一句代碼:

scaleMatrix.postScale(scaleFactor, scaleFactor, 
            getWidth() / 2, getHeight() / 2);

scaleMatrix.postScale(scaleFactor, scaleFactor, 
            detector.getFocusX(), detector.getFocusY());

但是這一改出事了……現(xiàn)在是能根據(jù)手勢縮放中心進(jìn)行縮放了好爬,但是縮放到最小時(shí)圖片位置可能發(fā)生了變化……現(xiàn)在還要解決的就是縮放時(shí)圖片位置變化局雄,新增如下方法:

    /**
     * 在縮放時(shí),控制范圍
     */
    private void checkBorderAndCenterWhenScale() {
        Matrix matrix = scaleMatrix;
        RectF rectF = new RectF();
        Drawable d = getDrawable();
        if (d != null) {
            rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            matrix.mapRect(rectF);
        }

        float deltaX = 0;
        float deltaY = 0;
        int width = getWidth();
        int height = getHeight();
        // 如果寬或高大于屏幕存炮,則控制范圍
        if (rectF.width() >= width) {
            if (rectF.left > 0) {
                deltaX = -rectF.left;
            }
            if (rectF.right < width) {
                deltaX = width - rectF.right;
            }
        }
        if (rectF.height() >= height) {
            if (rectF.top > 0) {
                deltaY = -rectF.top;
            }
            if (rectF.bottom < height) {
                deltaY = height - rectF.bottom;
            }
        }
        // 如果寬或高小于屏幕炬搭,則讓其居中
        if (rectF.width() < width) {
            deltaX = width * 0.5f - rectF.right + 0.5f * rectF.width();
        }
        if (rectF.height() < height) {
            deltaY = height * 0.5f - rectF.bottom + 0.5f * rectF.height();
        }
        scaleMatrix.postTranslate(deltaX, deltaY);
    }

然后在onScale方法里調(diào)用以上檢測的方法:

        if ((scale < SCALE_MAX && scaleFactor > 1.0f)
                || (scale > initScale && scaleFactor < 1.0f)) {
            if (scaleFactor * scale < initScale)
                scaleFactor = initScale / scale;
            if (scaleFactor * scale > SCALE_MAX)
                scaleFactor = SCALE_MAX / scale;
            Log.e(TAG, "scaleFactor2---->" + scaleFactor);
            //設(shè)置縮放比例
            scaleMatrix.postScale(scaleFactor, scaleFactor,
                    detector.getFocusX(), detector.getFocusY());
            checkBorderAndCenterWhenScale();
            setImageMatrix(scaleMatrix);
        }

最終成型

以上代碼算出初步的能用了蜈漓,不過還有一點(diǎn)值得注意的地方,如果你在onTouch這個(gè)方法里的代碼是這樣的:

return scaleGestureDetector.onTouchEvent(event);

那么所有的事件都會被消費(fèi)宫盔,因?yàn)槲尹c(diǎn)到scaleGestureDetector的onTouch方法里融虽,沒看到return false的東西,所以你設(shè)置的oncilck事件之類的都沒什么卵用灼芭。

對于我來說這樣是不行的有额,因?yàn)槲蚁M脩酎c(diǎn)擊一次之后可以退出當(dāng)前界面,所以你可以調(diào)用sacleGestureDetector.onTouchEvent(event)但是返回false彼绷,不消耗這個(gè)事件巍佑,讓onClick來處理點(diǎn)擊事件。當(dāng)我想的很美的時(shí)候寄悯,卻發(fā)現(xiàn)這么做雖然點(diǎn)擊事件會被處理萤衰,而且縮放也正常,但是縮放的操作會被判斷為點(diǎn)擊事件猜旬,也就是說這么干不行了脆栋。我的腦海中第二個(gè)想到的解決方案是回調(diào),既然系統(tǒng)的回調(diào)不行了洒擦,那我自己設(shè)置一個(gè)時(shí)間椿争,在這個(gè)時(shí)間之內(nèi)就是click事件,我在這個(gè)事件的回調(diào)里把當(dāng)前界面退出了不就行了熟嫩。實(shí)現(xiàn)如下:

    private ClickCloseListener c;

    public interface ClickCloseListener {
        void close();
    }

    public void setClickCloseListener(ClickCloseListener c) {
        this.c = c;
    }

    /**
     * 按下的時(shí)間
     */
    long downTime;

    /**
     * down 和 up之間的間隔
     */
    long closeTime = 100L;

    /**
     * 設(shè)置按下的時(shí)間
     */
    //suppress unused warning for no reason
    @SuppressWarnings("unused")
    public void setClickTime(long time) {
        this.closeTime = time;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);
        //如果監(jiān)聽為null秦踪,消費(fèi)該事件,不讓onclick生效
        if (c == null)
            return true;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downTime = System.currentTimeMillis();
                break;
            case MotionEvent.ACTION_UP:
                downTime = System.currentTimeMillis() - downTime;
                if (downTime < closeTime)
                    c.close();
                break;
            default:
                break;
        }
        return true;
    }

最后看一下效果圖吧~

ZZ-最終打死都不改版

當(dāng)然了邦危,自己搞的點(diǎn)擊事件有點(diǎn)不靠譜洋侨,時(shí)間間隔設(shè)置為100ms,有點(diǎn)短了倦蚪,你可以自己設(shè)置希坚,不過這篇文到這里也就結(jié)束了。本來還想連什么移動(dòng)一起加上陵且,嗯裁僧,現(xiàn)在發(fā)現(xiàn)好像篇幅超出了我的控制。暫且還是算了吧~而且這個(gè)姑且也算是能用了慕购,只不過適用的場景只是查看大圖的一個(gè)單獨(dú)的界面聊疲。這個(gè)簡單的小東西就寫到這了。完整代碼就放在這把沪悲,也懶得上傳github了:

package com.example.luo_pc.view.CustomView;

/**
 * Created by Luo_xiasuhuei321@163.com on 2016/9/24.
 * desc:
 */

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

public class ZZoomImageView extends ImageView implements View.OnTouchListener, ScaleGestureDetector.OnScaleGestureListener,
        ViewTreeObserver.OnGlobalLayoutListener {
    //suppress the unused warning because maybe it will be used sometime later
    @SuppressWarnings("unused")
    private static final String TAG = "ZZoomImageView";

    /**
     * 最大放大倍數(shù)
     */
    public static final float SCALE_MAX = 4.0f;

    /**
     * 默認(rèn)縮放
     */
    private float initScale = 1.0f;

    /**
     * 手勢檢測
     */
    ScaleGestureDetector scaleGestureDetector = null;

    Matrix scaleMatrix = new Matrix();

    /**
     * 處理矩陣的9個(gè)值
     */
    float[] martixValue = new float[9];

    public ZZoomImageView(Context context) {
        this(context, null);
    }

    public ZZoomImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ZZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setScaleType(ScaleType.MATRIX);
        scaleGestureDetector = new ScaleGestureDetector(context, this);
        this.setOnTouchListener(this);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    //suppress deprecate warning because i have dealt with it 
    @Override
    @SuppressWarnings("deprecation")
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    /**
     * 獲取當(dāng)前縮放比例
     */
    public float getScale() {
        scaleMatrix.getValues(martixValue);
        return martixValue[Matrix.MSCALE_X];
    }

    /**
     * 在縮放時(shí)获洲,控制范圍
     */
    private void checkBorderAndCenterWhenScale() {
        Matrix matrix = scaleMatrix;
        RectF rectF = new RectF();
        Drawable d = getDrawable();
        if (d != null) {
            rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            matrix.mapRect(rectF);
        }

        float deltaX = 0;
        float deltaY = 0;
        int width = getWidth();
        int height = getHeight();
        // 如果寬或高大于屏幕,則控制范圍
        if (rectF.width() >= width) {
            if (rectF.left > 0) {
                deltaX = -rectF.left;
            }
            if (rectF.right < width) {
                deltaX = width - rectF.right;
            }
        }
        if (rectF.height() >= height) {
            if (rectF.top > 0) {
                deltaY = -rectF.top;
            }
            if (rectF.bottom < height) {
                deltaY = height - rectF.bottom;
            }
        }
        // 如果寬或高小于屏幕殿如,則讓其居中
        if (rectF.width() < width) {
            deltaX = width * 0.5f - rectF.right + 0.5f * rectF.width();
        }
        if (rectF.height() < height) {
            deltaY = height * 0.5f - rectF.bottom + 0.5f * rectF.height();
        }
        scaleMatrix.postTranslate(deltaX, deltaY);
    }

    //--------------------------implement OnTouchListener----------------------------//
    private ClickCloseListener c;

    public interface ClickCloseListener {
        void close();
    }

    public void setClickCloseListener(ClickCloseListener c) {
        this.c = c;
    }

    /**
     * 按下的時(shí)間
     */
    long downTime;

    /**
     * down 和 up之間的間隔
     */
    long closeTime = 100L;

    /**
     * 設(shè)置按下的時(shí)間
     */
    //suppress unused warning for no reason
    @SuppressWarnings("unused")
    public void setClickTime(long time) {
        this.closeTime = time;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);
        //如果監(jiān)聽為null贡珊,消費(fèi)該事件最爬,不讓onclick生效
        if (c == null)
            return true;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downTime = System.currentTimeMillis();
                break;
            case MotionEvent.ACTION_UP:
                downTime = System.currentTimeMillis() - downTime;
                if (downTime < closeTime)
                    c.close();
                break;
            default:
                break;
        }
        return true;
    }

    //----------------------implement OnScaleGestureListener------------------------//

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float scale = getScale();
        float scaleFactor = detector.getScaleFactor();
        if (getDrawable() == null)
            return true;
//        Log.e(TAG,"君甚咸,此魚何能及君也门岔?");
        if ((scale < SCALE_MAX && scaleFactor > 1.0f)
                || (scale > initScale && scaleFactor < 1.0f)) {
            if (scaleFactor * scale < initScale)
                scaleFactor = initScale / scale;
            if (scaleFactor * scale > SCALE_MAX)
                scaleFactor = SCALE_MAX / scale;
            Log.e(TAG, "scaleFactor2---->" + scaleFactor);
            //設(shè)置縮放比例
            scaleMatrix.postScale(scaleFactor, scaleFactor,
                    detector.getFocusX(), detector.getFocusY());
            checkBorderAndCenterWhenScale();
            setImageMatrix(scaleMatrix);
        }
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }

    boolean once = true;

    @Override
    public void onGlobalLayout() {
        if (!once)
            return;
        Drawable d = getDrawable();
        if (d == null)
            return;
        //獲取imageview寬高
        int width = getWidth();
        int height = getHeight();

        //獲取圖片寬高
        int imgWidth = d.getIntrinsicWidth();
        int imgHeight = d.getIntrinsicHeight();

        float scale = 1.0f;

        //如果圖片的寬或高大于屏幕爱致,縮放至屏幕的寬或者高
        if (imgWidth > width && imgHeight <= height)
            scale = (float) width / imgWidth;
        if (imgHeight > height && imgWidth <= width)
            scale = (float) height / imgHeight;
        //如果圖片寬高都大于屏幕,按比例縮小
        if (imgWidth > width && imgHeight > height)
            scale = Math.min((float) imgWidth / width, (float) imgHeight / height);
        initScale = scale;
        //將圖片移動(dòng)至屏幕中心
        scaleMatrix.postTranslate((width - imgWidth) / 2, (height - imgHeight) / 2);
        scaleMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
        setImageMatrix(scaleMatrix);
        once = false;
    }
}

參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寒随,一起剝皮案震驚了整個(gè)濱河市糠悯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妻往,老刑警劉巖互艾,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒲讯,居然都是意外死亡忘朝,警方通過查閱死者的電腦和手機(jī)灰署,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門判帮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溉箕,你說我怎么就攤上這事晦墙。” “怎么了肴茄?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵晌畅,是天一觀的道長。 經(jīng)常有香客問我寡痰,道長抗楔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任拦坠,我火速辦了婚禮连躏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贞滨。我一直安慰自己入热,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布晓铆。 她就那樣靜靜地躺著勺良,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骄噪。 梳的紋絲不亂的頭發(fā)上尚困,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機(jī)與錄音链蕊,去河邊找鬼事甜。 笑死忙芒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的讳侨。 我是一名探鬼主播呵萨,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跨跨!你這毒婦竟也來了潮峦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤勇婴,失蹤者是張志新(化名)和其女友劉穎忱嘹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耕渴,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拘悦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了橱脸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片础米。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖添诉,靈堂內(nèi)的尸體忽然破棺而出屁桑,到底是詐尸還是另有隱情,我是刑警寧澤栏赴,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布蘑斧,位于F島的核電站,受9級特大地震影響须眷,放射性物質(zhì)發(fā)生泄漏竖瘾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一花颗、第九天 我趴在偏房一處隱蔽的房頂上張望捕传。 院中可真熱鬧,春花似錦捎稚、人聲如沸乐横。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葡公。三九已至,卻和暖如春条霜,著一層夾襖步出監(jiān)牢的瞬間催什,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工宰睡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蒲凶,地道東北人气筋。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像旋圆,于是被迫代替她去往敵國和親宠默。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 手勢圖片控件 PinchImageView 點(diǎn)擊圖片框架 photoView packagecom.example...
    Ztufu閱讀 727評論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,161評論 25 707
  • 概述 項(xiàng)目開發(fā)中灵巧,大家APP開發(fā)一般都會用到上傳圖片搀矫,比如是上傳了自己的生活照,然后在某個(gè)界面處查看上傳的圖片刻肄,這...
    青蛙要fly閱讀 6,200評論 2 20
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫瓤球、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評論 4 62
  • 《南京·清涼山》 昔諸葛駐馬 虎踞龍?bào)?又后主避暑 三絕遺世 曾有金戈鐵馬 亦有詩情畫意 無數(shù)王侯將相 文人雅士 ...
    梁子仗劍走天下閱讀 582評論 3 5