功能

初始时大小控制,图片宽或高大于view的,缩小至view大小,否则按原始大小显示
双击放大,第一次双击后将图片宽或高放大到view的宽或高的比例
再次双击会再在此前基础上放大固定的倍数
放大两次后后再次双击可缩小到初始大小
双指可对图片大小进行缩放,可设置控制最大缩放倍数
根据双击点的不同控制放大时的中心点
放大后,若图片大小超出view大小,则可将图片滑动到指定区域
可在view区域随意拖动,且可控制边缘,防止图片边缘滑到view边缘之内的区域
解决和ViewPage滑到事件的冲突

使用案例

public class MainActivity extends Activity {
    private int[] mImgs = new int[] { R.drawable.small, R.drawable.vertical, R.drawable.middle, R.drawable.big };
    private ImageView[] mImageViews = new ImageView[mImgs.length];
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewPager mViewPager = new ViewPager(this);
        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;
            }
        });
        setContentView(mViewPager);
    }

}


View代码

public class ZoomImageView extends ImageView implements OnTouchListener, OnGlobalLayoutListener {
    private static final String TAG = "bqt";
    /**一些边界缩放倍数,SCALE_FULL的效果为:第一次双击后将图片宽或高放大到view的宽或高的比例*/
    private float SCALE_FULL, SCALE_DOUBLE, SCALE_MAX;
    /** 初始化时【屏幕/图片】的大小,也是最后一次双击时使用的缩放比例。如果图片宽高大于屏幕宽高,此值将小于1 */
    private float SCALE_INIT;
    /**是否限制双指缩放时的缩放倍数*/
    private boolean isLimitedScale = true;
    private Matrix mScaleMatrix = new Matrix();
    private final float[] matrixValues = new float[9];//用于存放矩阵的9个值
    /** 缩放的手势检测 */
    private ScaleGestureDetector mScaleGestureDetector;
    private GestureDetector mGestureDetector;
    private boolean isAutoScale;
    private int mTouchSlop;
    private float mLastX, mLastY;
    private boolean isCanDrag;
    private int lastPointerCount;
    private boolean isCheckTopAndBottom = true, isCheckLeftAndRight = true;
    public ZoomImageView(Context context) {
        this(context, null);
    }
    public ZoomImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setScaleType(ScaleType.MATRIX);//用矩阵来绘制
        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {//双击时
                if (isAutoScale == true) return true;
                float x = e.getX();
                float y = e.getY();
                Log.i(TAG, "双击onDoubleTap," + "缩放比例:" + getScale() + " , " + SCALE_INIT);
                if (getScale() < SCALE_FULL) post(new AutoScaleRunnable(SCALE_FULL, x, y));
                else if (getScale() >= SCALE_FULL && getScale() < SCALE_DOUBLE) post(new AutoScaleRunnable(SCALE_DOUBLE, x, y));
                else post(new AutoScaleRunnable(SCALE_INIT, x, y));
                isAutoScale = true;
                return true;
            }
        });
        mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {//双指缩放时
                if (getDrawable() == null) return true;
                float scale = getScale();
                float scaleFactor = detector.getScaleFactor();
                //缩放的范围控制
                if (!isLimitedScale) mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
                else if ((scale < SCALE_MAX && scaleFactor > 1.0f) || (scale > SCALE_INIT && scaleFactor < 1.0f)) {
                    //最大值最小值判断
                    if (scaleFactor * scale < SCALE_INIT) scaleFactor = SCALE_INIT / scale;
                    if (scaleFactor * scale > SCALE_MAX) scaleFactor = SCALE_MAX / scale;
                    //设置缩放比例
                    mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
                    checkBorderAndCenterWhenScale();
                    setImageMatrix(mScaleMatrix);
                }
                return true;
            }
        });
        this.setOnTouchListener(this);
    }
    @Override
    protected void onAttachedToWindow() {//在onDraw前调用的。也就是我们写的View在没有绘制出来时调用的
        super.onAttachedToWindow();
        Log.i(TAG, "onAttachedToWindow");
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }
    @Override
    public void onGlobalLayout() {//根据图片的宽和高以及屏幕的宽和高,对图片进行缩放以及移动至屏幕的中心
        if (getDrawable() == null) return;
        // 图片的宽高
        int dw = getDrawable().getIntrinsicWidth();
        int dh = getDrawable().getIntrinsicHeight();
        Log.i(TAG, "view大小:" + getWidth() + " * " + getHeight() + " ,图片大小: " + dw + " * " + dh);
        //初始化缩放比例
        if (dw >= getWidth() && dh >= getHeight()) {// 如果图片的宽【和】高都大于view,则让其按按比例适应屏幕大小
            SCALE_INIT = Math.min(getWidth() * 1.0f / dw, getHeight() * 1.0f / dh);
            SCALE_FULL = Math.max(getWidth() * 1.0f / dw, getHeight() * 1.0f / dh);
        } else if (dw >= getWidth()) { // 如果图片的宽【或】高大于view,则缩放至屏幕的宽或者高
            SCALE_INIT = getWidth() * 1.0f / dw;
            SCALE_FULL = getHeight() * 1.0f / dh;
        } else if (dh >= getHeight()) {
            SCALE_INIT = getHeight() * 1.0f / dh;
            SCALE_FULL = getWidth() * 1.0f / dw;
        } else {//其他情况,也即小图片时,默认不进行缩放
            SCALE_INIT = 1.0f;
            SCALE_FULL = Math.min(getWidth() * 1.0f / dw, getHeight() * 1.0f / dh);
        }
        SCALE_DOUBLE = 1.5f * SCALE_FULL;//第二次双击时的缩放比例
        SCALE_MAX = 3.5f * SCALE_FULL;//最大缩放比例
        Log.i(TAG, "缩放比例SCALE_INIT = " + SCALE_INIT + "--双击缩放比例:" + SCALE_FULL);
        // 将图片移动至屏幕中心,以SCALE_INIT为比例进行缩放
        mScaleMatrix.postTranslate((getWidth() - dw) / 2, (getHeight() - dh) / 2);
        mScaleMatrix.postScale(SCALE_INIT, SCALE_INIT, getWidth() / 2, getHeight() / 2);
        setImageMatrix(mScaleMatrix);
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (mGestureDetector.onTouchEvent(event)) return true;//双击时,不再响应其他操作
        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;
        RectF rectF = getMatrixRectF();
        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);
            Log.i(TAG, "ACTION_MOVE");
            float dx = x - mLastX;
            float dy = y - mLastY;
            if (!isCanDrag) isCanDrag = isCanDrag(dx, dy);
            if (isCanDrag) {
                if (getDrawable() != null) {
                    isCheckLeftAndRight = isCheckTopAndBottom = true;
                    // 如果宽度小于屏幕宽度,则禁止左右移动
                    if (rectF.width() < getWidth()) {
                        dx = 0;
                        isCheckLeftAndRight = false;
                    }
                    // 如果高度小雨屏幕高度,则禁止上下移动
                    if (rectF.height() < getHeight()) {
                        dy = 0;
                        isCheckTopAndBottom = false;
                    }
                    /**移动时的缩放比例*/
                    float translateScale = getScale();
                    if (translateScale > 2.0f) translateScale = 2.0f;
                    if (translateScale < 0.5f) translateScale = 0.5f;
                    mScaleMatrix.postTranslate(dx * translateScale, dy * translateScale);//非常有用的设置,当放大很多倍时可以快速的滑动
                    checkMatrixBounds();
                    setImageMatrix(mScaleMatrix);
                }
            }
            mLastX = x;
            mLastY = y;
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            Log.i(TAG, "ACTION_UP");
            lastPointerCount = 0;
            break;
        }
        return true;
    }
    //**************************************************************************************************************************
    /** 在缩放时,进行图片显示范围的控制,防止图片宽高大于view时,图片与控件间出现白边;防止图片小于view时不居中 */
    private void checkBorderAndCenterWhenScale() {
        RectF rect = getMatrixRectF();
        float deltaX = 0;
        float deltaY = 0;
        int width = getWidth();
        int height = getHeight();
        // 如果宽或高大于屏幕,则控制范围
        if (rect.width() >= width) {
            if (rect.left > 0) deltaX = -rect.left;
            if (rect.right < width) deltaX = width - rect.right;
        }
        if (rect.height() >= height) {
            if (rect.top > 0) deltaY = -rect.top;
            if (rect.bottom < height) deltaY = height - rect.bottom;
        }
        // 如果宽或高小于屏幕,则让其居中
        if (rect.width() < width) deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
        if (rect.height() < height) deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
        Log.i(TAG, "deltaX = " + deltaX + " , deltaY = " + deltaY);
        mScaleMatrix.postTranslate(deltaX, deltaY);
    }
    /** 根据当前图片的Matrix获得图片的范围 */
    private RectF getMatrixRectF() {
        Matrix matrix = mScaleMatrix;
        RectF rect = new RectF();
        Drawable d = getDrawable();
        if (null != d) {
            rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            matrix.mapRect(rect);
        }
        return rect;
    }
    /** 获得当前的缩放比例 */
    public final float getScale() {
        mScaleMatrix.getValues(matrixValues);
        return matrixValues[Matrix.MSCALE_X];
    }
    /** 移动时,进行边界判断,主要判断宽或高大于屏幕的 */
    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);
    }
    /** 是否是拖动行为 */
    private boolean isCanDrag(float dx, float dy) {
        return Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
    }
    //**************************************************************************************************************************
    /** 自动缩放的任务*/
    private class AutoScaleRunnable implements Runnable {
        private float mTargetScale;
        private int TIME = 100;//分几次缩放到指定的的比例
        private float tmpScale;//
        private float x, y;//缩放的中心
        /**传入目标缩放值,根据目标值与当前值,判断应该放大还是缩小 */
        public AutoScaleRunnable(float targetScale, float x, float y) {
            this.mTargetScale = targetScale;
            this.x = x;
            this.y = y;
            if (getScale() < mTargetScale) tmpScale = 1.0f + (mTargetScale - getScale() / TIME);
            else tmpScale = 1.0f - (getScale() - mTargetScale / TIME);
        }
        @Override
        public void run() {
            // 进行缩放
            mScaleMatrix.postScale(tmpScale, tmpScale, x, y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(mScaleMatrix);
            // 如果值在合法范围内,继续缩放
            if ((tmpScale > 1f && getScale() < mTargetScale) || (tmpScale < 1f && getScale() > mTargetScale)) {
                post(this);
            } else { // 设置为目标的缩放比例
                float deltaScale = mTargetScale / getScale();
                mScaleMatrix.postScale(deltaScale, deltaScale, x, y);
                checkBorderAndCenterWhenScale();
                setImageMatrix(mScaleMatrix);
                isAutoScale = false;
            }
        }
    }

}

