Android ImageView源码解析

单指移动图片实现

界面布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center_horizontal">
    <ImageView
        android:id="@+id/imageview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="center" //drawable居中对齐不缩放
        android:src="@drawable/test"
        android:visibility="visible"/>
    <Button
        android:id="@+id/clearMatrix"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="clearMatrix"/>
</RelativeLayout>

主实现

public class MainActivity extends AppCompatActivity {
    @Bind(R.id.applyMatrix)
    Button applyMatrix;
    @Bind(R.id.imageview)
    ImageView imageview;
    float firstDownX, firstDownY;
    float centerX, centerY;
    private int mode = 0;
    private boolean firstDown = true; //是否是第一次按
    @Override
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        imageview.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction() & MotionEvent.ACTION_MASK) { //否则ACTION_POINTER_DOWN和ACTION_POINTER_UP事件监听不到
                    case MotionEvent.ACTION_DOWN:
                        //除了MATRIX模式其它模式都是drawable居中的,所以需要记录第一次按下的坐标
                        if (imageview.getScaleType() != ImageView.ScaleType.MATRIX && firstDown) {
                            centerX = imageview.getWidth() / 2 - imageview.getDrawable().getIntrinsicWidth() / 2;
                            centerY = imageview.getHeight() / 2 - imageview.getDrawable().getIntrinsicHeight() / 2;
                        }//如果是MATRIX模式,那么drawable在imageview左上角(0,0)所以不需要记录中心坐标
                        firstDown = false;
                        firstDownX = event.getX();
                        firstDownY = event.getY();
                        mode = 1;
                        break;
                    case MotionEvent.ACTION_UP: //松开手指重新记录中心坐标
                        centerX += (event.getX() - firstDownX);
                        centerY += (event.getY() - firstDownY);
                        mode = 0;
                        break;
                    case MotionEvent.ACTION_POINTER_UP: //双指松开一个
                        mode -= 1;
                        break;
                    case MotionEvent.ACTION_POINTER_DOWN: //双指按下一个
                        secondDownX = event.getX();
                        secondDownY = event.getY();
                        mode += 1;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (mode >= 2) { //手指移动模式
                            //imageview.setImageMatrix(getScaleMatrix(event));
                        } else { //单指移动模式
                            float moveX = event.getX() - firstDownX;
                            float moveY = event.getY() - firstDownY;
                            Matrix matrix = new Matrix();
                            matrix.setTranslate(centerX + moveX, centerY + moveY); //x,y轴移动的距离,为什么要这么处理?
                            imageview.setScaleType(ImageView.ScaleType.MATRIX); //必须设置为MATRIX模式才行
                            imageview.setImageMatrix(matrix);
                        }
                        break;
                }
                return false; //返回false不消耗事件,从而让onClickImage可以得到执行
            }
        });
    }

    private Matrix getScaleMatrix(MotionEvent event) {
        Matrix matrix = new Matrix();
        //............
        return matrix;
    }

    @OnClick(R.id.clearMatrix)
    void clearMatrix() {
        imageview.setImageMatrix(null); //还原
    }

    @OnClick(R.id.imageview)
    void onClickImage() { //可以发现在drawable没显示的地方也可以回调到该方法,imageview这个控件的位置并没有发生变化,只是显示发生了变化,所以.....补间动画也是这样滴
        Toast.makeText(this, "点击了图片", Toast.LENGTH_SHORT).show();
    }
}

看到这里的实现:matrix.setTranslate(centerX + moveX, centerY + moveY);在imageview的onDraw源码中,每次发起了一个重绘,都只是在当前图层进行操作,并没有影响到canvas,因为内部进行了save/restore调用嘛,其实没有save/restore也不会影响下次的重绘,因为canvas是从父viewgroup传递过来的,父viewgroup绘制每个子view,都会先把canvas保存起来,就是说下一次重绘onDraw,canvas矩阵操作移动缩放错切旋转默认的中心始终是在(0,0)坐标,所以需要加上上一次up事件xy方向移动的距离

