Android ImageView源码解析
2016-01-17 18:28
926 查看
单指移动图片实现
界面布局<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显示不一样了
android:scaleType=”center”
(1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按照图片的原大小居中显示,不缩放,用ImageView的大小截取图片的居中部分。
(2)当图片小于ImageView的宽高:直接居中显示该图片。
android:scaleType=”centerCrop”
(1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按比例缩小图片,直到图片的宽高有一边等于ImageView的宽高,则对于另一边,图片的长度大于或等于ImageView的长度,最后用ImageView的大小居中截取该图片。
(2)当图片小于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按比例扩大图片,直到图片的宽高大于或等于ImageView的宽高,并按ImageView的大小居中截取该图片。
android:scaleType=”centerInside”
(1)当图片大于ImageView的宽高:以图片的中心和ImageView的中心点为基准,按比例缩小图片,使图片宽高等于或者小于ImagevView的宽高,直到将图片的内容完整居中显示。
(2)当图片小于ImageView的宽高:直接居中显示该图片。
android:scaleType=”fitCenter” Bitmap**默认scaleType属性**fitCenter
表示把图片按比例扩大(缩小)到ImageView的宽度,居中显示,。如果高度要比view的要低,那缺少的部分不显示,如果高度比view的高度要高,多余的部分不显示。
android:scaleType=”fitStart”
表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的上方显示。
android:scaleType=”fitEnd”
表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的下方显示。
android:scaleType=”fitXY”
表示把图片按指定的大小在ImageView中显示,拉伸或收缩图片,不保持原比例,填满ImageView。
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方法相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories