美图欣赏
冬季里的黄山0. 基本特性
- src: 设置图片资源
- scaleType: 图形设定显示效果,是裁剪,缩放,拉伸等(下面的configureBounds方法重点关注);
- cropToPadding: 保证padding空间区域不会有图形内容,会被裁剪掉;
- tint: 图片上色处理.
- adjustViewBounds: 这个属性对imageView的实际宽高有很大的影响,他的目的是保证控件宽高比例和图形drawable比例相同.在测量过程时会通过该属性以及drawble宽高比来构建控件的实际大小;
- 等等
1. 测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
resolveUri();
int w;
int h;
//真是图片drawable的宽高比记录
float desiredAspect = 0.0f;
//控件的宽高是否支持动态的调整以适配图片的宽高比;
boolean resizeWidth = false;
boolean resizeHeight = false;
//imageview的宽,高测量规格
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (mDrawable == null) {
// If no drawable, its intrinsic size is 0.
mDrawableWidth = -1;
mDrawableHeight = -1;
w = h = 0;
} else {//如果有drawable;
w = mDrawableWidth;
h = mDrawableHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
//如果设置了adjustViewBounds="true"
if (mAdjustViewBounds) {
//宽或者高设定的不是match_parent可以
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
//记录图片draowable的宽高比
desiredAspect = (float) w / (float) h;
}
}
//控件的padding数值
int pleft = mPaddingLeft;
int pright = mPaddingRight;
int ptop = mPaddingTop;
int pbottom = mPaddingBottom;
int widthSize;
int heightSize;
//宽,高支持调整, 看看怎么调整?
if (resizeWidth || resizeHeight) {
/* If we get here, it means we want to resize to match the
drawables aspect ratio, and we have the freedom to change at
least one dimension.
*/
//从这里看出,如果iv控件如果没有设定adjustViewBounds="true",maxWith,maxHeight是不会生效的.
//预先计算出iv的宽度,这里考虑了最大宽度;
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
//预先计算出iv的高度,这里考虑了最大高度;
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
//然而不止这样,还要考虑drawable的宽高比,
if (desiredAspect != 0.0f) {
// 计算iv控件本身的宽,高比;
float actualAspect = (float)(widthSize - pleft - pright) /
(heightSize - ptop - pbottom);
//如果宽,高比是不同的时候,进入A1
if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
boolean done = false;
//宽度是wrap的时候
if (resizeWidth) {
//根据drawable的宽高比,以及当前的iv的预测量高度来计算现在的宽度;
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
//当高度固定的时候.会在newWidth和mMaxWidth中再得出一个最小的;
if (!resizeHeight && !mAdjustViewBoundsCompat) {
widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
}
//当高度固定的时候,该条件会满足;若不固定即宽高是actualAspect和desiredAspect
//一般是相同的,也不会进入进入A1,固不会来到这里.
if (newWidth <= widthSize) {
widthSize = newWidth;
//等比计算iv宽度之后就不会再计算高了,不需要重复的计算了;
done = true;
}
}
// 如果宽没有计算,即宽度是固定的,高度是wrap,那么就计算高的数值;原理和宽计算是一样的.
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
// Allow the height to outgrow its original estimate if width is fixed.
if (!resizeWidth && !mAdjustViewBoundsCompat) {
heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
heightMeasureSpec);
}
if (newHeight <= heightSize) {
heightSize = newHeight;
}
}
}
}
} else {
//当不存在宽,高调整的时候,如果设置了adjustViewBounds="false"
//计算方式就是根据drawable的宽,高,以及通常的测量方式来测量iv的大小了.
w += pleft + pright;
h += ptop + pbottom;
w = Math.max(w, getSuggestedMinimumWidth());
h = Math.max(h, getSuggestedMinimumHeight());
widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
}
setMeasuredDimension(widthSize, heightSize);
}
- 总结:测量有一些复杂,分为两类.其一是普通的测量,根据padding, drawable的宽高,以及测量规格进行标准的图形测量.其二,当图形设置了adjustViewBounds=true这一个属性,那么意图是要让图形显示的时候能够保持原来的宽高比效果,必须要至少有一方的规格不是固定的,这样就可以根据drawable的宽高比以及另外一方的fix尺寸进行比例缩放,进而让图形可以不变形地展示出来.测量就是实现了这样的意图.(注意: 图形的最终是否变形,是否等比还是要看scaleType的属性设定)
- 尺寸的修订:
private int resolveAdjustedSize(int desiredSize, int maxSize,
int measureSpec) {
int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
/* Parent says we can be as big as we want. Just don't be larger
than max size imposed on ourselves.
*/
result = Math.min(desiredSize, maxSize);
break;
case MeasureSpec.AT_MOST:
// Parent says we can be as big as we want, up to specSize.
// Don't be larger than specSize, and don't be larger than
// the max size imposed on ourselves.
result = Math.min(Math.min(desiredSize, specSize), maxSize);
break;
case MeasureSpec.EXACTLY:
// No choice. Do what we are told.
result = specSize;
break;
}
return result;
}
- 根据图形内容drawable宽/高, 以及限制的最大值来计算出图形控件最终可以使用的宽与高.drawable最终填入到该图形区域中.
2. 图形效果设定, 主要是根据设定scaleType来缩放或者裁剪图片.
private void configureBounds() {
if (mDrawable == null || !mHaveFrame) {
return;
}
int dwidth = mDrawableWidth;
int dheight = mDrawableHeight;
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
int vheight = getHeight() - mPaddingTop - mPaddingBottom;
boolean fits = (dwidth < 0 || vwidth == dwidth) &&
(dheight < 0 || vheight == dheight);
//如果是color类型的drawable或者fitxy,那么drawable图形会填满控件,不管是否拉伸;
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);
mDrawMatrix = null;
} else {
// 将drawable的宽高设地为原始的图形宽高,后面的缩放都是在这个基础上做的变换
mDrawable.setBounds(0, 0, dwidth, dheight);
if (ScaleType.MATRIX == mScaleType) {
//设定了matrix模式,就用他们设定的;
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
} else if (fits) {
// 没有缩放,也没有偏移
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
// 将Bitmap放在图形控件的中心,不会去缩放drawable;不管大小如何
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
(int) ((vheight - dheight) * 0.5f + 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
//CENTER_CROP,他会将图形进行缩放,并且铺满整个iv空间居中显示,至少一个方向保留所有内容
//图形的宽高比大和iv控件宽高比进行比对: 图形的宽高比大,就以iv的高进行缩放;图形的宽高比小,
//就以iv的宽缩放.最后都图形能铺满空间.
mDrawMatrix = mMatrix;
float scale;
float dx = 0, dy = 0;
//图形的宽高比大,以iv的高为基准缩放,图形的高内容保留,宽会丢失一部分图形内容
if (dwidth * vheight > vwidth * dheight) {
scale = (float) vheight / (float) dheight;
dx = (vwidth - dwidth * scale) * 0.5f;
} else {//图形的宽高比小,以iv的宽为基准缩放,高内容会有部分的图形丢失
scale = (float) vwidth / (float) dwidth;
dy = (vheight - dheight * scale) * 0.5f;
}
//缩放
mDrawMatrix.setScale(scale, scale);
//平移drawable中心到iv中心;
mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
//该模式会以完整展示为目的,图形大于iv就缩放,小于就不做什么.
//fitcenter大于iv和center_inside一样,小于iv大小会放大的,这个不会放大!
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 = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
//整合到matrix里面,后面组合到drawable中去;
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
} else {
// ScaleType.FIT_START || ScaleType.FIT_CENTER || ScaleType.FIT_END,
//这三个模式的使用实现; 会等比缩放至填满一边,另外一边一般会有空隙;
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
}
-
总结一下:
-
FIT_START || FIT_CENTER<默认的> || FIT_END
- 等比缩放,以至能够填满整个iv控件空间.
-
CENTER_INSIDE
- 以完整展示图片为目的,drawable图形大于iv就缩放,小于就不做什么.fit_center小于会放大填满
-
CENTER
- 不会缩放,多大就是多大,图形中心在iv控件中心
-
CENTER_CROP
- 缩放, 在一个方向顶边,一个方向放大后裁剪,中心显示
-
FIT_XY
- 填满控件空间, 会出现拉扯变形现象.
-
MATRIX
- 可以自定义矩阵变化,平移,缩放,旋转等等效果.
-
3. 图片设定效果的生效与绘制
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//没有drwable不绘制
if (mDrawable == null) {
return; // couldn't resolve the URI
}
//color,shape一开始没有宽高,经过设定之后是有宽高的;
if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return; // nothing to draw (empty bounds)
}
//如果没有矩阵变化,没有图形设定就执行drawable的绘制了.
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
int saveCount = canvas.getSaveCount();
canvas.save();
//裁剪和偏移的内容;当mCropToPadding为true的时候,会将padding区域的图形内容都切去.
// 不是padding区域本来就没有图形内容吗?不是的,在有些场景下比如centerCrop类型.
//图形在放大情况下会填充到padding区间的,这个时候如果进行mCropToPadding,才可以让
//padding区域没有图形内容.
if (mCropToPadding) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
scrollX + mRight - mLeft - mPaddingRight,
scrollY + mBottom - mTop - mPaddingBottom);
}
//画布偏移。
canvas.translate(mPaddingLeft, mPaddingTop);
//这里是重要的组合前面的图形变化设定,组合到canvas中去
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
//drawbale来绘制图形;
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
4. 实例说明:
8238c52d33b4a3ba0be03a613faad9c9.jpg-
假设是一张宽图,宽/高 > 1. 当scaleType为centerCrop, padding=30dp, 我们实际的内容显示是如何显示的呢? 当paddingLeft=30dp的时候又是怎样的效果呢?
//图形会居中显示,无边距。 0. scaleType=centerCrop <ImageView android:id="@+id/iv" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/reba" android:scaleType="centerCrop" /> //图形上下有30内边距,左右无内边距,会居中显示。 1. scaleType=centerCrop, padding=30dp; <ImageView android:id="@+id/iv" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/reba" android:scaleType="centerCrop" android:padding="30dp" /> //图形的中心在控件的中心右15dp位置,不会居中显示,也没有内边距。 2. scaleType=centerCrop, paddingLeft=30dp; <ImageView android:id="@+id/iv" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/reba" android:scaleType="centerCrop" android:paddingLeft="30dp" />
-
0模式下:根据
configureBounds
中centerCrop的计算规则图形的宽高比大,就以iv的高进行缩放
, 所以高缩放到填满高的空间,高不会裁剪,但是宽会有一些裁剪,然后根据dx = (vwidth - dwidth * scale) * 0.5f;
的矩阵中心偏移可以得出偏移量刚好是图形中心和控件中心的差值,然后经过mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
偏移,就将图形矩阵的中心和控件中心重合在一起了。所以图形就居中显示.
-
1模式下:同样是和0一样进行高缩放至填满,因为有上下30的padding,所以只能缩放之后上下有30dp的空白,但是在这里经过矩阵偏移之后图形的中心并不在控件的中心。假设经过缩放之后图形的宽为200dp, 高为100dp:
1590912302158.png
-
该模式下, 当经过
1590913188325.pngdx = (vwidth - dwidth * scale) * 0.5f;
计算之后,dx = -80dp, 经过postTranslate偏移,也就是说图形向中心会左移80, x中心为20dp了,并没有与控件的中心(x = 50dp)对齐, 那么控件显示中心是图形的中心的右边位置吗?答案不是, 在的onDraw中还有一次画布偏移,canvas.translate(mPaddingLeft, mPaddingTop);
这里会将图形的位置往右移30dp,所以就会和控件中心水平重合。图形会居中显示的呢,同时paddingTop的向下偏移30dp在垂直方向上也能对齐控件中心。最终的图形效果是这样子, 红色区域为图形的画布内容。
- 在2模式下,只有paddingLeft=30dp, 图形向右偏移30dp/2 = 15dp。来看下, 红色为图形drawable, 首先经过偏移之后, 图形的中心在0点的右边35dip之处。
然后经过canvas的右移paddingLeft, 30dp, 那么drawable的中心就在0点右侧65dp处,所以图形就在控件中心右侧的15dp处而不是30dp处(控件中心在0点的右侧50dp处)。
1590915351468.png
大概就这样子吧,
5. 图片的着色,滤色处理
-
ColorStateList: 这个是对图片进行着色的颜色集,底层是通过ColorFilter来实现的.区别是他可以提供一组色彩处理.他会读取tint设置的颜色属性值. 他会和PorterDuff.Mode组合使用.
//读取tint颜色到ColorStateList中 ColorStateList mDrawableTintList = a.getColorStateList(R.styleable.ImageView_tint); ....... //将颜色集设置到drawable中去;最后在onDraw方法中通过omDrawable.draw(canvas),生效图形的着色处理. private void applyImageTint() { if (mDrawable != null && (mHasDrawableTint || mHasDrawableTintMode)) { mDrawable = mDrawable.mutate(); if (mHasDrawableTint) { mDrawable.setTintList(mDrawableTintList); } if (mHasDrawableTintMode) { mDrawable.setTintMode(mDrawableTintMode); } } }
-
colorFilter: 也是着色,会覆盖ColorStateList的效果.图片的黑白处理就可以用他来实现.
//设置滤色效果 public void setColorFilter(ColorFilter cf) { if (mColorFilter != cf) { mColorFilter = cf; mHasColorFilter = true; mColorMod = true; //颜色 applyColorMod(); invalidate(); } } private void applyColorMod() { // Only mutate and apply when modifications have occurred. This should // not reset the mColorMod flag, since these filters need to be // re-applied if the Drawable is changed. if (mDrawable != null && mColorMod) { mDrawable = mDrawable.mutate(); if (mHasColorFilter) { //将颜色filter设定到drawable中去 mDrawable.setColorFilter(mColorFilter); } mDrawable.setXfermode(mXfermode); mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); } } //实现图片的灰白/黑白效果处理,一般是利用该策略来做的; ---- start ---- Drawable mDrawable = iv.getDrawable(); ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0);//灰白设定 ColorMatrixColorFilter cf = new ColorMatrixColorFilter(cm); mDrawable.setColorFilter(cf); ---- end ----
网友评论