Android 手勢檢測實戰 打造支持縮放平移的圖片預覽效果(下)

jopen 8年前發布 | 10K 次閱讀 Android開發 移動開發

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/39480503,本文出自:【張鴻洋的博客】

上一篇已經帶大家實現了自由的放大縮小圖片,簡單介紹了下Matrix;具體請參考:Android 手勢檢測實戰 打造支持縮放平移的圖片預覽效果(上);本篇繼續完善我們的ImageView~~

首先加入放大后的移動~~

1、自由的進行移動

我們在onTouchEvent里面,加上移動的代碼,當然了,必須長或寬大于屏幕才可以移動~~~

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

        float x = 0, y = 0;
        // 拿到觸摸點的個數
        final int pointerCount = event.getPointerCount();
        // 得到多個觸摸點的x與y均值
        for (int i = 0; i < pointerCount; i++)
        {
            x += event.getX(i);
            y += event.getY(i);
        }
        x = x / pointerCount;
        y = y / pointerCount;

        /**
         * 每當觸摸點發生變化時,重置mLasX , mLastY 
         */
        if (pointerCount != lastPointerCount)
        {
            isCanDrag = false;
            mLastX = x;
            mLastY = y;
        }


        lastPointerCount = pointerCount;

        switch (event.getAction())
        {
        case MotionEvent.ACTION_MOVE:
            Log.e(TAG, "ACTION_MOVE");
            float dx = x - mLastX;
            float dy = y - mLastY;

            if (!isCanDrag)
            {
                isCanDrag = isCanDrag(dx, dy);
            }
            if (isCanDrag)
            {
                RectF rectF = getMatrixRectF();
                if (getDrawable() != null)
                {
                    isCheckLeftAndRight = isCheckTopAndBottom = true;
                    // 如果寬度小于屏幕寬度,則禁止左右移動
                    if (rectF.width() < getWidth())
                    {
                        dx = 0;
                        isCheckLeftAndRight = false;
                    }
                    // 如果高度小雨屏幕高度,則禁止上下移動
                    if (rectF.height() < getHeight())
                    {
                        dy = 0;
                        isCheckTopAndBottom = false;
                    }
                    mScaleMatrix.postTranslate(dx, dy);
                    checkMatrixBounds();
                    setImageMatrix(mScaleMatrix);
                }
            }
            mLastX = x;
            mLastY = y;
            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            Log.e(TAG, "ACTION_UP");
            lastPointerCount = 0;
            break;
        }

        return true;
    }

首先我們拿到觸摸點的數量,然后求出多個觸摸點的平均值,設置給我們的mLastX , mLastY , 然后在移動的時候,得到dx ,dy 進行范圍檢查以后,調用mScaleMatrix.postTranslate進行設置偏移量,當然了,設置完成以后,還需要再次校驗一下,不能把圖片移動的與屏幕邊界出現白邊,校驗完成后,調用setImageMatrix.

這里:需要注意一下,我們沒有復寫ACTION_DOWM,是因為,ACTION_DOWN在多點觸控的情況下,只要有一個手指按下狀態,其他手指按下不會再次觸發ACTION_DOWN,但是多個手指以后,觸摸點的平均值會發生很大變化,所以我們沒有用到ACTION_DOWN。每當觸摸點的數量變化,我們就會跟新當前的mLastX,mLastY.

下面是上面用到的兩個私有方法,一個用于檢查邊界,一個用于判斷是否是拖動的操作:

/**
     * 移動時,進行邊界判斷,主要判斷寬或高大于屏幕的
     */
    private void checkMatrixBounds()
    {
        RectF rect = getMatrixRectF();

        float deltaX = 0, deltaY = 0;
        final float viewWidth = getWidth();
        final float viewHeight = getHeight();
        // 判斷移動或縮放后,圖片顯示是否超出屏幕邊界
        if (rect.top > 0 && isCheckTopAndBottom)
        {
            deltaY = -rect.top;
        }
        if (rect.bottom < viewHeight && isCheckTopAndBottom)
        {
            deltaY = viewHeight - rect.bottom;
        }
        if (rect.left > 0 && isCheckLeftAndRight)
        {
            deltaX = -rect.left;
        }
        if (rect.right < viewWidth && isCheckLeftAndRight)
        {
            deltaX = viewWidth - rect.right;
        }
        mScaleMatrix.postTranslate(deltaX, deltaY);
    }

    /**
     * 是否是推動行為
     * 
     * @param dx
     * @param dy
     * @return
     */
    private boolean isCanDrag(float dx, float dy)
    {
        return Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
    }

這樣,我們就可以快樂的放大、縮小加移動了~~~

效果圖:這次換個男人的圖片,我們越獄的主角之一,TBug~

我們的縮放+移動搞定~~

2、雙擊放大與縮小

談到雙擊事件,我們的GestureDetector終于要登場了,這哥們可以捕獲雙擊事件~~

1、GestureDetector的使用

因為GestureDetector設置監聽器的話,方法一大串,而我們只需要onDoubleTap這個回調,所以我們準備使用它的一個內部類SimpleOnGestureListener,對接口的其他方法實現了空實現。

不過還有幾個問題需要討論下,才能開始我們的代碼:

1、我們雙擊尺寸如何變化?

我是這樣的,根據當前的縮放值,如果是小于2的,我們雙擊直接到變為原圖的2倍;如果是2,4之間的,我們雙擊直接為原圖的4倍;其他狀態也就是4倍,雙擊后還原到最初的尺寸。

如果你覺得這樣不合適,可以根據自己的愛好調整。

2、我們雙擊變化,需要一個動畫~~比如我們上例的演示圖,圖片很大,全屏顯示的時候initScale=0.5左后,如果雙擊后變為2,也就是瞬間大了四倍,沒有一個過渡的效果的話,給用戶的感覺會特別差。所以,我們準備使用postDelay執行一個Runnable,Runnable中再次根據的當然的縮放值繼續執行。

首先我們在構造方法中,完成對GestureDetector的初始化,以及設置onDoubleTap監聽

public ZoomImageView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mScaleGestureDetector = new ScaleGestureDetector(context, this);
        mGestureDetector = new GestureDetector(context,
                new SimpleOnGestureListener()
                {
                    @Override
                    public boolean onDoubleTap(MotionEvent e)
                    {
                        if (isAutoScale == true)
                            return true;

                        float x = e.getX();
                        float y = e.getY();
                        Log.e("DoubleTap", getScale() + " , " + initScale);
                        if (getScale() < SCALE_MID)
                        {
                            ZoomImageView.this.postDelayed(
                                    new AutoScaleRunnable(SCALE_MID, x, y), 16);
                            isAutoScale = true;
                        } else if (getScale() >= SCALE_MID
                                && getScale() < SCALE_MAX)
                        {
                            ZoomImageView.this.postDelayed(
                                    new AutoScaleRunnable(SCALE_MAX, x, y), 16);
                            isAutoScale = true;
                        } else
                        {
                            ZoomImageView.this.postDelayed(
                                    new AutoScaleRunnable(initScale, x, y), 16);
                            isAutoScale = true;
                        }

                        return true;
                    }
                });
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        super.setScaleType(ScaleType.MATRIX);
        this.setOnTouchListener(this);
    }

1、當雙擊的時候,首先判斷是否正在自動縮放,如果在,直接retrun ; 

2、然后就進入了我們的if,如果當然是scale小于2,則通過view.發送一個Runnable進行執行;其他類似;

下面看我們的Runnable的代碼:

/**
     * 自動縮放的任務
     * 
     * @author zhy
     * 
     */
    private class AutoScaleRunnable implements Runnable
    {
        static final float BIGGER = 1.07f;
        static final float SMALLER = 0.93f;
        private float mTargetScale;
        private float tmpScale;

        /**
         * 縮放的中心
         */
        private float x;
        private float y;

        /**
         * 傳入目標縮放值,根據目標值與當前值,判斷應該放大還是縮小
         * 
         * @param targetScale
         */
        public AutoScaleRunnable(float targetScale, float x, float y)
        {
            this.mTargetScale = targetScale;
            this.x = x;
            this.y = y;
            if (getScale() < mTargetScale)
            {
                tmpScale = BIGGER;
            } else
            {
                tmpScale = SMALLER;
            }

        }

        @Override
        public void run()
        {
            // 進行縮放
            mScaleMatrix.postScale(tmpScale, tmpScale, x, y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(mScaleMatrix);

            final float currentScale = getScale();
            //如果值在合法范圍內,繼續縮放
            if (((tmpScale > 1f) && (currentScale < mTargetScale))
                    || ((tmpScale < 1f) && (mTargetScale < currentScale)))
            {
                ZoomImageView.this.postDelayed(this, 16);
            } else//設置為目標的縮放比例
            {
                final float deltaScale = mTargetScale / currentScale;
                mScaleMatrix.postScale(deltaScale, deltaScale, x, y);
                checkBorderAndCenterWhenScale();
                setImageMatrix(mScaleMatrix);
                isAutoScale = false;
            }

        }
    }

代碼寫完了,我們依然需要把我們的event傳給它,依然是在onTouch方法:

@Override
    public boolean onTouch(View v, MotionEvent event)
    {
        if (mGestureDetector.onTouchEvent(event))
            return true;

好了,雙擊放大與縮小的功能就搞定了,下面測試下~~~

效果圖,終于可以用模擬器了~~:

 

3、處理與ViewPager的沖突

直接把我們的圖片作為ViewPager的Item,可想而知,肯定有沖突~~

1、布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.view.ViewPager
        android:id="@+id/id_viewpager"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    </android.support.v4.view.ViewPager>

</RelativeLayout>


2、Activity代碼

package com.zhy.zhy_scalegesturedetector02;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.zhy.view.ZoomImageView;

public class MainActivity extends Activity
{
    private ViewPager mViewPager;
    private int[] mImgs = new int[] { R.drawable.tbug, R.drawable.a,
            R.drawable.xx };
    private ImageView[] mImageViews = new ImageView[mImgs.length];

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.vp);

        mViewPager = (ViewPager) findViewById(R.id.id_viewpager);
        mViewPager.setAdapter(new PagerAdapter()
        {

            @Override
            public Object instantiateItem(ViewGroup container, int position)
            {
                ZoomImageView imageView = new ZoomImageView(
                        getApplicationContext());
                imageView.setImageResource(mImgs[position]);
                container.addView(imageView);
                mImageViews[position] = imageView;
                return imageView;
            }

            @Override
            public void destroyItem(ViewGroup container, int position,
                    Object object)
            {
                container.removeView(mImageViews[position]);
            }

            @Override
            public boolean isViewFromObject(View arg0, Object arg1)
            {
                return arg0 == arg1;
            }

            @Override
            public int getCount()
            {
                return mImgs.length;
            }
        });

    }
}

