Android:手把手教你打造可缩放移动的ImageView(下) -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

    实现功能

    当进行缩放操作时,手势不会同时触发ViewPager的滑动切换Item事件,

Android:手把手教你打造可缩放移动的ImageView(下)

    当进行拖动操作时,除非图片已经到达左右边界,否则不触发ViewPager的滑动切换Item事件。

    当进行拖动操作时,若图片左边缘到达左边界,则可以向左滑动触发ViewPager切换至前一个Item;当图片右边缘到达右边界,则可以向右滑动触发ViewPager的切换至后一个Item。

    每个down-up(cancel)事件周期内只执行一种类型的事务操作(缩放、拖动或者ViewPager切换Item),防止多重事务互相干扰。

    将事务处理封装到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的设置,

电脑资料

Android:手把手教你打造可缩放移动的ImageView(下)》(https://www.unjs.com)。当监听到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]

    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的改造。下面还有两点显示优化。

最新文章