Android viewpager + 可縮放的imageview

來源:互聯網
上載者:User

標籤:

http://files.cnblogs.com/files/liaolandemengxiang/PhotoWallFallsDemo.rar

http://files.cnblogs.com/files/liaolandemengxiang/ViewPager_imageview%E7%9A%84%E7%BC%A9%E6%94%BE%E4%BC%98%E5%8C%96%E5%90%8E.rar

第一個地址是中心縮放的demo,值得注意的是,這裡面的位移是相對於(0,0)位置的位移量,每次都是畫布重新畫圖片

第二個是最佳化了以後的viewpager 和zoomimageview的組合的demo,是通過imageview的回呼函數還控制viewpager是否可以縮放。並對原來的bug進行修改。bug主要的問題是存在於沒有考慮高度大於螢幕高度的長圖片的縮放,已經中心位置的計算錯誤。還有就是對於最大縮放倍數的理解,初始化縮放比例是X,可是視為1,最大縮放倍數應該是X的倍數。

引自http://www.cnblogs.com/linjzong/p/4212474.html

在上一篇Android:手把手教你打造可縮放移動的ImageView最後提出了一個注意點:當自訂的MatrixImageView如ViewPager、ListView等帶有滑動效果的ViewGroup中時,ImageView自訂的拖動事件會和ViewGroup的滑動事件衝突,並且指出了衝突原因是由於ViewGroup攔截了Move事件的緣故。如果對於Touch事件的分發機制不甚瞭解的話,可以參考下這篇Android:30分鐘弄明白Touch事件分發機制。

這篇文章將會在MatrixImageView的基礎上,以ViewPager作為測試容器做進一步最佳化。

實現功能

 

  1. 當進行縮放操作時,手勢不會同時觸發ViewPager的滑動切換Item事件。
  2. 當進行拖動操作時,除非圖片已經到達左右邊界,否則不觸發ViewPager的滑動切換Item事件。
  3. 當進行拖動操作時,若圖片左邊緣到達左邊界,則可以向左滑動觸發ViewPager切換至前一個Item;當圖片右邊緣到達右邊界,則可以向右滑動觸發ViewPager的切換至後一個Item。
  4. 每個down-up(cancel)事件周期內只執行一種類型的事務操作(縮放、拖動或者ViewPager切換Item),防止多重事務互相干擾。
  5. 將交易處理封裝到MatrixImageView類內,提供狀態介面給ViewPager使用,方便適配多種ViewGroup。
實現原理

 

當ViewPager內嵌MatrixImageView時,由於MatrixImgaeView在Down事件中返回了true,表明ViewPager將捕獲本次完整的Touch事件(Move-Ponit_Down-UP等等),其中最重要的一個事件便是Move事件,因為ViewPager自身需要捕獲Move事件在onTouch中進行切換Item操作,MatrixImageView的捕獲意味著它將無法響應。不過,ViewPager本身控制著Touch事件的下發操作,每個Touch事件的下發都遵從從上至下層層遞迴,在MatrixImageView真正獲得Move事件前,Move事件必須經過ViewPager的onInterceptTouchEvent和dispatchTouchEvent事件,前者執行攔截操作後者執行下發操作。ViewPager便是在onInterceptTouchEvent中對Move事件進行了過濾,當移動距離超過一定值時,它會攔截掉Move事件,阻止MatrixImageView繼續處理Touch事件的權利,轉而讓自身的onTouch事件處理。於是,我們要做的便是重寫onInterceptTouchEvent事件,通過判斷MatrixImageView的狀態決定是否攔截。

 

具體實現

 

由於容器ViewPager在滿足條件的時候會攔截掉子View的touch事件,因此需要自訂個ViewPager修改攔截邏輯。當MatriImageView進行縮放和拖動時,我們不希望ViewPager攔截。具體代碼如下:

