Android 实现可以自由移动缩放的图片控件并实现ViewPager滑动

本文转载自Android 实现可以自由移动缩放的图片控件

那位大佬本身已经把此控件说的很明白了,有什么不懂的可以去原帖看看,我这里就不说明所有代码的意思了,就简单的贴一下全部的代码吧

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

/**
 * @author dj
 * @version 1.0
 * @description 可缩放的图片控件
 * @created on 2017/4/19.
 */

public class ScaleView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,
        ScaleGestureDetector.OnScaleGestureListener
            ,View.OnTouchListener{
    //** 表示是否只有一次加载 */
    private boolean isOnce = false;
    /** 初始时的缩放值 */
    private float mInitScale;
    /** 双击时 的缩放值 */
    private float mClickScale;
    /** 最大的缩放值 */
    private float mMaxScale;
    /** 图片缩放矩阵 */
    private Matrix mMatrix;
    /** 图片缩放手势 */
    private ScaleGestureDetector mScaleGesture;

    // ----------------------------自由移动--------------------------------
    /** 可移动最短距离限制,大于这个值时就可移动 */
    private int mTouchSlop;
    /** 是否可以拖动 */
    private boolean isCanDrag;

    // ----------------------------双击放大--------------------------------
    private GestureDetector mGesture;
    // 是否自动缩放
    private boolean isAutoScale;

    public ScaleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScaleView(Context context) {
        this(context, null);
    }

    public ScaleView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // 必须设置才能触发
        this.setOnTouchListener(this);

        mMatrix = new Matrix();
        // 设置缩放模式
        super.setScaleType(ScaleType.MATRIX);

        mScaleGesture = new ScaleGestureDetector(context, this);
        mGesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {

                // 如果正在缩放时,不能放大
                if (isAutoScale) {
                    return true;
                }

                float px = e.getX();
                float py = e.getY();
                // 只有小于最大缩放比例才能放大
                float scale = getScale();
                if (scale < mClickScale) {
                    // mMatrix.postScale(mClickScale/scale, mClickScale/scale,
                    // px, py);
                    postDelayed(new ScaleRunnale(px, py, mClickScale), 16);
                    isAutoScale = true;
                } else {
                    // mMatrix.postScale(mInitScale/scale, mInitScale/scale, px,
                    // py);
                    postDelayed(new ScaleRunnale(px, py, mInitScale), 16);
                    isAutoScale = true;
                }
                // setImageMatrix(mMatrix);
                return true;
            }
        });

        /**
         * 是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如viewpager
         * 就是用这个距离来判断用户是否翻页。
         */
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    private class ScaleRunnale implements Runnable {
        // 放大值
        private static final float BIGGER = 1.08f;
        // 缩小值
        private static final float SMALLER = 0.96f;
        private float x;
        private float y;
        private float mTargetScale;
        private float mTempScale;

        public ScaleRunnale(float x, float y, float mTargetScale) {
            super();
            this.x = x;
            this.y = y;
            this.mTargetScale = mTargetScale;

            if (getScale() < mTargetScale) {
                mTempScale = BIGGER;
            } else if (getScale() > mTargetScale) {
                mTempScale = SMALLER;
            }
        }

        @Override
        public void run() {
            // 先进行缩放
            mMatrix.postScale(mTempScale, mTempScale, x, y);
            checkSideAndCenterWhenScale();
            setImageMatrix(mMatrix);

            float currentScale = getScale();

            // 如果想放大,并且当前的缩放值小于目标值
            if ((mTempScale > 1.0f && currentScale < mTargetScale)
                    || (mTempScale < 1.0f && currentScale > mTargetScale)) {
                // 递归执行run方法
                postDelayed(this, 16);
            } else {
                float scale = mTargetScale / currentScale;
                mMatrix.postScale(scale, scale, x, y);
                checkSideAndCenterWhenScale();
                setImageMatrix(mMatrix);

                isAutoScale = false;
            }
        }

    }

    @Override
    public void onGlobalLayout() {
        // 如果还没有加载图片
        if (!isOnce) {

            // 获得控件的宽高
            int width = getWidth();
            int height = getHeight();

            Drawable drawable = getDrawable();
            if (drawable == null) {
                return;
            }
            // 获得图片的宽高
            int bitmapWidth = drawable.getIntrinsicWidth();
            int bitmapHeight = drawable.getIntrinsicHeight();

            // 设定比例值
            float scale = 0.0f;

            // 如果图片的宽度>控件的宽度,缩小
            if (bitmapWidth > width && bitmapHeight < height) {
                scale = width * 1.0f / bitmapWidth;
            }
            // 如果图片的高度>控件的高度,缩小
            if (bitmapHeight > height && bitmapWidth < width) {
                scale = height * 1.0f / bitmapHeight;
            }
            // 如果图片的宽高度>控件的宽高度,缩小 或者 如果图片的宽高度<控件的宽高度,放大
            if ((bitmapWidth > width && bitmapHeight > height) || (bitmapWidth < width && bitmapHeight < height)) {
                float f1 = width * 1.0f / bitmapWidth;
                float f2 = height * 1.0f / bitmapHeight;
                scale = Math.min(f1, f2);
            }

            // 初始化缩放值
            mInitScale = scale;
            mClickScale = mInitScale * 2;
            mMaxScale = mInitScale * 4;

            // 得到移动的距离
            int dx = width / 2 - bitmapWidth / 2;
            int dy = height / 2 - bitmapHeight / 2;

            // 平移
            mMatrix.postTranslate(dx, dy);

            // 在控件的中心缩放
            mMatrix.postScale(scale, scale, width / 2, height / 2);

            // 设置矩阵
            setImageMatrix(mMatrix);

            // 关于matrix,就是个3*3的矩阵
            /**
             * xscale xskew xtrans yskew yscale ytrans 0 0 0
             */

            isOnce = true;
        }
    }

    /**
     * 注册全局事件
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    /**
     * 移除全局事件
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    /**
     * 获得缩放值
     *
     * @return
     */
    public float getScale() {
        /**
         * xscale xskew xtrans yskew yscale ytrans 0 0 0
         */
        float[] values = new float[9];
        mMatrix.getValues(values);
        return values[Matrix.MSCALE_X];
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        // 如果没有图片,返回
        if (getDrawable() == null) {
            return true;
        }
        // 缩放因子,>0表示正在放大,<0表示正在缩小
        float intentScale = detector.getScaleFactor();
        float scale = getScale();

        // 进行缩放范围的控制
        // 判断,如果<最大缩放值,表示可以放大,如果》最小缩放,说明可以缩小
        if ((scale < mMaxScale && intentScale > 1.0f) || (scale > mInitScale && intentScale < 1.0f)) {

            // scale 变小时, intentScale变小
            if (scale * intentScale < mInitScale) {
                // intentScale * scale = mInitScale ;
                intentScale = mInitScale / scale;
            }

            // scale 变大时, intentScale变大
            if (scale * intentScale > mMaxScale) {
                // intentScale * scale = mMaxScale ;
                intentScale = mMaxScale / scale;
            }

            // 以控件为中心缩放
            // mMatrix.postScale(intentScale, intentScale, getWidth()/2,
            // getHeight()/2);
            // 以手势为中心缩放
            mMatrix.postScale(intentScale, intentScale, detector.getFocusX(), detector.getFocusY());

            // 检测边界与中心点
            checkSideAndCenterWhenScale();

            setImageMatrix(mMatrix);
        }

        return true;
    }

    /**
     * 获得图片缩放后的矩阵
     *
     * @return
     */
    public RectF getMatrixRectF() {
        Matrix matrix = mMatrix;
        RectF rectF = new RectF();
        Drawable drawable = getDrawable();
        if (drawable != null) {
            // 初始化矩阵
            rectF.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
            // 移动s
            matrix.mapRect(rectF);
        }
        return rectF;
    }

    private void checkSideAndCenterWhenScale() {
        RectF rectF = getMatrixRectF();
        float deltaX = 0f;
        float deltaY = 0f;
        int width = getWidth();
        int height = getHeight();

        // 情况1, 如果图片的宽度大于控件的宽度
        if (rectF.width() >= width) {
            if (rectF.left > 0) {
                deltaX = -rectF.left;// 如果图片没有左边对齐,就往左边移动
            }
            if (rectF.right < width) {
                deltaX = width - rectF.right;// 如果图片没有右边对齐,就往右边移动
            }
        }
        // 情况2, 如果图片的宽度大于控件的宽度
        if (rectF.height() >= height) {
            if (rectF.top > 0) {
                deltaY = -rectF.top;//
            }
            if (rectF.bottom < height) {
                deltaY = height - rectF.bottom;// 往底部移动
            }
        }

        // 情况3,如图图片在控件内,则让其居中
        if (rectF.width() < width) {
            // deltaX = width/2-rectF.left - rectF.width()/2;
            // 或
            deltaX = width / 2f - rectF.right + rectF.width() / 2f;
        }

        if (rectF.height() < height) {
            deltaY = height / 2f - rectF.bottom + rectF.height() / 2f;
        }

        mMatrix.postTranslate(deltaX, deltaY);
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        // TODO Auto-generated method stub

    }

    private float mLastX;
    private float mLastY;
    /** 上次手指的数量 */
    private int mLastPointerCount;

    /** 判断是否检测了x,y轴 */
    private boolean isCheckX;
    private boolean isCheckY;

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        // 把事件传递给双击手势
        if (mGesture.onTouchEvent(event)) {
            return true;
        }
        // 把事件传递给缩放手势
        mScaleGesture.onTouchEvent(event);

        float x = event.getX();
        float y = event.getY();

        int pointerCount = event.getPointerCount();
        for (int i = 0; i < pointerCount; i++) {
            x += event.getX(i);
            y += event.getY(i);
        }
        x /= pointerCount;
        y /= pointerCount;

        // 说明手指改变
        if (mLastPointerCount != pointerCount) {
            isCanDrag = false;
            mLastX = x;
            mLastY = y;
        }
        mLastPointerCount = pointerCount;

        RectF rectF = getMatrixRectF();

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

            case MotionEvent.ACTION_MOVE:
                if (rectF.width() > getWidth()) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }

                float dx = x - mLastX;
                float dy = y - mLastY;

                if (!isCanDrag) {
                    isCanDrag = isMoveAction(dx, dy);
                }
                /**
                 * 如果能移动
                 */
                if (isCanDrag) {
                    //RectF rectF = getMatrixRectF();
                    if (getDrawable() == null) {
                        return true;
                    }

                    isCheckX = isCheckY = true;

                    // 如果图片在控件内,不允许移动
                    if (rectF.width() < getWidth()) {
                        isCheckX = false;
                        dx = 0f;
                    }
                    if (rectF.height() < getHeight()) {
                        isCheckY = false;
                        dy = 0f;
                    }

                    mMatrix.postTranslate(dx, dy);

                    // 移动事检测边界
                    checkSideAndCenterWhenTransate();

                    setImageMatrix(mMatrix);
                }

                mLastX = x;
                mLastY = y;

                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // 清楚手指
                mLastPointerCount = 0;

                break;
        }

        return true;
    }

    private void checkSideAndCenterWhenTransate() {
        RectF rectF = getMatrixRectF();
        float deltaX = 0f;
        float deltaY = 0f;
        int width = getWidth();
        int height = getHeight();

        if (rectF.top > 0 && isCheckY) {
            deltaY = -rectF.top;// 往上边移动
        }
        if (rectF.bottom < height && isCheckY) {
            deltaY = height - rectF.bottom;// 往底部移动
        }

        if (rectF.left > 0 && isCheckX) {
            deltaX = -rectF.left;// 往左边移动
        }
        if (rectF.right < width && isCheckX) {
            deltaX = width - rectF.right;// 往右边移动
        }
        // 移动
        mMatrix.postTranslate(deltaX, deltaY);
    }

    private boolean isMoveAction(float dx, float dy) {
        // 求得两点的距离
        return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;
    }

}

