前言:
最近遇到一個bug,問題描述是這樣的:啟動頁需要放置一張廣告圖药薯,要使這張圖在不變形的情況下(等比例縮放)绑洛,寬度要占滿屏幕寬,于是手動計算并設(shè)置ImageView需要的縮放比例來對圖片進(jìn)行縮放童本。該方法觸發(fā)的時機(jī)引發(fā)了一些問題真屯。
private void scaleView(ImageView iv) {
if (iv.getDrawable() == null) {
return;
}
Matrix matrix = iv.getImageMatrix();
float scaleWidth = (float) iv.getWidth() / (float) iv.getDrawable().getIntrinsicWidth();
float scaleHeight = (float) iv.getHeight() / (float) iv.getDrawable().getIntrinsicHeight();
float scaleFactor = scaleWidth > scaleHeight ? scaleWidth : scaleHeight;
matrix.setScale(scaleFactor, scaleFactor, 0.0F, 0.0F);
if (BigDecimal.valueOf((double) scaleFactor).compareTo(BigDecimal.valueOf((double) scaleHeight)) == 0) {
float translateX = ((float) iv.getDrawable().getIntrinsicWidth() * scaleFactor - (float) iv.getWidth()) / 2.0F;
matrix.postTranslate(-translateX, 0.0F);
}
iv.setImageMatrix(matrix);
}
問題在于:
xml布局中使用了match_parent來定義ImageView大小,然而我在Fragment的onViewCreated方法中來調(diào)用該方法(當(dāng)然在Activity的onCreate方法中調(diào)用也是一樣)穷娱,從上面代碼可以發(fā)現(xiàn) iv.getWidth()/iv.getHeight()需要獲取ImageView的width和height來計算比例绑蔫,而此時并沒有進(jìn)行Layout,所以iv.getWidth()/iv.getHeight()的返回值是0泵额,導(dǎo)致該圖片最終不可見或者縮放操作無效配深。
解決思路:
問題的關(guān)鍵在于修改觸發(fā)這個函數(shù)的時機(jī),好的時機(jī)是在View的onLayout函數(shù)結(jié)束之后用戶可見之前嫁盲,因為此時實際高寬已經(jīng)確定(提醒一下測量高寬MeasureWidth/MeasureHeight不一定等于實際高寬Width/Height)
解決方案:
總結(jié)了三種常見的解決方案篓叶,如下:
1.手動調(diào)用view的measure方法(不太建議):
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyImageView myImageView = (MyImageView) findViewById(R.id.imageview);
int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
myImageView.measure(w, h);
Log.d("===MainActivity", "onCreate執(zhí)行完畢..myImageView " +
"height:" + myImageView.getMeasuredHeight() + " ,width:" + myImageView.getMeasuredWidth());
}
手動調(diào)用之后就確保可以獲取到MeasuredHeight了羞秤,但MeasuredHeight不一定等于Height缸托。
2.利用ViewTreeObserver注冊addOnPreDrawListener監(jiān)聽
ViewTreeObserver vto = myImageView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
int height = myImageView.getMeasuredHeight();
int width = myImageView.getMeasuredWidth();
Log.d("===PreDrawListener", "PreDrawListener..myImageView " +
"height:" + height + " ,width:" + width);
myImageView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
ViewTreeObserver,View事件的觀察者瘾蛋,可以用來監(jiān)聽視圖樹俐镐,會監(jiān)聽視圖樹發(fā)生全局變化時發(fā)出的通知。這里指的全局 事件包括而且不局限在以下幾個:整個視圖樹的布局變化哺哼,開始繪制視圖京革,觸摸模式改變等等。添加addOnPreDrawListener確保了Layout過程執(zhí)行幸斥,此時能獲取寬高匹摇,在獲取之后應(yīng)及時調(diào)用remove方法將監(jiān)聽移除
3.利用ViewTreeObserver注冊O(shè)nGlobalLayoutListener監(jiān)聽
ViewTreeObserver vto = myImageView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
myImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.d("===OnGlobalLayout", "OnGlobalLayoutListener..myImageView " +
"height:" + myImageView.getHeight() + " ,width:" + myImageView.getWidth());
}
});
跟方法2類似,只不過換了個監(jiān)聽甲葬,可以實現(xiàn)
**值得注意的是remove方法需要兼容一下sdk版本:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
mWelcomeImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
mWelcomeImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
**
4.使用View.post(new Runnable)該方法比較巧妙廊勃,也是我比較喜歡的。
mWelcomeImageView.post(new Runnable() {
@Override
public void run() {
scaleView(mWelcomeImageView);
mWelcomeImageView.invalidate();
}
});
該方法將涉及獲取高寬的方法使用post方法加入到隊列中,而該方法的調(diào)用一定是在View被創(chuàng)建成功之后
注意點:在圖片最大高度確定時坡垫,若按屏幕寬度等比例縮放圖片梭灿,需要圖片高度超出控件高度才能實現(xiàn),此時需要添加一個scaleType:matrix屬性冰悠,如下:
<ImageView
android:id="@+id/welcome"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="100dp"
android:scaleType="matrix" />