ImageView源码实现

    public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initImageView();
        ...........
        Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src); //android:src=""属性
        if (d != null) {
            setImageDrawable(d);
        }
        final int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1); //android:scaleType=""属性
        if (index >= 0) {
            setScaleType(sScaleTypeArray[index]);
        }
        ...........
    }
    private void initImageView() {
        mMatrix     = new Matrix();
        mScaleType  = ScaleType.FIT_CENTER; //默认缩放模式FIT_CENTER
        mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <=
                Build.VERSION_CODES.JELLY_BEAN_MR1;
    }
    public void setImageDrawable(@Nullable Drawable drawable) {
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;
            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;
            updateDrawable(drawable);
            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout(); //如果新drawable和源drawable的宽高不等,那么会请求父viewgroup重新发起measure,layout,draw流程
            }
            invalidate(); //重绘
        }
    }
    public void setScaleType(ScaleType scaleType) {
        if (scaleType == null) {
            throw new NullPointerException();
        }
        if (mScaleType != scaleType) { //先判断新的scaletype是否发生过变化
            mScaleType = scaleType;
            setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);            
            requestLayout(); //请求父viewgroup重新发起measure,layout,draw流程
            invalidate();
        }
    }
    public void setImageMatrix(Matrix matrix) {
        // collapse null and identity to just null
        if (matrix != null && matrix.isIdentity()) {
            matrix = null;
        }
        // don't invalidate unless we're actually changing our matrix
        if (matrix == null && !mMatrix.isIdentity() ||
                matrix != null && !mMatrix.equals(matrix)) {
            mMatrix.set(matrix); //mMatrix变量赋值
            configureBounds(); //关键方法啊,字面意思配置imageview的边界
            invalidate(); //导致了重绘
        }
    }
   private void configureBounds() { //这个方法主要就是对mDrawMatrix矩阵进行操作,进而在onDraw方法中应用到canvas上去
        if (mDrawable == null || !mHaveFrame) {
            return;
        }
        int dwidth = mDrawableWidth; //drawable的宽,注意跟imageveiw是不同的
        int dheight = mDrawableHeight; //drawable的高,注意跟imageveiw是不同的
        int vwidth = getWidth() - mPaddingLeft - mPaddingRight; //这个是imageview宽
        int vheight = getHeight() - mPaddingTop - mPaddingBottom; //这个是imageview高
        boolean fits = (dwidth < 0 || vwidth == dwidth) &&
                       (dheight < 0 || vheight == dheight);

        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            /* If the drawable has no intrinsic size, or we're told to
                scaletofit, then we just fill our entire view.
            */
            mDrawable.setBounds(0, 0, vwidth, vheight); //这说明了如果drawable的像素宽高比bitmap还大的话,那么drawable多余的部分就会被裁掉
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.
            mDrawable.setBounds(0, 0, dwidth, dheight);
            if (ScaleType.MATRIX == mScaleType) { //MATRIX缩放模式
                // Use the specified matrix as-is.
                if (mMatrix.isIdentity()) { //是单位矩阵,不处理
                    mDrawMatrix = null;
                } else {
                    mDrawMatrix = mMatrix; //赋值mDrawMatrix,onDraw会用到该变量
                }
            } else if (fits) {
                // The bitmap fits exactly, no transform needed.
                mDrawMatrix = null;
            } else if (ScaleType.CENTER == mScaleType) { //CENTER缩放模式
                // Center bitmap in view, no scaling.
                mDrawMatrix = mMatrix;
                //mDrawMatrix矩阵的中心点移动距离计算方法,这里充分说明了CENTER缩放模式下图片的中心点和ImageView的中心点为基准,按照图片的原大小居中显示,不缩放,
                //用ImageView的大小截取图片的居中部分
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                                         Math.round((vheight - dheight) * 0.5f));
            } else if (ScaleType.CENTER_CROP == mScaleType) { //CENTER_CROP模式就不一一说明了
                mDrawMatrix = mMatrix;

                float scale;
                float dx = 0, dy = 0;

                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight; 
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
                mDrawMatrix = mMatrix;
                float scale;
                float dx;
                float dy;

                if (dwidth <= vwidth && dheight <= vheight) {
                    scale = 1.0f;
                } else {
                    scale = Math.min((float) vwidth / (float) dwidth,
                            (float) vheight / (float) dheight);
                }

                dx = Math.round((vwidth - dwidth * scale) * 0.5f);
                dy = Math.round((vheight - dheight * scale) * 0.5f);

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(dx, dy);
            } else {
                // Generate the required transform.
                mTempSrc.set(0, 0, dwidth, dheight);
                mTempDst.set(0, 0, vwidth, vheight);

                mDrawMatrix = mMatrix;
                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
            }
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mDrawable == null) {
            return; // couldn't resolve the URI
        }
        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
            return;     // nothing to draw (empty bounds)
        }
        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
            mDrawable.draw(canvas);
        } else {
            int saveCount = canvas.getSaveCount();
            canvas.save(); //保存当前画布,说明本次canvas的任何矩阵运算只在本次内有效
            .........
            canvas.translate(mPaddingLeft, mPaddingTop);
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix); //实际上是操作对canvas进行矩阵操作的,concat方法而不是canvas的setMatrix方法
            }
            mDrawable.draw(canvas); //在mDrawMatrix矩阵操作后的canvas上绘制该drawable
            canvas.restoreToCount(saveCount); //恢复canvas到save之前
        }
    }