然后是activity的代码

import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.bumptech.glide.DrawableRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.ListPreloader;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.example.user.mypractice.R;
import com.squareup.picasso.Picasso;

import java.util.ArrayList;
import java.util.List;

/**
 * @author dj
 * @version 1.0
 * @description 缩放图片
 * @created on 2017/4/19.
 */

public class ScaleViewActivity extends AppCompatActivity {
    private List list;
    private RelativeLayout rlBack;
    private TextView tvCurrent;
    private TextView tvTotal;
    private ViewPager vpImgCheck;
    private ImageVpAdapter adapter;
    //图片所在的位置
    private int position;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scale_view);
        initView();
        list = new ArrayList<>();
        adapter=new ImageVpAdapter();
        position = 0;
        vpImgCheck.setAdapter(adapter);
        vpImgCheck.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int i, float v, int i1) {

            }

            @Override
            public void onPageSelected(int i) {
                int current=i+1;
                tvCurrent.setText(current+"");
            }

            @Override
            public void onPageScrollStateChanged(int i) {

            }
        });
        //加载数据
        loadData();
    }

    /**
     * 加载数据
     */
    private void loadData() {
        list.clear();
        list.add("http://scimg.jb51.net/allimg/151228/14-15122Q60431W4.jpg");
        list.add("http://img1.3lian.com/2015/a1/137/d/37.jpg");
        list.add("http://pic.qiantucdn.com/58pic/18/37/96/18n58PICPb7_1024.jpg");
        list.add("http://pic.qiantucdn.com/58pic/12/81/76/44n58PICAT2.jpg");
        list.add("http://pic.qiantucdn.com/58pic/14/44/24/94b58PICCxn_1024.jpg");
        tvTotal.setText(list.size()+"");
        vpImgCheck.setCurrentItem(position);
        adapter.notifyDataSetChanged();
    }


    private void initView() {
        rlBack = (RelativeLayout) findViewById(R.id.rl_back);
        tvCurrent = (TextView) findViewById(R.id.tv_current);
        tvTotal = (TextView) findViewById(R.id.tv_total);
        vpImgCheck = (ViewPager) findViewById(R.id.vp_img_check);
    }

    /**
     * 适配器
     */
    private class ImageVpAdapter extends PagerAdapter {
        @Override
        public int getCount() {
            return list.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object o) {
            return view == o;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            final ScaleView scaleView = new ScaleView(ScaleViewActivity.this);
            scaleView.setScaleType(ImageView.ScaleType.MATRIX);
            Picasso.with(ScaleViewActivity.this).load(list.get(position)).placeholder(R.mipmap.load).error(R.mipmap.error)
                    .resize(500,500).centerCrop().into(scaleView);

            container.addView(scaleView);
            return scaleView;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            if (object instanceof ScaleView) {
                ScaleView view = (ScaleView) object;
                container.removeView(view);
            }
        }
    }