現在直接運行,發現ViewPager好著呢,但是我們的圖片放大以后,移動和ViewPager沖突了,又不能移動了~。。。擦擦擦。。。

3、處理沖突

現在我們迅速的想一想,記得之前學習過事件分發機制,我們的ZoomImageView在ViewPager中,如果我們不想被攔截,那么如何做呢?
首先不想被攔截的條件是:我們的寬或高大于屏幕寬或高時,因為此時可以移動,我們不想被攔截。接下來,不想被攔截:

getParent().requestDisallowInterceptTouchEvent(true);

一行代碼足以,如果你對事件分發中,不被攔截不清晰,可以參考:如何不被攔截 。

放在一起我們的代碼就是:

switch (event.getAction())
        {
        case MotionEvent.ACTION_DOWN:
            if (rectF.width() > getWidth() || rectF.height() > getHeight())
            {
                getParent().requestDisallowInterceptTouchEvent(true);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if (rectF.width() > getWidth() || rectF.height() > getHeight())
            {
                getParent().requestDisallowInterceptTouchEvent(true);
            }

~當寬或高大于屏幕寬或高時,拖動效果認為是移動圖片,反之則讓ViewPager去處理

此時的效果:

ok,現在已經解決了和ViewPager的沖突,ps:尼瑪不應該雙擊還能放大兩次到4倍,,,,,好惡心。。。

4、到達邊界事件交給ViewPager處理

可能有些用戶還希望,當圖片到達邊界時,不能再拖動的時候,能夠把事件給ViewPager

那就在ACTION_MOVE中,判斷當前已經到達邊界,且還在拉的時候,事件交給ViewPager

if (isCanDrag)
            {

                if (getDrawable() != null)
                {
                    if (getMatrixRectF().left == 0 && dx > 0)
                    {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }

                    if (getMatrixRectF().right == getWidth() && dx < 0)
                    {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }

此時的效果:

好了,其實添加了這個之后,體驗一般哈~~~

 

 

終于寫完了,代碼中可能存在BUG,發現問題,或者解決了發現的BUG時,希望可以直接在博客下面留言,也能夠方便他人~~

到此,我們的Android 手勢檢測實戰 打造支持縮放平移的圖片預覽效果 結束~~!

 

建議把雙擊放大到4倍的地方,注釋掉一個If

//                       else if (getScale() >= SCALE_MID
//                              && getScale() < SCALE_MAX)
//                      {
//                          ZoomImageView.this.postDelayed(
//                                  new AutoScaleRunnable(SCALE_MAX, x, y), 16);
//                          isAutoScale = true;
//                      }

連續雙擊放大,感覺不爽,代碼已經上傳,我就不重傳了,如果你也覺得不爽,可以自行注釋。

 

單圖版源碼點擊下載

 

ViewPager版源碼下載

 

 

----------------------------------------------------------------------------------------------------------

博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0側滑




 

 

 

 

 

 

 

 

 

來自: http://blog.csdn.net//lmj623565791/article/details/39480503

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!