【Android】實(shí)現(xiàn)一個(gè)手勢(shì)可旋轉(zhuǎn)/縮放/移動(dòng)的ImageView

我們?cè)陧?xiàng)目里經(jīng)常會(huì)用到需要使用ImageView的地方云石,這時(shí)候有些產(chǎn)品經(jīng)理可能會(huì)提出需要讓這個(gè)ImageView玩出花樣旋起來盔腔,原來的那個(gè)只能顯示圖片的就不好使了涤妒,需要考慮去實(shí)現(xiàn)一個(gè)功能更強(qiáng)大的ImageView了格二。

一歪沃、 準(zhǔn)備工作

由于我們要實(shí)現(xiàn)的是一個(gè)可自由變換的ImageView,那么首先要想到的是如何通過手勢(shì)的方式對(duì)特定的操作進(jìn)行區(qū)分,這里使用了Github中封裝手勢(shì)操作的組件庫Almeros/android-gesture-detectors达吞,其對(duì)移動(dòng)张弛、旋轉(zhuǎn)和縮放都進(jìn)行了很好的封裝,可以幫助我們減少很多麻煩酪劫。

repositories {
        //添加jitpack倉庫
        maven { url 'https://jitpack.io' }
}
implementation 'com.github.Almeros:android-gesture-detectors:v1.0.1'

二吞鸭、實(shí)現(xiàn)旋轉(zhuǎn)

效果

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

  1. 聲明RotateGestureDetector對(duì)象,初始化并設(shè)置監(jiān)聽器等覆糟;
  2. 為ImageView設(shè)置setOnTouchListener事件刻剥,在其回調(diào)中讓RotateGestureDetector實(shí)例對(duì)象接手觸摸事件;
  3. 在RotateGestureDetector的監(jiān)聽器回調(diào)中滩字,獲取對(duì)應(yīng)的變化值造虏,并設(shè)置給ImageView。

代碼實(shí)例

public class DemoRotateImageView extends AppCompatImageView implements ViewTreeObserver.OnGlobalLayoutListener{
    //操作對(duì)象
    private Matrix mMatrix;
    //旋轉(zhuǎn)手勢(shì)監(jiān)聽器
    private RotateGestureDetector mRotateGestureDetector ;
    //當(dāng)前焦點(diǎn)
    private float mFocusX, mFocusY;

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

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

    public DemoRotateImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //記住麦箍,一定要把ScaleType設(shè)置成ScaleType.MATRIX漓藕,否則無法縮放
        setScaleType(ScaleType.MATRIX);
        mMatrix = new Matrix();
        //手勢(shì)旋轉(zhuǎn)
        mRotateGestureDetector = new RotateGestureDetector(context, new RotateGestureDetector.OnRotateGestureListener() {
            @Override
            public boolean onRotate(RotateGestureDetector detector) {
                float rotationDegrees = -detector.getRotationDegreesDelta();
                mMatrix.postRotate(rotationDegrees,mFocusX,mFocusY);
                setImageMatrix(mMatrix);
                return true;
            }

            @Override
            public boolean onRotateBegin(RotateGestureDetector detector) {
                return true;
            }

            @Override
            public void onRotateEnd(RotateGestureDetector detector) {

            }
        });
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mRotateGestureDetector.onTouchEvent(event);
        return true ;
    }
    public boolean mIsOneLoad = true;
    @Override
    public void onGlobalLayout() {
        if (mIsOneLoad) {
            //獲取圖片,如果沒有圖片則直接退出
            Drawable d = getDrawable();
            if (d == null){
                return;
            }
            //獲取圖片的寬和高
            int dw = d.getIntrinsicWidth();
            int dh = d.getIntrinsicHeight();
            //將其中圖片中心點(diǎn)作為焦點(diǎn)
            mFocusX = dw * 1.0f / 2 ;
            mFocusY = dh * 1.0f / 2 ;
            mIsOneLoad = false;
        }
    }
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //修改窗口事件中對(duì)視圖變化進(jìn)行監(jiān)聽
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
}

三、實(shí)現(xiàn)縮放

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

  1. 聲明ScaleGestureDetector對(duì)象挟裂,初始化并設(shè)置監(jiān)聽器等享钞;
  2. 為ImageView設(shè)置setOnTouchListener事件,在其回調(diào)中讓ScaleGestureDetector實(shí)例對(duì)象接手觸摸事件诀蓉;
  3. 在onGlobalLayout中設(shè)置初始栗竖、最大、最小縮放值渠啤;
  4. 在ScaleGestureDetector的監(jiān)聽器回調(diào)中狐肢,獲取焦點(diǎn)并對(duì)縮放進(jìn)行處理后設(shè)置ImageView;
  5. 當(dāng)縮放值大于最大和最小縮放值后不允許其繼續(xù)縮放沥曹。