好了,我这里用的是第三方的Picasso加载的图片,大家都知道,这个框架轻量并且使用方便,所以没有什么太大问题,好了开始运行

Android 实现可以自由移动缩放的图片控件并实现ViewPager滑动_第1张图片

咦?这TM是什么和我想象的不一样啊?

Android 实现可以自由移动缩放的图片控件并实现ViewPager滑动_第2张图片

明明原图是这么的山清水秀,怎么变成那样的呢?

这是因为picasso图片没有加载完整然后就将图片放置到控件里了,所以没有显示完全,可以随意拖动,但是看起来就好像放大了数倍一样。

那么有没有什么方法可以让图片加载完成后再把图片放置上去呢?

我试着查了一下,发现picasso并没有这个方法,不过在glide这个图片加载框架有相应的方法

new SimpleTarget() {
                        @Override
                        public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
                            
                        }
                    }

有Drawable属性,可以直接设置,所以就可以写成

Glide.with(ScaleViewActivity.this).load(list.get(position)).placeholder(R.mipmap.load).error(R.mipmap.error)
                    .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                    .into(new SimpleTarget() {
                        @Override
                        public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) {
                            scaleView.setImageDrawable(resource);
                        }
                    });

将原来picasso加载图片的地方替换为这个就行了

Android 实现可以自由移动缩放的图片控件并实现ViewPager滑动_第3张图片

这样就达到效果了,破费。

注意:使用时如果这个控件在布局文件里写的要加上

android:scaleType="matrix"

 
  

你可能感兴趣的:(android,自定义View)