Android:改造ViewPager优化MatrixImageView显示效果

在上一篇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如果有问题的可以在这篇文章下留言或私信我。

时间: 2024-10-04 17:51:32

Android:改造ViewPager优化MatrixImageView显示效果的相关文章

响应式设计、改造与优化——互动出版网

这篇是计算机类的优质预售推荐>>>><响应式设计.改造与优化> 响应式移动设计从零到一+现有网站实现响应式改造+用户体验优化 编辑推荐 本书主要涵盖了以下内容: ◎ 论述为什么"移动优先"仍然是最佳实践 ◎ 融合内容.结构和美感向用户提供他们喜爱的体验 ◎ 利用响应式图片提升渲染速度并更有效地传达视觉信息 ◎ 利用栅格系统避免让用户觉得你的设计"被禁锢在盒子里" ◎ 掌握测量单位,例如px.em.rem和视图相关单位等,并理解它们

Android APP 性能优化的一些思考

说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了很多文件,然后用手机管家 APP 不断地进行清理优化 ,才感觉运行速度稍微提高了点,就算手机在各种性能跑分软件面前分数遥遥领先,还是感觉无论有多大的内存空间都远远不够用.相信每个使用 Android 系统的用户都有过以上类似经历,确实,Android 系统在流畅性方面不如 IOS 系统,为何呢,明明在看手机硬件配置上时,Android 设备都不会输于 IO

Android app 性能优化的思考--性能卡顿不好的原因在哪?

说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了很多文件,然后用手机管家 APP 不断地进行清理优化 ,才感觉运行速度稍微提高了点,就算手机在各种性能跑分软件面前分数遥遥领先,还是感觉无论有多大的内存空间都远远不够用.相信每个使用 Android 系统的用户都有过以上类似经历,确实,Android 系统在流畅性方面不如 IOS 系统,为何呢,明明在看手机硬件配置上时,Android 设备都不会输于 IO

Android:ViewPager扩展详解——带有导航的ViewPagerIndicator(附带图片缓存,异步加载图片)

大家都用过viewpager了, github上有对viewpager进行扩展,导航风格更加丰富,这个开源项目是ViewPagerIndicator,很好用,但是例子比较简单,实际用起来要进行很多扩展,比如在fragment里进行图片缓存和图片异步加载. 下面是ViewPagerIndicator源码运行后的效果,大家也都看过了,我多此一举截几张图: 下载源码请点击这里 ===========================================华丽的分割线==============

Android的内存优化

腾讯公司在五月三十一日开展[腾讯Bugly移动开发者沙龙]大会,大会上面叶方正老师讲解了 关于Android的内存优化的问题,不过我感觉叶老师更多的站在了测试的角度上去解释了这一方面,叶老师给我们介绍了很多的工具去测试Android应用在各种情况下的内存占用情况,不过好像对我们开发的帮助并不是特别的大.我在这里总结叶老师所说的重点和自己对内存优化的一些理解,希望能够对大家有所帮助. Android应用优化主要集中在内存和UI流畅度上,从内存占用与泄露.UI流畅度的帧数和响应时间到IO的阻塞式响应

【读书笔记】《Android应用性能优化最佳实践》

<第一行代码>读书笔记 一.引言 二.读书内容 书名:<Android应用性能优化最佳实践> 作者:罗彧成 (腾讯音乐Android开发总监) 出版社:机械工业出版社 封面: 三.书籍评价 四.个人心得 五.参考文档

Android app性能优化大汇总之内存性能优化

写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在Android开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读.(本文最后我会尽量列出所参考的文章). 内存简介: RAM(random access memory)随机存取存储器.说白了就是内存. 一般Java在内存分配时会涉及到以下区域: 寄存器(R

Android的ViewPager的初步使用

之前想实现页面的滑动效果,在网上搜了下,挺多的关于TabHost的使用的例子,然后就使用了TabHost,但是效果非常一般(Maybe我不会用).然后同学介绍使用ViewPager,就上网问百老师,挺多例子挺详细的,亲自试了下,做出了我想要的效果,现在就总结下: 1.建立一个主的Activity,继承FragmentActivity,再创建一个Adapter类,继承FragmentPagerAdapter(继承其他Adapter没有测试过),使用的方式和ListView的Adapter差不多.然

android shape图形优化Button效果

android shape可以让我们通过定义xml文件的方式创建图形,当然只能实现一些比较简单的图形(圆形,矩形,椭圆,线段),但是已经相当不错了,通过shape创建的图形作为控件的背景已经基本可以满足我的简单需求了,而且通过shape创建的图形可以适配各种屏幕. 下面就用shape定义的图形来优化Button的整体效果. 定义主布局文件activity_main.xml: 1 <RelativeLayout xmlns:android="http://schemas.android.co