如何保证ImageView.setImageResource()传入.9图片时展示正常?
> ImageView占位图可以通过backgroud设置.9图片实现,但是在src设置成带透明alpha图片时会透视显示背景,所以用src实现占位图更合理,但是scaleType的裁剪方式同样会影响到作为src的.9图片,这将会导致.9图片不能按照预期展示占位图


现象
-
ic_launcher.9.png
-
deafault¢erCrop 同样裁剪中间显示
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/ic_launcher" />
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher" />

- fitXy显示如预期
<ImageView
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="fitXY"
android:src="@drawable/ic_launcher" />

问题分析 (read the fucking source code)
- mScaleType怎么参与图片裁剪逻辑?
- 图片裁剪在什么时机执行?
- 如何干预裁剪方式?
第一个问题很简单ImageView.configureBounds(), 根据不同的case提供不同的换算策略,但是google用private告诉你,小子这不给你改!!!
private void configureBounds() {
...
if (ScaleType.MATRIX == mScaleType) {
// Use the specified matrix as-is.
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
} else if (fits) {
// The bitmap fits exactly, no transform needed.
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
// Center bitmap in view, no scaling.
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
Math.round((vheight - dheight) * 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
...
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
...
} else {
...
}
}
}
来看下第二个问题,configureBounds()都在哪调用?
- setImageMatrix(...)
- updateDrawable(...) <-- setImageResource(...)
- setFrame(...)
分析不难发现在绘制前这1,2两个方法并不会触发configureBounds(),所以问题指向3,setFrame的调用在View的layout(...)方法中
public void layout(int l, int t, int r, int b) {
...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
至此一和二两个问题解开迷雾,梳理下调用流程(-> 下一个方法 --> 方法内调用)
view->measure->layout-->setFrame-->configureBounds()->draw
不难分析只有在layout和draw之间动刀才可以干预裁剪方式,layout和setFrame都可以切入,根据最小作用域原则,更适合在setFrame处理.
解决办法
自定义ImageView重写setFrame()如下
public class NinePatchIamgeView extends ImageView {
public NinePatchIamgeView(Context context) {
super(context);
}
public NinePatchIamgeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public NinePatchIamgeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
// 当前ScaleType
ScaleType curScaleType = getScaleType();
// .9图片裁剪前设置临时类型ScaleType.FIT_XY
if (getDrawable() instanceof NinePatchDrawable) {
setScaleType(ScaleType.FIT_XY);
}
boolean changed = super.setFrame(l, t, r, b);
// 还原ScaleType
setScaleType(curScaleType);
return changed;
}
}