总结一下,现在终于知道了为什么不同的scaleType显示不一样了

  1. android:scaleType=”center
    (1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按照图片的原大小居中显示,不缩放,用ImageView的大小截取图片的居中部分。
    (2)当图片小于ImageView的宽高:直接居中显示该图片。
  2. android:scaleType=”centerCrop
    (1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按比例缩小图片,直到图片的宽高有一边等于ImageView的宽高,则对于另一边,图片的长度大于或等于ImageView的长度,最后用ImageView的大小居中截取该图片。
    (2)当图片小于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按比例扩大图片,直到图片的宽高大于或等于ImageView的宽高,并按ImageView的大小居中截取该图片。
  3. android:scaleType=”centerInside
    (1)当图片大于ImageView的宽高:以图片的中心和ImageView的中心点为基准,按比例缩小图片,使图片宽高等于或者小于ImagevView的宽高,直到将图片的内容完整居中显示。
    (2)当图片小于ImageView的宽高:直接居中显示该图片。
  4. android:scaleType=”fitCenter” Bitmap**默认scaleType属性**fitCenter
    表示把图片按比例扩大(缩小)到ImageView的宽度,居中显示,。如果高度要比view的要低,那缺少的部分不显示,如果高度比view的高度要高,多余的部分不显示。
  5. android:scaleType=”fitStart
    表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的上方显示。
  6. android:scaleType=”fitEnd
    表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的下方显示。
  7. android:scaleType=”fitXY
    表示把图片按指定的大小在ImageView中显示,拉伸或收缩图片,不保持原比例,填满ImageView。
  8. android:scaleType=”Matrix
    从ImageView左上角开始显示,不进行任何缩放

通过setImageMatrix方法实现动画效果

我们完全可以通过ImageView的setImageMatrix方法去自己实现一个动画,但是如果用补间动画去实现有什么区别呢?
setImageMatrix实际上通过把matrix应用到自己ImageView的画布canvas来进行缩放,移动等矩阵操作的

        protected void onDraw(Canvas canvas) {
            ........
            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix); //实际上是操作对canvas进行矩阵操作的,concat方法而不是canvas的setMatrix方法
            }
            mDrawable.draw(canvas);
        }

但是补间动画就不一样了,之前我分析过补间动画的原理,它会首先让imageview所在的viewgroup发起重绘,再调用ImageView的onDraw方法之前canvas已经带了矩阵操作信息了

//ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
   return child.draw(canvas, this, drawingTime);
}
//View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime){ //canvas是父viewgroup传递下来
    .........
    final Animation a = getAnimation();
    ..... //这之间对canvas进行矩阵操作
    draw(canvas);
    .........
}
//ImageView.java
protected void onDraw(Canvas canvas){ //补间动画的话,此时canvas就带了matrix信息了
    ......
}

双指缩放ImageView

有时间再去实现,原因其实也是一样的,调用ImageView的setImageMatrix方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值