public class AlbumViewPager extends ViewPager implements OnChildMovingListener {/**  當前子控制項是否處理拖動狀態  */     private boolean mChildIsBeingDragged=false;    @Override    public boolean onInterceptTouchEvent(MotionEvent arg0) {        if(mChildIsBeingDragged)            return false;        return super.onInterceptTouchEvent(arg0);    }@Override    public void startDrag() {        // TODO Auto-generated method stub        mChildIsBeingDragged=true;    }    @Override    public void stopDrag() {        // TODO Auto-generated method stub        mChildIsBeingDragged=false;    }}
public interface OnChildMovingListener{        public void  startDrag();        public void  stopDrag();    }

 通過判斷變數mChildIsBeingDragged的值決定是否攔截,而mChildIsBeingDragged的值通過OnChildMovingListener介面由MatriImageView進行設定。別忘了在PagerAdapter的instantiateItem中給MatriImageView設定監聽介面

    MatrixImageView imageView = (MatrixImageView) imageLayout.findViewById(R.id.image);    imageView.setOnMovingListener(AlbumViewPager.this);

ViewPager的改造便完成了,只需要新增一個變數和實現一個介面,之後對於事件的攔截操作都轉到了MatrixImageView中。

接下去看下改造後的MatrixImageView的onTouch方法。