附件列表

自定义ImageView 手势 缩放 滑动 矩阵的更多相关文章

  1. Android 自定义ImageView支持缩放,拖拽,方便复用

    今天刚发了一篇关于ImageView的缩放和拖拽的博客,然后我想了下,将他自定义下,方便我们来复用这个imageView,效果我就不多说了,http://blog.csdn.net/xiaanming ...

  2. 自定义ImageView实现图片手势滑动,多点触摸放大缩小效果

    首先呢,还是一贯作风,我们先来看看众多应用中的示例:(这种效果是很常见的,可以说应用的必须品.)                搜狐客户端                               ...

  3. (一)自定义ImageView,初步实现多点触控、自由缩放

    真心佩服那些一直专注于技术共享的大神们,正是因为他们无私的分享精神,我才能每天都有进步.近日又算是仔细学了android的自定义控件技术,跟着大神的脚步实现了一个自定义的ImageView.里面涉及到 ...

  4. js实现移动端图片预览:手势缩放, 手势拖动,双击放大...

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  5. 动画--问题追踪:ImageView执行缩放动画ScaleAnimation之后,图像显示不全的问题。

    http://www.bkjia.com/Androidjc/929473.html: 问题追踪:ImageView执行缩放动画ScaleAnimation之后,图像显示不全的问题., 问题:我有一个 ...

  6. Android自定义ImageView实现图片圆形 ,椭圆和矩形圆角显示

    Android中的ImageView只能显示矩形的图片,为了用户体验更多,Android实现圆角矩形,圆形或者椭圆等图形,一般通过自定义ImageView来实现,首先获取到图片的Bitmap,然后通过 ...

  7. android131 360 05 手势触摸滑动,sim卡,开机启动的广播,手机联系人,SharedPreferences,拦截短信

    安卓手势触摸滑动: package com.itheima52.mobilesafe.activity; import android.app.Activity; import android.con ...

  8. 缩放系列(三):一个可以手势缩放、拖拽、旋转的layout

    弄了一个下午,终于搞出来了,PowerfulLayout 下面是一个功能强大的改造的例子: 可以实现以下需求: 1.两个手指进行缩放布局 2.所有子控件也随着缩放, 3.子控件该有的功能不能丢失(像b ...

  9. mui中图片手势缩放功能的实现

    MUI框架,要实现手势缩放图片,可以使用imageviewer组件来实现.代码很简单: 引入css: <link href="assets/css/mui.imageviewer.cs ...

随机推荐

  1. 遍历datatable的几种方法

    方法一: DataTable dt = dataSet.Tables[]; ; i < dt.Rows.Count ; i++) { string strName = dt.Rows[i][&q ...

  2. CodeForces 785B Anton and Classes

    简单判断. 找第一类区间中$R$最大的,以及第二类区间中$L$最小的,判断距离. 找第二类区间中$R$最大的,以及第一类区间中$L$最小的,判断距离. 两种情况取个最大值即可. #include &l ...

  3. POJ 1597 Function Run Fun

    记忆化搜索. #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> ...

  4. C# 消除累计误差的倒计时

    使用 C# 中自带的各种 timer 计时,都会有累计误差,以下代码实现了一种消除累计误差的方法,使得每次计时的误差,空值在 100 ms 以内(可以通过修改代码提升精度.) 对于精度要求在秒级别的简 ...

  5. 扩展swap分区

    swap分区在系统的物理内存不够用时,把硬盘的一部分空间释放出来,以供当前运行的程序使用.(临时使用,如果swap分区都不够了还是直接加内存吧) (1).步骤 mkswap /devices(可以是分 ...

  6. 在centos6.0上通过nginx远程执行shell

    nginx本身不支持直接调用shell脚本,我们可以通过安装fastcgi程序,让nginx把调用shell的http请求交给fastcgi程序去处理,然后nginx 再将结果返回给用户方式间接调用s ...

  7. Codeforces Beta Round #5 B. Center Alignment 模拟题

    B. Center Alignment 题目连接: http://www.codeforces.com/contest/5/problem/B Description Almost every tex ...

  8. Unity UGUI之Image

    Image组件在Inspector Source Image--需要一个Sprite(精灵). Color--图片的颜色 Material--可以添加材质球 RayCast Target--选中可以传 ...

  9. hihocoder1320 160周 压缩字符串

    hihocoder1320 题目链接 思路: dp解法.用map[i][j]表示从第i个开始到第j个的字串的best压缩长度.(包括i,j,两端闭合). 用k表示i,j中的一点. 用zip()表示压缩 ...

  10. tomcat使用Eclipse进行远程调试(线上调试)

    什么是远程调试,就是在A机器上利用Eclipse单步跟踪调试B机器上的Web应用,当然调试A机器上Web应用也是没有问题的,90%我都是调试本机的Web应用,远程调试的意义我想我不用说了,大家都会想到 ...