【Android_View】ImageView源码简析笔记(二)
来源:互联网 发布:语音录入软件 编辑:程序博客网 时间:2024/06/06 00:36
ImageView源码简析
ImageView的测量–onMeasure()
总所周知,对于继承自View的控件来说,最重要的就是测量、布局与绘制三个步骤。当然,因为毕竟不是ViewGroup,当中很少考虑的子控件的位置问题,所以布局(Layout)的作用相对较小。下面,我们就一起来看看ImageView的上述几个步骤。
1、知识准备
在查看ImageView的源码之前,我们先一起简单的回顾一下有关Measure的3个知识点。
1.1 啥叫MeasureSpec
答:我们一般称之为【测量规格】。
首先,贴上google官方文档。
当然,这里是源码。嗯,仅仅是View的一个内部类。。。
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** @hide */ @IntDef({UNSPECIFIED, EXACTLY, AT_MOST}) @Retention(RetentionPolicy.SOURCE) public @interface MeasureSpecMode {} //定义3种【测量模式】 public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; //根据提供的尺寸[size]和模式[mode] 创建 [测量规格]。 public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } //如果测量模式为UNSPECIFIED,直接返回0. public static int makeSafeMeasureSpec(int size, int mode) { if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) { return 0; } return makeMeasureSpec(size, mode); } //获取测量模式 // MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。 public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } //获取测量值的大小 public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } //测量值 的 增量自适应 static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // 不需要适配 UNSPECIFIED 模式. return makeMeasureSpec(size, UNSPECIFIED); } size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +") spec: " + toString(measureSpec) + " delta: " + delta); //测量值不能为负数 size = 0; } //如测量值合理,则重新创建测量规格 return makeMeasureSpec(size, mode); } //输出测量规格 包含 mode+size 的信息 public static String toString(int measureSpec) { int mode = getMode(measureSpec); int size = getSize(measureSpec); StringBuilder sb = new StringBuilder("MeasureSpec: "); if (mode == UNSPECIFIED) sb.append("UNSPECIFIED "); else if (mode == EXACTLY) sb.append("EXACTLY "); else if (mode == AT_MOST) sb.append("AT_MOST "); else sb.append(mode).append(" "); sb.append(size); return sb.toString(); } }
官方对这个类的定义是:
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.
很明显,
* MeasureSpec封装了从父View传递给子View的布局要求。
* 每个MeasureSpec既包含表示宽度Width也包含高度Height。
* 每一个MeasureSpec由size(大小)和mode(模式)组成。
本质上,MeasureSpec是一个32位的int型数据。其中,其高2位表示mode,即【测量模式】,低30位表示size,即【测量值的大小】。
测量值的大小不需要多说,我们一起看看这个【测量模式】:
需要在再次强调一点的是,这个任何View的MeasureSpec的测量模式,是受父布局影响的。
当然,有一种情况可以看作是例外。当子View在定义宽、高时直接应用了某些具体的数值,例如20dp、30px…,此时此刻无论父布局的【测量模式】是什么,最终子View的【测量值】都会直接无视之,而直接设为先前的定义值(20dp、30px…)。
其实这仅仅是定义指出的一种特殊情况,也是官方定义的合情合理的实际。
一篇很强大的大神的总结:奉上:大神原文
1.2 LayoutParams又是个啥
我们知道子View的MeasureSpec受到【父布局的MeasureSpec】 与 【自身LayoutParams】的双重影响。前面看完了MeasureSpec,那么接下来我们在看一看LayoutParams。
LayoutParams是ViewGroup中的一个静态内部类。源码太长,这里就不贴出了。
public static class LayoutParams { ...}
官方注释的第一句是:子View通过LayoutParams参数告诉它的父布局自己的【布局意愿】和【期望的布局方式】。再简单点,就是告诉它的布局老爹,想要怎么被放置以及自己的宽高。
这个布局意愿实际上包括了三个方面:
* 布局的位置
* 宽
* 高
这个LayoutParams实际上我们大家经常用到,只不过隐藏的太深,没感觉。。。
举个栗子:
我们在xml文件里新建控件的时候,一般都是这样的:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.mario.myviewdemo.view.myPathView android:id="@+id/demo_view" android:layout_height="match_parent" android:layout_width="match_parent"/></LinearLayout>
是不是很眼熟?是不是经常用??是不是觉得也没啥特别的呢???
那么再多看一眼啊:
为什么在具体控件的定义当中,我们还要写上【layout_height】和【layout_width】呢?
前缀–layout,他是不是很显眼!
是的。在我们定义的xml文件中,所有前缀为layout的属性,实际上都不属于当前View而直接属于LayoutParams。比如说这些:
在上述例子当中,我们的父控件是LinearLayout,因此子View中所有的layout_XXX属性,都要考虑LinearLayout的特点,同样的道理,如果保持其余不变的前提下只更改父布局的类型,如RelativeLayout等,那么子View最终显示的时候还需要考虑RelativeLayout的特点。
好了,相应的细节我们在以后的文章中再来分析。
1.3 那么合起来呢
有了MeasureSpec和LayoutParams,我们的子VIew就比较容易的测量出自己的宽高了。
粗略的回顾了一下相关的基础知识与定义,具体的解释我们会在以后的文章中再详谈。接下来我们开始对本次的主角进行“解剖”吧。
2、onMeasure()
2、1 综述
我们来看ImageView中onMeasure()方法的全部代码:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { resolveUri(); int w; int h; //视图内容期望的宽高比(不包含边界) float desiredAspect = 0.0f; //是否允许改变view's width boolean resizeWidth = false; //是否允许改变view's height boolean resizeHeight = false; //通过传入的宽、高的【测量规格】获取相应的【测量模式】 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (mDrawable == null) { // 如果当前显示的图像资源为空,则固有尺寸设为0 mDrawableWidth = -1; mDrawableHeight = -1; w = h = 0; } else { //不为空则设置为当前图像的宽、高 w = mDrawableWidth; h = mDrawableHeight; //容错--不能为0 if (w <= 0) w = 1; if (h <= 0) h = 1; // 调整视图的边界以匹配资源图像的宽高比。 if (mAdjustViewBounds) { //如果当前测量模式 != EXACTLY(精确测量模式)==> 宽高可变 resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; //计算宽高比 desiredAspect = (float) w / (float) h; } } //距离周围的padding值 final int pleft = mPaddingLeft; final int pright = mPaddingRight; final int ptop = mPaddingTop; final 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) { // 计算获得实际的宽高比(注意减掉边距) final float actualAspect = (float)(widthSize - pleft - pright) /(heightSize - ptop - pbottom); //如果两者的绝对值相差大于0.0000001 if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { boolean done = false; // 调整 width 获得合适的height以符合宽高比 if (resizeWidth) { int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +pleft + pright; // 如果height固定,则允许width超过其原始估计值 if (!resizeHeight && !sCompatAdjustViewBounds) { widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); } //重新设置width if (newWidth <= widthSize) { widthSize = newWidth; done = true; } } // 如果,没有重新设置Width,则调整Height以适应宽高比,步骤同上。 if (!done && resizeHeight) { int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom; if (!resizeWidth && !sCompatAdjustViewBounds) { 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); }
一眼略过前面的代码,WQ,是不是感觉代码略长呢?没事,我们一起慢慢分析。
首先传入的形参widthMeasureSpec和heightMeasureSpec分别用来描述宽度测量规范和高度测量规范。通过前面的回顾,我们也知道,这个测量规范采用了一个int值来表示,分为两个部分:mode+size即高两位表示mode(测量模式),其余30位表示size(测量大小)。(再次“化肥”一遍…)
2.2 第一个方法
可以看到,方法体内部首先执行的是resolveUri()方法。我们点击进去,发现代码如下:
private void resolveUri() { if (mDrawable != null) { return; } if (getResources() == null) { return; } Drawable d = null; if (mResource != 0) { try { d = mContext.getDrawable(mResource); } catch (Exception e) { Log.w(LOG_TAG, "Unable to find resource: " + mResource, e); // Don't try again. mUri = null; } } else if (mUri != null) { d = getDrawableFromUri(mUri); if (d == null) { Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri); // Don't try again. mUri = null; } } else { return; } updateDrawable(d); }
如名称所示,这个方法主要是根据Uri实现图片加载的。可以看到,在在判断完成相关变量是否为空后,resolveUri()方法主要做了两件事,一是在成员变量mUri不为空的情况下,调用getDrawableFromUri(mUri)方法加载图像,获得Drawable对象。二是将获得的对象设为ImageView的显示资源。
首先,getDrawableFromUri(mUri)代码如下:
private Drawable getDrawableFromUri(Uri uri) { final String scheme = uri.getScheme(); if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { try { // Load drawable through Resources, to get the source density information ContentResolver.OpenResourceIdResult r = mContext.getContentResolver().getResourceId(uri); return r.r.getDrawable(r.id, mContext.getTheme()); } catch (Exception e) { Log.w(LOG_TAG, "Unable to open content: " + uri, e); } } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) || ContentResolver.SCHEME_FILE.equals(scheme)) { InputStream stream = null; try { stream = mContext.getContentResolver().openInputStream(uri); return Drawable.createFromResourceStream(sCompatUseCorrectStreamDensity ? getResources() : null, null, stream, null); } catch (Exception e) { Log.w(LOG_TAG, "Unable to open content: " + uri, e); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { Log.w(LOG_TAG, "Unable to close content: " + uri, e); } } } } else { return Drawable.createFromPath(uri.toString()); } return null; }
可以看到,在这个方法中,一共有两处可以返回Drawable资源。
一是通过InputStream,即Drawable.createFromResourceStream()处;
一是通过URI,即Drawable.createFromPath()处。
这两个方法都是Drawable类中的方法,前者应用了输入流,后者应用了统一资源标识符。
对于
Drawable.createFromResourceStream(Resources res, TypedValue value,InputStream is, String srcName)
方法,此处应用了Resources资源加载,我们回看上一篇文章中的initImageView()方法会发现这样一句
sCompatUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
即当前编译环境 > Android 6.0(23)时,赋值号左侧的成员变量会为true,否则为false。
而在本方法中,可以知道如果当前的系统版本号低于23,Resource会被设定成null,反之如果当前的系统版本号高于23,则会通过getResources()获取resource对象后进行设置。注意此处调用的是View中的方法而不是Drawable类自有的方法,代码如下:
/** * Returns the resources associated with this view. * * @return Resources object. */ public Resources getResources() { return mResources; }
如果获得的Drawable对象不为空,则继续调用updateDrawable()方法,将其赋值给成员变量mDrawable。很明显,这就是当前ImageView将要显示的图像。
对于第二种方式,通过源码可以发现,主要调用了Drawable类中的createFromPath(String pathName)方法,很明显,这里是通过将Uri转换为文件名称后,在内部再次调用了Drawable类中的drawableFromBitmap()方法获取一个Drawable对象,调用关系如下:
首先是createFromPath方法
public static Drawable createFromPath(String pathName) { ... Bitmap bm = BitmapFactory.decodeFile(pathName); if (bm != null) { return drawableFromBitmap(null, bm, null, null, null, pathName); ... }
紧接着是内部调用的drawableFromBitmap()方法:
private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np, Rect pad, Rect layoutBounds, String srcName) { if (np != null) { return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName); } return new BitmapDrawable(res, bm); }
通过上述方法得到Drawable对象后,再调用updateDrawable()方法,将得到的Drawable设置为ImageView的显示资源,即赋值给成员变量mDrawable。代码如下:
private void updateDrawable(Drawable d) { if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) { mRecycleableBitmapDrawable.setBitmap(null); } boolean sameDrawable = false; if (mDrawable != null) { sameDrawable = mDrawable == d; mDrawable.setCallback(null); unscheduleDrawable(mDrawable); if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) { //传入false,设置mDrawable不可见以及不强制重新启动(绘制) mDrawable.setVisible(false, false); } } //Drawable赋值 mDrawable = d; if (d != null) { d.setCallback(this); //设置布局方向 d.setLayoutDirection(getLayoutDirection()); if (d.isStateful()) { //设置Drawable状态 d.setState(getDrawableState()); } if (!sameDrawable || sCompatDrawableVisibilityDispatch) { final boolean visible = sCompatDrawableVisibilityDispatch ? getVisibility() == VISIBLE : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();//通过之前的InitImageView()方法可以知道,如果当前系统小于24,则sCompatDrawableVisibilityDispatch变量为true,在此情况下,局部变量visable的值仅与当前控件的VISABLE属性有关系。而当系统版本大于24时,则需要综合考虑【view是否已经依赖于窗口Window】、【当前Window的可见情况】以及【所有当前视图全部父View的可见度】。 d.setVisible(visible, true); } d.setLevel(mLevel); mDrawableWidth = d.getIntrinsicWidth(); mDrawableHeight = d.getIntrinsicHeight(); applyImageTint(); applyColorMod(); configureBounds(); } else { mDrawableWidth = mDrawableHeight = -1; } }
applyImageTint()的代码如下所示,我们可以知道,这个方法主要是用来设置与着色Tint有关的属性。分别代表了绘制到屏幕之前Drawable的颜色状态列表与色调模式。
很明显主要有两个:TintList与TintMode。
其中mDrawableTintList与mDrawableTintMode都是在ImageView的构造器中赋初值,具体代码可以查阅ImageView源码解析笔记(一)。
private void applyImageTint() { if (mDrawable != null && (mHasDrawableTint || mHasDrawableTintMode)) { mDrawable = mDrawable.mutate(); if (mHasDrawableTint) { mDrawable.setTintList(mDrawableTintList); } if (mHasDrawableTintMode) { mDrawable.setTintMode(mDrawableTintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (mDrawable.isStateful()) { mDrawable.setState(getDrawableState()); } } }
applyColorMod()代码如下。可以看到,本方法主要是设置Drawable的颜色过滤器、图像混合模式、透明度。
private void applyColorMod() { if (mDrawable != null && mColorMod) { mDrawable = mDrawable.mutate(); if (mHasColorFilter) { mDrawable.setColorFilter(mColorFilter); } mDrawable.setXfermode(mXfermode); mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); } }
上述方法中还需要特别注意的一点是
mDrawable = mDrawable.mutate();
这一句。其中mutate方法代码如下:
public @NonNull Drawable mutate() { return this; }
通过此方法获得的Drawable对象是【可变的】。这意味着这个对象可以接受设置各种属性,并且保证不会与其余Drawable对象【共享状态】且这种【可变】状态不可逆。
我们再来看updateDrawable中执行的最后一个方法configureBounds()。该方法属于ImageView,源代码如下:
private void configureBounds() { if (mDrawable == null || !mHaveFrame) { return; } //这里的值指的是Drawable的值,与imageview无关 final int dwidth = mDrawableWidth; final int dheight = mDrawableHeight; //这里指的是ImageView的宽、高 final int vwidth = getWidth() - mPaddingLeft - mPaddingRight; final int vheight = getHeight() - mPaddingTop - mPaddingBottom; final boolean fits = (dwidth < 0 || vwidth == dwidth) && (dheight < 0 || vheight == dheight); if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { //裁掉Drawable的超出的边界,以Imageview的宽高赋值 mDrawable.setBounds(0, 0, vwidth, vheight); mDrawMatrix = null; } else { mDrawable.setBounds(0, 0, dwidth, dheight); 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) { 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 { // 创建所需要的转换 mTempSrc.set(0, 0, dwidth, dheight); mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix; mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); } } }
首先通过方法名,我们可以知道,这个方法主要是“配置边界信息”。而实际上,在本方法中主要是操作了mDrawMatrix矩阵,便于以后在onDraw()方法中使用。
可以看到:如果当前drawable没有固定的尺寸或者缩放模式为fit_XY时,我们需要做的是直接将试图充满整个View(即左上坐标为(0,0),右下角坐标为(view的宽高-边界长度))。
除此之外,便需要自己做相应的缩放操作以适应图片原生的宽高。很明显,因为要缩放,所以这里多次调用了缩放矩阵:
private Matrix mMatrix;
在得到需要缩放的坐标矩阵后,我们将mMatrix赋值给mDrawMatrix,然后调用mDrawMatrix的相应方法,如setScale()、setTranslate()等进行矩阵操作以达到相应的图像转换要求。
这里有一个大前提:
ImageView中所有的图像转换是基于【图像矩阵】进行的。
接下来:
我们来看一个多次被调用的方法:setBounds()。首先需要明确的是:这是Drawable类中的方法。
public void setBounds(int left, int top, int right, int bottom) { Rect oldBounds = mBounds; if (oldBounds == ZERO_BOUNDS_RECT) { oldBounds = mBounds = new Rect(); } if (oldBounds.left != left || oldBounds.top != top || oldBounds.right != right || oldBounds.bottom != bottom) { if (!oldBounds.isEmpty()) { invalidateSelf(); } //重新设置边界 mBounds.set(left, top, right, bottom); onBoundsChange(mBounds); } }
官方的注释告诉我们,这个方法主要是为Drawable绘制一个边界矩形,并且是在Drawable的draw()方法被调用时绘制。可以看到,当原来的边界矩形坐标与当前传进的坐标不相同时,随即调用invalidateSelf()放方法进行重绘,然后将当前的边界矩形的坐标设置为传入值。
注意,mBounds是Drawable类中的成员变量,
private Rect mBounds = ZERO_BOUNDS_RECT;
而赋值号的右侧为该类之前定义好的一个Rect坐标:
private static final Rect ZERO_BOUNDS_RECT = new Rect();
还有要注意的一个点是onBoundsChange(mBounds)方法。
protected void onBoundsChange(Rect bounds) { // Stub method. }
注意这个方法是需要在子类中Override(复写)的。本身不具有相应的实现。
截止到这里,我们一起大概的看完了onMeasure()中执行的第一个方法—resolveUri()。接下来继续吧:
2.2其他方法
计算可以获得的最大(宽、高)尺寸:resolveAdjustedSize()方法:
private int resolveAdjustedSize(int desiredSize, int maxSize, int measureSpec) { int result = desiredSize; //根据测量规格,获取测量值与测量模式 final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: /* 未指定模式。父布局明确子View可以获得想要的尺寸,且只要不超过最大值则不做限制 */ //只要不超过最大值就可以 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; }
可以看到,传入的形参分别为【期望得到的测量值】【最大值】【测量规格】。这个方法,主要是根据传入形参的不同,最终根据不同的测量模式,获取不同的测量值,测量模式的介绍见【前文的知识回顾】。
获取最小建议宽度。注意这个方法是VIew类中的方法。
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
注意返回值中Drawable类中的getMinimumWidth()方法:
public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; }
注意:上述方法返回此Drawable建议的最小宽度。
如果视图使用此Drawable作为背景,则建议View至少使用此值作为其宽度。如果此Drawable没有建议的最小宽度,则返回0。
再来看:resolveSizeAndState()方法:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
我们看看该方法的形参列表:
很明显,三个参数分别代表了view的尺寸、测量规格、子View的测量状态。
通过代码很明显可以看出,最终的返回值result实际上就是传入的参数size在不同测量模式Mode下的结果。
最后,需要特别注意的是,ImageView在完成自身的宽、高测量后,必须调用继承得到的父视图View的setMeasuredDimension()方法,通知自己需要设置的宽高。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
This method must be called by {@link #onMeasure(int, int)} to store the measured width and measured height. Failing to do so will trigger an exception at measurement time
通过上述源码注释,我们可以知道,这个方法是【必须调用】的,否则会触发IllegalStateException(无效状态)异常。
2.3小结
好了,到这里我们也算初步的对ImageView.onMeasure()有了一点自己的简介,那么趁热打铁,再来回顾一下整个测量过程:
- 【Android_View】ImageView源码简析笔记(二)
- 【Android_View】ImageView源码简析笔记(一)
- 【Android_View】ImageView源码简析笔记(三)
- 【Android_View】ImageView源码简析笔记(四)
- 【Android_View】ImageView源码简析笔记(五)
- android_view
- android_view
- Android_VIEW
- 安卓学习笔记(二)ImageView
- android_View Animation
- Android_View动画
- Android_View详解
- STL源码剖析笔记二
- FastDFS源码阅读笔记(二)
- Axel源码阅读笔记<二>
- nginx 源码学习笔记(二)
- libevent源码阅读笔记(二)
- memcached源码阅读笔记(二)
- 贴之道-别生气了,会伤害乳房
- POJ3318:Matrix Multiplication(矩阵相乘)
- Splay基本操作和线段树(指针版)
- go在windows上环境搭建
- Node.js ORM框架Sequlize之表间关系
- 【Android_View】ImageView源码简析笔记(二)
- POJ 2828 Buy Tickets【线段树单点更新+逆序遍历】【经典题】【模板题】
- 如何访问函数内部的变量
- HDU 6082 度度熊与邪恶大魔王 DP
- 在CentOS 7 下安装.Net 框架
- Android 使用反射调用自定义AIDL **.Stub.asInterface(IBinder obj)、反射实现关机shutdown
- 机器学习:偏差、方差与欠拟合、过拟合
- 递归学习简单的小例子之hanoi塔问题
- gRPC 源码编译安装