最近在研究圖片加載庫,趁機找時間學習下ImageView的源碼,探究下它內部機制,同時學習下View的封裝思想。
概述
Displays an arbitrary image, such as an icon. The ImageView class can load images from various sources (such as resources or content providers), takes care of computing its measurement from the image so that it can be used in any layout manager, and provides various display options such as scaling and tinting.
ImageView可以顯示各種圖片,比如icon圖。它可以顯示各種來源的圖片,包括Resource,Content Provider等,顯示的過程中會計算圖片的尺寸從而適應任何布局管理器,并且提供各種顯示可選項,比如縮放、著色等。(來自筆者蹩腳的翻譯)
XML屬性
我們平時一般在XML聲明ImageView,除了繼承自View的屬性,官方文檔列出了以下屬性,我們也主要從這些屬性了解ImageView的功能
XML屬性 | 說明 |
---|---|
android:adjustViewBounds | 設置為true,可以讓ImageView根據圖片的寬寬比調整大小,使用該屬性時不能固定ImageView的寬高,或者將寬高設置為match_parent |
android:baseline | 設置文字的baseLine到ImageView頂部的距離,如果baselineAlignBottom被設置為true,則該屬性會被覆蓋,即該屬性無效 |
android:baselineAlignBottom | 設置為true,baseLine則為ImageView的底部 |
android:cropToPadding | 設置為true,圖像會在Padding屬性的基礎上進行裁剪 |
android:maxHeight | 設置ImageView的最大高度 |
android:maxWidth | 設置ImageView的最大寬度 |
android:scaleType | 設置ImageView的縮放模式 |
android:src | 設置ImageView顯示圖片資源 |
android:tint | 設置ImageView顯示圖片的著色顏色 |
android:tintMode | 設置ImageView顯示圖片的著色模式 |
源碼學習
接下來通過查看ImageView源碼,分析下ImageView上面這些屬性是怎么起效果的,像adjustViewBound
、cropToPadding
這些屬性經常用不太順,從源碼的角度了解這些屬性是怎么影響ImageView最終效果。
圖片顯示
ImageView最大的功能就是顯示圖片,首先研究下圖片是怎么在ImageView中顯示的。
在平時使用中,我們可以通過以下兩種方式設置圖片:
- XML 設置src屬性,可以設置drawable資源,eg.
android:src="@drawable/test"
- Java代碼設置,可以設置調用imageView的
setImageBitmap
,setImageDrawable
,setImageResource
,setImageURI
設置
注意如果使用 setImageResource
, setImageURI
官方提示這兩個方法是需要在UI線程中解析圖片資源的,建議不要使用這兩個方法加載太大的圖,或者在其他線程中解析成Bitmap或者Drawable,避免畫面卡頓。
This does Bitmap reading and decoding on the UI thread, which can cause a latency hiccup. If that's a concern, consider using
接下來看一下在ImageView內部如何處理。
在構造函數中,XML中的src屬性會被讀取并轉化成Drawable,并通過setDrawable
設置圖片。
Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
if (d != null) {
setImageDrawable(d);
}
接著看下setDrawable
代碼,判斷當前的Drawable與設置的Drawable是否不同,不同的話,把ImageView的成員變量mResource
和mUri
值置空,避免其它方式的值影響內容顯示。然后調用updateDrawable
這個更新Drawable的方法。如果圖片寬高不同,需要重新布局requestLayout
,最后通過invalidate
刷新頁面,因為最后圖片的顯示是通過onDraw
刷新。
public void setImageDrawable(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();
}
invalidate();
}
}
updateDrawable
主要功能是將ImageView
中mDrawable
中替換,同時修改mDrawable
屬性,這里大部分是Drawable
的知識,先不細究了,留意下最后三個方法applyImageTint applyColorMod configureBounds
會影響圖片著色和縮放,后面會繼續提到。
private void updateDrawable(Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
}
mDrawable = d;
if (d != null) {
d.setCallback(this);
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setVisible(getVisibility() == VISIBLE, true);
d.setLevel(mLevel);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyImageTint();
applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
到這里我們可以了解到通過XML設置屬性就是最終是修改了ImageView中的mDrawable
變量,同時setDrawable
方法也是通過updateDrawable
來更新mDrawable
。
接下來看一下ImageView
的幾個設置顯示圖片的方法。
setImageBitmap
也是調用了setImageDrawable
,同時使用BitmapDrawable
可以減少中間過程中產生的drawable對象,可以大膽推測其他的Drawable
最終會轉化成BitmapDrawable
用于顯示。
public void setImageBitmap(Bitmap bm) {
setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}
然后看下setImageResource
和setImageUri
兩個方法的思路是是一樣,首先判斷mResource
或者mUri
有值,然后將原有的mDrawable
置空,resolverUri
重新解析資源,這個方法最終也是調用了updateDrawable
,更新圖片顯示,同樣也需要考慮到重新布局的問題。
public void setImageResource(int resId) {
if (mUri != null || mResource != resId) {
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(null);
mResource = resId;
mUri = null;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
public void setImageURI(Uri uri) {
if (mResource != 0 ||
(mUri != uri &&
(uri == null || mUri == null || !uri.equals(mUri)))) {
updateDrawable(null);
mResource = 0;
mUri = uri;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
圖片縮放 ScaleType
詳細大家都了解過ImageView
的圖片縮放類型,現在看一下是怎么通過代碼實現的。
首先看setScaleType
發現好像沒有什么特別,是重新設置ScaleType
枚舉,然后重新布局與繪制。
public void setScaleType(ScaleType scaleType) {
if (scaleType == null) {
throw new NullPointerException();
}
if (mScaleType != scaleType) {
mScaleType = scaleType;
setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);
requestLayout();
invalidate();
}
}
Ctrl+F
搜索 ScaleType
,仔細一看,原來藏在configureBounds
里,這里對ScaleType
枚舉進行處理,這個方法我們剛才已經在updateDrawable
中看到過,
接下來看下代碼。
private void configureBounds() {
if (mDrawable == null || !mHaveFrame) {
return;
}
//期望顯示的寬高,即要顯示的Drawable的寬高
int dwidth = mDrawableWidth;
int dheight = mDrawableHeight;
//ImageView中顯示內容的寬與高(不包括內邊距)
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
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) {
/* 如果Drawable沒有明確的尺寸或者設定為FIX_XY,那么圖片內容會充滿整個ImageView
*/
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
//否則我們需要自己處理縮放,讓Drawable顯示原有尺寸
mDrawable.setBounds(0, 0, dwidth, dheight);
if (ScaleType.MATRIX == mScaleType) {
//判斷mMatric是否為單位矩陣,如果不是的話將mMatric賦值給mDrawMatric,用于onDraw中對圖片進行變換
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
} else if (fits) {
//如果剛好合適則不需要變換
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
//如果ScaleType為CENTER,對圖片不進行縮放,將圖片移動到ImageView中心
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
(int) ((vheight - dheight) * 0.5f + 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
//如果ScaleType為CENTER_CROP ,對圖片進行等比縮放至圖片寬高大于等于ImageView,將圖片中心移動到ImageView中心
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((int) (dx + 0.5f), (int) (dy + 0.5f));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
//如果ScaleType為CENTER_INSIDE ,對圖片進行等比縮放至圖片寬高小等于ImageView,將圖片中心移動到ImageView中心
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);
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
} else {
//剩下三種情況生成相應的轉化矩陣
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
}
通過configureBounds
中可以知道ScaleType
本質就是通過Matrix
矩陣變換,主要通過縮放和位移實現具體效果。
ImageView構造函數中初始化時將ScaleType
默認為FIT_CENTER
。
mScaleType = ScaleType.FIT_CENTER;
cropToPadding 屬性
CropToPadding
這個屬性感覺平時不太用,嘗試用了下又感覺沒什么效果,所以這個屬性究竟是怎么其效果的呢?
首先我們可以找到mCropToPadding
這個變量,我們平時修改的就是這個值,進一步查找,可以發現在onDraw
中有用到這個變量,所以這個變量是在圖形繪制過程中生效的。我們來看下onDraw
中的相關代碼。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawable == null) {
return;
}
if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return;
}
//如果變換矩陣為空,并且沒有上內邊距和左內邊距則只直接繪制
if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
int saveCount = canvas.getSaveCount();
canvas.save();
//如果mCropToPadding設置為ture,需要裁剪區域,裁剪區域要考慮到x和y方向的內容滾動位移
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);
//如果存在變換矩陣則進行變換
if (mDrawMatrix != null) {
canvas.concat(mDrawMatrix);
}
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
從上面代碼中我們可以看到mCropToPadding
如果為true
,canvas
會劃出除去內邊距的實際顯示區域,這里我們還發現除了考慮到padding
屬性,還要考慮mScrollX
mScrollY
兩個屬性,為什么呢?
The offset, in pixels, by which the content of this view is scrolled
官方的解釋是內容區域在View中滾動偏移量,View容器實際是不會滾動的,真正滾動效果是通過內容的移動達到滾動(偏移量,也可以理解成位移),而padding
是包括在內容區域里的,這兩個偏移量會讓padding
區域滾動,從而導致原有padding
效果失效。以下代碼是View
類中獲取繪制區域的方法,繪制區域是考慮到Scroll
偏移的。
public void getDrawingRect(Rect outRect) {
outRect.left = mScrollX;
outRect.top = mScrollY;
outRect.right = mScrollX + (mRight - mLeft);
outRect.bottom = mScrollY + (mBottom - mTop);
}
為了讓padding
區域是基于View
,所以我們要事先繪制好期望的內容區域,避免Scroll
帶來的影響。畫了張圖。
除了Scroll
偏移會影響邊距,我們能從代碼中發現Matrix
矩陣縮放也會影響到邊距,大家可以回到上面講ScaleType
的部分,我們可以看到CENTER , CENTER_CROP , CENTER_INSIDE , FIT_CENTER , FIT_START , FIT_END
的矩陣變換都是基于不包括內邊距的區域的(·vheight
,vwidth
),而MATRIX
只是設置了下mDrawMatix
,沒有前幾種縮放類型做的范圍處理。onDraw
代碼中我們也能看到是先translate
確定繪制起點,然后再contact
矩陣變換,如果我們這樣做。
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="@color/colorAccent"
android:cropToPadding="false"
android:padding="10dp"
android:scaleType="matrix"
android:src="@mipmap/ic_launcher" />
ImageView imageView = (ImageView) findViewById(R.id.imageView);
Matrix matrix = new Matrix();
matrix.setTranslate(-40, -40);
imageView.setImageMatrix(matrix);
最后我們看到的效果是這樣的,同樣我們理解中的padding
失效了
如果我們把
cropToPadding
屬性設置為true
則得到下面的效果。所以總結來說
cropToPadding
是ImageView
為了補償其他屬性導致我們最終的“理解的內邊距效果”實現而做出的調整,目前了解到如果你的ImageView
設置了scrollX
scrollY
或者將scaleType
為MATRIX
時又需要保證padding
的效果,可以將cropToPadding
設置為true
。
adjustViewBounds屬性
我們可以通過ScaleType
讓顯示的圖片根據ImageView
大小進行各種類型的調整,我們可不可以讓ImageView
根據圖片內容調整大小呢?這時候我們就需要用到adjustViewBounds
這個屬性啦,能讓顯示的圖片原有比例,調整ImageView
到合適的大小,達到我們想要的圖片與ImageView
無縫銜接,我們從ImageView
代碼中看下是怎么實現的。老規矩搜索mAdjustViewBounds
屬性,就可以發現這個屬性主要是在onMeasure
方法中起作用。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
resolveUri();
int w;
int h;
// 期望的顯示比例(不包括padding),也就是要顯示圖片的原有比例
float desiredAspect = 0.0f;
// 用于判斷是否允許調整寬度,默認為false
boolean resizeWidth = false;
// 用于判斷是否允許調整高度,默認為false
boolean resizeHeight = false;
//獲取父容器為ImageView設置的尺寸測量模式
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (mDrawable == null) {
mDrawableWidth = -1;
mDrawableHeight = -1;
w = h = 0;
} else {
w = mDrawableWidth;
h = mDrawableHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
//如果設置mAdjustViewBounds為true,判斷widthSpecMode和heightSpecMode,如果不是EXACTLY則支持調整,并獲得期望的寬高比
if (mAdjustViewBounds) {
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
desiredAspect = (float) w / (float) h;
}
}
int pleft = mPaddingLeft;
int pright = mPaddingRight;
int ptop = mPaddingTop;
int pbottom = mPaddingBottom;
int widthSize;
int heightSize;
//如果寬或者高可以調整則進行調整,并且只能調整一個
if (resizeWidth || resizeHeight) {
// 獲取最大可能的寬度
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
// 獲取最大可能的高度
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
if (desiredAspect != 0.0f) {
// 查看實際的寬高比
float actualAspect = (float)(widthSize - pleft - pright) /
(heightSize - ptop - pbottom);
// 如果實際比例與期望比例差距小于0.0000001則不進行調整
if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
boolean done = false;
// 調整寬度去適應高
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
//是否允許調整寬度,即高度固定,另一個參數是和兼容性有關先不研究
if (!resizeHeight && !mAdjustViewBoundsCompat) {
//計算調整后的高度
widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
}
//調整高度符合規則,調整結束
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
}
}
// 調整高度,思路和寬度一致
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
if (!resizeWidth && !mAdjustViewBoundsCompat) {
heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
heightMeasureSpec);
}
if (newHeight <= heightSize) {
heightSize = newHeight;
}
}
}
}
} else {
// 如果不需要尺寸調整,只做常規的測量計算
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);
}
上面遇到了resolveAdjustedSize
resolveSizeAndState
之前沒遇到過,接下看看下這兩個方法。
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:
// 父容器沒有約束,如果期望值在最大值范圍內則取期望值
result = Math.min(desiredSize, maxSize);
break;
case MeasureSpec.AT_MOST:
// 父容器給出調整的最大值,這個值與期望值,設置的最大值之間取最小值
result = Math.min(Math.min(desiredSize, specSize), maxSize);
break;
case MeasureSpec.EXACTLY:
// 沒有選擇,直接返回父容器分配的值
result = specSize;
break;
}
return result;
}
resolveAdjustedSize
就是通過父容器分配的MeasureSpec
的約束下盡可能去取期望值。
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}
resolveSizeAndState
是父類View
中的一個靜態方法,和上面resolveAdjustedSize
差不多,也是根據父容器分配的MeasureSpec
的約束下盡可能去取要顯示Drawable
的寬高,也包括一些位操作用于表示狀態。
了解了原理我們可以結合實際情況分析一下。
(1)寬度設置為match_parent
高度設置為wrap_content
,第一張圖為adjustViewBounds
為 false
,第二張圖為true
match_parent
對應的specMode
為EXACTLY
,wrap_content
的specMode
為AT_MOST
,如果將adjustViewBounds
設置為true
則ImageView
的高度依據圖片的原有比例調整,否則ImageView
的高度為顯示圖片的高度。(2)寬度與高度設置均為
wrap_content
,無論adjustViewBounds
取值如何,效果一樣。wrap_content
的specMode
為AT_MOST
,從onMeasure
方法中可知,如果兩個尺寸都不定的話,是不會進行調整。(3)寬度與高度設置均為
match_parent
,無論adjustViewBounds
取值如何,效果也都一樣。wrap_content
的specMode
為EXACTLY
,所以說寬和高均不可變,也就是說不能調整,同理如果寬高設置為具體尺寸,adjustViewBounds
也是起不了作用的。
ImageView拓展
ImageView
是我們UI繪制過程一個必不可少的基礎控件,然而在實際開發過程中我們發現有些情況下ImageView
功能還不夠,所以我們會基于ImageView
基礎上去拓展,這里找了兩個小案例。
RoundImageView
現在很多應用中,頭像采用是圓形的,而ImageVeiw
只能顯示方形圖片,最簡單的方法是在加一層背景色遮罩,當然也可以通過自定View來解決。這是一個自定義控件用來顯示圓形圖片,當然現在也有很多圖片庫來顯示圓形頭像。具體代碼就不貼了,實現思路主要是繼承ImageView
,重寫OnDraw
,通過設置Xfermodes
(這里我理解為圖形疊加模式)在圓形上繪制圖片,達到繪制圓形圖像的效果。
public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) {
Bitmap scaledSrcBmp;
int diameter = radius * 2;
...
// 為了防止寬高不相等,造成圓形圖片變形,因此截取長方形中處于中間位置最大的正方形圖片
int bmpWidth = bmp.getWidth();
int bmpHeight = bmp.getHeight();
int squareWidth = 0, squareHeight = 0;
int x = 0, y = 0;
Bitmap squareBitmap;
if (bmpHeight > bmpWidth) {
squareWidth = squareHeight = bmpWidth;
x = 0;
y = (bmpHeight - bmpWidth) / 2;
squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
} else if (bmpHeight < bmpWidth) {
squareWidth = squareHeight = bmpHeight;
x = (bmpWidth - bmpHeight) / 2;
y = 0;
squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight);
} else {
squareBitmap = bmp;
}
//將正方形圖片縮放至與ImageView直徑相同
if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) {
scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter, diameter, true);
} else {
scaledSrcBmp = squareBitmap;
}
Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(),
scaledSrcBmp.getHeight(),
Bitmap.Config.ARGB_8888);
//獲取處理圖片的Canvas
Canvas canvas = new
Canvas(output);
Paint paint = new Paint();
Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight());
paint.setAntiAlias(true);//開啟抗鋸齒
paint.setFilterBitmap(true);//開啟過濾效果
paint.setDither(true);//開啟抖動
canvas.drawARGB(0, 0, 0, 0);
//繪制圓形
canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint);
//設置疊加模式為SRC_IN,即在圓形的返回內繪制圖像
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(scaledSrcBmp, rect, rect, paint);
//釋放內存
bmp = null;
squareBitmap = null;
scaledSrcBmp = null;
return output;
}
onDraw
繪制圓形圖片
@Override
protected void onDraw(Canvas canvas) {
...
Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius);
canvas.drawBitmap(roundBitmap, defaultWidth / 2
- radius, defaultHeight / 2
- radius, null);
}
AutoSquareImageView
這是我做Android項目遇到的第一個難題,四個ImageView
是要通過weight
屬性來實現水平方向上的寬度均等,但是UI要求是讓這個圖形按鈕正方形顯示,不同手機水平寬度不同,需要自適應高度,為了達到正方形效果需要自定義AutoSquareImageView
,修改思路其實很簡單,將ImageView
寬度通過weight
去獲取,高度設置為wrap_content
,重寫onMeasure
方法,計算正常流程下的寬與高,然后取大值達到滿足正方形圖的最小邊長。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
//保持長度大的一邊
if (measuredWidth > measuredHeight) {
setMeasuredDimension(measuredWidth, measuredWidth);
} else {
setMeasuredDimension(measuredHeight, measuredHeight);
}
}
總結
-
ImageView
顯示的圖片資源可以是通過XML屬性或者Java代碼指定顯示的圖片內容,可以通過設置Resource
、Uri
、Bitmap
對象、Drawable
對象來指定。 -
ScaleType
屬性的修改本質上根據設定的枚舉將變換賦值給mMatrix
,最終通過onDraw
中的矩陣操作實現。 -
cropToPadding
屬性可以讓圖片內容是在padding
基礎上進行變換(縮放,移動),根據實踐如果使用了mScrollX
mScrollY
屬性或者scaleType
設置為MATRIX
類型時會配合用到這個屬性。 -
adjustViewBounds
屬性主要是讓ImageView
根據圖片原有比例的約束下將調整大小,注意必須要限定住寬或者高,即將其中一個屬性設置為match_parent
或者確定尺寸。
筆記就寫到這里了,這也是我第一次在網上寫文章,寫的也是基礎知識,如果有什么不對的地方歡迎大家批評指正!寫文章也是為了能把學習的知識點進行梳理,然后也會有學習的動力,內容可能不高級,但希望能一點點慢慢提高。