    /** 和ViewPager互動相關,判斷當前是否可以左移、右移  */         boolean mLeftDragable;        boolean mRightDragable;          /**  是否第一次移動 */         boolean mFirstMove=false;        private PointF mStartPoint = new PointF();        @Override        public boolean onTouch(View v, MotionEvent event) {            // TODO Auto-generated method stub            switch (event.getActionMasked()) {            case MotionEvent.ACTION_DOWN:                //設定拖動模式                mMode=MODE_DRAG;                mStartPoint.set(event.getX(), event.getY());                isMatrixEnable();                startMove();                checkDragable();                break;            case MotionEvent.ACTION_UP:            case MotionEvent.ACTION_CANCEL:                reSetMatrix();                stopMove();                break;            case MotionEvent.ACTION_MOVE:                if (mMode == MODE_ZOOM) {                    setZoomMatrix(event);                }else if (mMode==MODE_DRAG) {                    setDragMatrix(event);                }else {                    stopMove();                }                break;            case MotionEvent.ACTION_POINTER_DOWN:                if(mMode==MODE_UNABLE) return true;                mMode=MODE_ZOOM;                mStartDis = distance(event);                break;            case MotionEvent.ACTION_POINTER_UP:                break;            default:                break;            }            return mGestureDetector.onTouchEvent(event);        }

其中加紅部分的代碼都是修改後的代碼,逐一分析。

/**          *   子控制項開始進入移動狀態,令ViewPager無法攔截對子控制項的Touch事件        */        private void startDrag(){            if(moveListener!=null) moveListener.startDrag();        }        /**          *   子控制項開始停止移動狀態,ViewPager將攔截對子控制項的Touch事件        */        private void stopDrag(){            if(moveListener!=null) moveListener.stopDrag();        }

startDrag和stopDrag方法很簡單,就是調用ViewPager傳遞進來的OnChildMovingListener介面進行mChildIsBeingDragged的設定。當監聽到down事件時,表示開始拖動,當接收到up和cancel事件時,表示結束拖動,以這個邏輯來說,ViewGroup將永遠無法攔截touch事件,所以我們還需要在其他地方設定stopDrag事件,後面說明。

接下去是在down事件中執行checkDragable方法:

        /**          *   根據當前圖片左右邊緣設定可拖拽狀態        */        private void checkDragable() {            mLeftDragable=true;            mRightDragable=true;            mFirstMove=true;            float[] values=new float[9];            getImageMatrix().getValues(values);            //圖片左邊緣離開左邊界,表示不可右移            if(values[Matrix.MTRANS_X]>=0)                mRightDragable=false;            //圖片右邊緣離開右邊界,表示不可左移            if((mImageWidth)*values[Matrix.MSCALE_X]+values[Matrix.MTRANS_X]<=getWidth()){                mLeftDragable=false;            }        }

該方法將會重設mLeftDragable、mRightDragable、mFirstMove三個參數的狀態。mLeftDragable表示目前狀態下的Matrix可以向左拖動,mRightDragable表示目前狀態下的Matrix可以向右拖動,mFirstMove為每次完整touch事件(從down到up或cancel)中的第一次拖動操作標誌。其中mLeftDragable和mRightDragable都是根據Matrix矩陣的數值計算出來的。

由於前面功能需求的時候說過"每個down-up(cancel)事件周期內只執行一種類型的事務操作(縮放、拖動或者ViewPager切換Item)",因此當進行縮放操作時,就不會再執行切換Item操作了,可以等縮放結束後執行up操作時stopDrag。而Move操作重點就是要識別是切換item還是拖動圖片了。查看修改後的setDragMatrix代碼

/**          *  設定拖拽狀態下的Matrix        *  @param event           */        public void setDragMatrix(MotionEvent event) {            if(isZoomChanged()){                float dx = event.getX() - mStartPoint.x; // 得到x軸的移動距離                float dy = event.getY() - mStartPoint.y; // 得到x軸的移動距離                //避免和雙擊衝突,大於10f才算是拖動                if(Math.sqrt(dx*dx+dy*dy)>10f){                        mStartPoint.set(event.getX(), event.getY());                    //在當前基礎上移動                    mCurrentMatrix.set(getImageMatrix());                    float[] values=new float[9];                    mCurrentMatrix.getValues(values);                    dy=checkDyBound(values,dy);                        dx=checkDxBound(values,dx,dy);                    mCurrentMatrix.postTranslate(dx, dy);                    setImageMatrix(mCurrentMatrix);                }            }else {                stopDrag();            }        }/**           *  和當前矩陣對比,檢驗dx,使映像移動後不會超出ImageView邊界         *  @param values         *  @param dx         *  @return            */        private float checkDxBound(float[] values,float dx,float dy) {            float width=getWidth();            if(!mLeftDragable&&dx<0){                //加入和y軸的對比,表示在監聽到垂直方向的手勢時不切換Item                if(Math.abs(dx)*0.4f>Math.abs(dy)&&mFirstMove){                    stopDrag();                }                return 0;            }            if(!mRightDragable&&dx>0){                //加入和y軸的對比,表示在監聽到垂直方向的手勢時不切換Item                if(Math.abs(dx)*0.4f>Math.abs(dy)&&mFirstMove){                    stopDrag();                }                return 0;            }            mLeftDragable=true;            mRightDragable=true;            if(mFirstMove) mFirstMove=false;            if(mImageWidth*values[Matrix.MSCALE_X]<width){                return 0;                            }            if(values[Matrix.MTRANS_X]+dx>0){                dx=-values[Matrix.MTRANS_X];            }            else if(values[Matrix.MTRANS_X]+dx<-(mImageWidth*values[Matrix.MSCALE_X]-width)){                dx=-(mImageWidth*values[Matrix.MSCALE_X]-width)-values[Matrix.MTRANS_X];            }            return dx;        }

處理邏輯是這樣的:

1.判斷當前縮放層級是否是原始縮放層級(isZoomChanged()),如果未縮放過那將可以直接切換Item,在這直接stopDrag。

2.若進行了縮放,那判斷是否累移動了10f,當移動了10f之後計算出x軸和y軸的移動量,並且通過checkDyBound方法計算出y軸的真實移動量

3.進入checkDxBound方法,首先判斷當前是否能夠左移,如果不能左移而實際的x軸位移量是向左的,那就返回x的位移量為0,防止左移。同時,如果當前是第一次移動,那就表示本次不是左移操作,而是向前切換item,於是執行stopDrag方法,令ViewPager攔截掉對MatrixImageView的事件分發。另外在這裡加入和Y軸位移量的對比,是為了防止執行的是垂直方向的滑動而導致stopDrag,ViewPager自身對於X軸位移量/2小於Y軸位移量的情況是不當成切換Item意圖的,這裡設定為*0.4可以保證不衝突。

4.右移同理。

5.當第一次左移和右移判斷結果都不是切換Item後,將mLeftDragable和mRightDragable都設定為true,表示可以正常移動了。之後就和單個MatrixImageView的拖動處理一樣了。

到此便完成了內嵌到ViewGroup內的MatriImageView的改造。下面還有兩點顯示最佳化。

首先在reSetMatrix中加入了新的功能:當縮放後的圖片高度未達到ImageView高度時,在up和cancel之後會將其Y軸置中,防止“放大圖片-Y軸移動圖片-縮小圖片”導致圖片位置不對稱。異常圖效果如下:

/**           *   重設Matrix         */        private void reSetMatrix() {            if(checkRest()){                mCurrentMatrix.set(mMatrix);                setImageMatrix(mCurrentMatrix);            }else {                //判斷Y軸是否需要更正                float[] values=new float[9];                getImageMatrix().getValues(values);                float height=mImageHeight*values[Matrix.MSCALE_Y];                if(height<getHeight()){                    //在圖片真實高度小於容器高度時,Y軸置中,Y軸理想位移量為兩者高度差/2,                    float topMargin=(getHeight()-height)/2;                    if(topMargin!=values[Matrix.MTRANS_Y]){                        mCurrentMatrix.set(getImageMatrix());                        mCurrentMatrix.postTranslate(0, topMargin-values[Matrix.MTRANS_Y]);                        setImageMatrix(mCurrentMatrix);                    }                }            }        }

最佳化了縮放操作的縮放x軸對稱軸選擇問題。在"圖片放大-移動X軸-縮小圖片"時,若直接以ImageView中心點為縮放原點,可能會導致縮放後的圖片邊緣離開ImageView邊界。

出錯圖效果如下:

/**           *  設定縮放Matrix         *  @param event            */        private void setZoomMatrix(MotionEvent event) {            //只有同時觸屏兩個點的時候才執行            if(event.getPointerCount()<2) return;            float endDis = distance(event);// 結束距離            if (endDis > 10f) { // 兩個手指併攏在一起的時候像素大於10                float scale = endDis / mStartDis;// 得到縮放倍數                mStartDis=endDis;//重設距離                mCurrentMatrix.set(getImageMatrix());//初始化Matrix                float[] values=new float[9];                mCurrentMatrix.getValues(values);                scale = checkMaxScale(scale, values);                PointF centerF=getCenter(scale,values);                mCurrentMatrix.postScale(scale, scale,centerF.x,centerF.y);                setImageMatrix(mCurrentMatrix);                }        }/**           *  擷取縮放的中心點。         *  @param scale         *  @param values         *  @return            */        private PointF getCenter(float scale,float[] values) {            //縮放層級小於原始縮放層級時或者為放大狀態時,返回ImageView中心點作為縮放中心點            if(scale*values[Matrix.MSCALE_X]<mScale||scale>=1){                return new PointF(getWidth()/2,getHeight()/2);            }            float cx=getWidth()/2;            float cy=getHeight()/2;            //以ImageView中心點為縮放中心,判斷縮放後的圖片左邊緣是否會離開ImageView左邊緣,是的話以左邊緣為X軸中心            if((getWidth()/2-values[Matrix.MTRANS_X])*scale<getWidth()/2)                cx=0;            //判斷縮放後的右邊緣是否會離開ImageView右邊緣,是的話以右邊緣為X軸中心            if((mImageWidth*values[Matrix.MSCALE_X]+values[Matrix.MTRANS_X])*scale<getWidth())                cx=getWidth();            return new PointF(cx,cy);        }

通過判斷圖片寬度,決定是以ImageView中點為X軸縮放原點,還是以左右邊緣為縮放原點。

目前為止MatrixImageView的功能基本完善了,具體代碼還是放在我的Github上的照相機Demo。該View如果有問題的可以在這篇文章下留言或私信我。

Android viewpager + 可縮放的imageview

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.