代碼實(shí)例

public class DemoScaleImageView extends AppCompatImageView implements ViewTreeObserver.OnGlobalLayoutListener{
    //操作對(duì)象
    private Matrix mMatrix;
    //縮放手勢(shì)監(jiān)聽器
    private ScaleGestureDetector mScaleGestureDetector ;
    //圖片寬高
    private float mFocusX, mFocusY;
    //初始化的比例,也就是最小比例
    private float mInitScale;
    //圖片最大比例
    private float mMaxScale;
    //圖片最小比例
    private float mMinScale ;

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

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

    public DemoScaleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //記住份名,一定要把ScaleType設(shè)置成ScaleType.MATRIX碟联,否則無法縮放
        setScaleType(ScaleType.MATRIX);
        mMatrix = new Matrix();
        //手勢(shì)旋轉(zhuǎn)
        mScaleGestureDetector = new ScaleGestureDetector(context,new ScaleGestureDetector.SimpleOnScaleGestureListener(){
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                //開始縮放
                float scaleFactor = detector.getScaleFactor();
                Log.i("縮放操作值",scaleFactor+"");
                float scale = getCurrentScale();
                //縮放操作需要在mMinScale和mMaxScale中進(jìn)行 范圍外的話就攔截
                if(scale < mMaxScale && scale > mMinScale){
                    mMatrix.postScale(scaleFactor, scaleFactor, mFocusX, mFocusY);
                    setImageMatrix(mMatrix);
                    return true;
                }else{
                    return false;
                }
            }

            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                //開始縮放并獲取焦點(diǎn)
                mFocusX = detector.getFocusX();
                mFocusY = detector.getFocusY();
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                super.onScaleEnd(detector);
            }
        });
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mScaleGestureDetector.onTouchEvent(event);
        return true ;
    }
    public boolean mIsOneLoad = true;
    @Override
    public void onGlobalLayout() {
        if (mIsOneLoad) {
            //獲取圖片,如果沒有圖片則直接退出
            Drawable d = getDrawable();
            if (d == null){
                return;
            }
            //獲取圖片的寬和高
            int dw = d.getIntrinsicWidth();
            int dh = d.getIntrinsicHeight();
            //使其圖片中點(diǎn)作為焦點(diǎn)
            mFocusX = dw * 1.0f / 2 ;
            mFocusY = dh * 1.0f / 2 ;
            //初始化縮放比例
            mInitScale = getCurrentScale();
            //手勢(shì)放大時(shí)最大比例
            mMaxScale = mInitScale * 2;
            //手勢(shì)縮小時(shí)的最小比例
            mMinScale = mInitScale * 0.8f ;
            mIsOneLoad = false;
        }
    }
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    /**
     * 獲取當(dāng)前圖片的縮放值
     * @return 縮放值
     */
    private float getCurrentScale() {
        float[] values = new float[9];
        mMatrix.getValues(values);
        return values[Matrix.MSCALE_X];
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市同窘,隨后出現(xiàn)的幾起案子玄帕,更是在濱河造成了極大的恐慌,老刑警劉巖想邦,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裤纹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡丧没,警方通過查閱死者的電腦和手機(jī)鹰椒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呕童,“玉大人漆际,你說我怎么就攤上這事《崴牵” “怎么了奸汇?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)往声。 經(jīng)常有香客問我擂找,道長(zhǎng),這世上最難降的妖魔是什么浩销? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任贯涎,我火速辦了婚禮,結(jié)果婚禮上慢洋,老公的妹妹穿的比我還像新娘塘雳。我一直安慰自己,他們只是感情好普筹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布败明。 她就那樣靜靜地躺著,像睡著了一般太防。 火紅的嫁衣襯著肌膚如雪肩刃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天杏头,我揣著相機(jī)與錄音,去河邊找鬼沸呐。 笑死醇王,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崭添。 我是一名探鬼主播寓娩,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了棘伴?” 一聲冷哼從身側(cè)響起寞埠,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎焊夸,沒想到半個(gè)月后仁连,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阱穗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年饭冬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揪阶。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昌抠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲁僚,到底是詐尸還是另有隱情炊苫,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布冰沙,位于F島的核電站侨艾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏倦淀。R本人自食惡果不足惜蒋畜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撞叽。 院中可真熱鬧姻成,春花似錦、人聲如沸愿棋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糠雨。三九已至才睹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甘邀,已是汗流浹背琅攘。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留松邪,地道東北人坞琴。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像逗抑,于是被迫代替她去往敵國(guó)和親剧辐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寒亥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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