> 文章列表 > NumberPicker分析(二)

NumberPicker分析(二)

NumberPicker分析(二)

NumberPicker分析(二)

NumberPicker继承自LinearLayout。一般而言,无论是继承自View,还是继承自ViewGroup,必然会经过如下的几个阶段:

  1. onMeasure
  2. onLayout
  3. onDraw

onMeasure

onMeasure方法测量当前控件大小,为正式布局提供建议。测量完成以后,要通过 setMeasuredDimen ion(int,int)函数设置给系统
NumberPicker中的构造方法中,通过调试断点可知(结合上一节中的Widget.Holo.NumberPickerstyle也可知),初始化时:

  • mMinHeight没有设置值(默认为-1, 表示SIZE_UNSPECIFIED
  • mMaxHeight有设置值(在本人测试机器上为495
  • mMinWidth有设置值(在本人测试机器上为176
  • mMaxWidth没有设置值(默认为-1, 表示SIZE_UNSPECIFIED

之后在tryComputeMaxWidth()方法中,给mMaxWidth又设置了值,mMaxWidth = mMinWidth
此时mMaxWidthmMaxHeight就都有值了

NumberPickeronMeasure方法如下:

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (!mHasSelectorWheel) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);return;}// Try greedily to fit the max width and height./* 1.widthMeasureSpec是父View传递给当前View的一个建议值* 2.mMaxWidth = 176*/final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);/* 1.heightMeasureSpec是父View传递给当前View的一个建议值* 2.mMaxHeight = 495*/final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);// Flag if we are measured with width or height less than the respective min.// 在这里还需要考虑最小值限制的问题/* 1.mMinWidth = 176, getMeasuredWidth() = 176* 2.按resolveSizeAndStateRespectingMinSize方法,需要创建一个新的widthMeasureSpec*/final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(),widthMeasureSpec);/* 1.mMinHeight = -1, getMeasuredWidth() = 495* 2.按resolveSizeAndStateRespectingMinSize方法,直接返回heightMeasureSpec*/        final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(),heightMeasureSpec);setMeasuredDimension(widthSize, heightSize);}

makeMeasureSpec方法和resolveSizeAndStateRespectingMinSize方法,定义如下:

    /* Makes a measure spec that tries greedily to use the max value. @param measureSpec The measure spec.* @param maxSize The max value for the size.* @return A measure spec greedily imposing the max size.*/private int makeMeasureSpec(int measureSpec, int maxSize) {if (maxSize == SIZE_UNSPECIFIED) {return measureSpec;}final int size = MeasureSpec.getSize(measureSpec);final int mode = MeasureSpec.getMode(measureSpec);switch (mode) {case MeasureSpec.EXACTLY:return measureSpec;case MeasureSpec.AT_MOST:return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);case MeasureSpec.UNSPECIFIED:return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);default:throw new IllegalArgumentException("Unknown measure mode: " + mode);}}/* Utility to reconcile a desired size and state, with constraints imposed* by a MeasureSpec. Tries to respect the min size, unless a different size* is imposed by the constraints. @param minSize The minimal desired size.* @param measuredSize The currently measured size.* @param measureSpec The current measure spec.* @return The resolved size and state.*/private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize, int measureSpec) {if (minSize != SIZE_UNSPECIFIED) {final int desiredWidth = Math.max(minSize, measuredSize);return resolveSizeAndState(desiredWidth, measureSpec, 0);} else {return measuredSize;}}

在layout布局中,设置layout_width="wrap_content" , layout_height="wrap_content",相当于是 MeasureSpec.AT_MOST,所以最终调用的都是如下的方法:

MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY)

width
height

onLayout

onLayout布局所有的子控件
NumberPickeronLayout方法主要用来:

  • 居中布局EditTextmInputText
  • 初始化选择轮 - initializeSelectorWheel
  • 初始换边缘Fading效果 - initializeFadingEdges
    @Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {if (!mHasSelectorWheel) {super.onLayout(changed, left, top, right, bottom);return;}final int msrdWdth = getMeasuredWidth();final int msrdHght = getMeasuredHeight();// Input text centered horizontally.final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();final int inptTxtMsrdHght = mInputText.getMeasuredHeight();final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);if (changed) {// need to do all this when we know our sizeinitializeSelectorWheel();initializeFadingEdges();//分割线位置mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2- mSelectionDividerHeight;mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight+ mSelectionDividersDistance;}}

initializeSelectorWheel方法

initializeSelectorWheel方法中,计算和初始化了许多值
initializeSelectorWheel
其中的关系,可以表示如下:
关系
并且在这里设置了mCurrentScrollOffset = mInitialScrollOffset;

onDraw

NumberPicker的构造方法中有如下的设置:

        // By default Linearlayout that we extend is not drawn. This is// its draw() method is not called but dispatchDraw() is called// directly (see ViewGroup.drawChild()). However, this class uses// the fading edge effect implemented by View and we need our// draw() method to be called. Therefore, we declare we will draw.setWillNotDraw(!mHasSelectorWheel);

从前面的文章可知,mHasSelectorWheelture,所以这里相当于是setWillNotDraw(false)

setWillNotDraw

public void setWillNotDraw (boolean willNotDraw)

If this view doesn’t do any drawing on its own, set this flag to allow further optimizations. By default, this flag is not set on View, but could be set on some View subclasses such as ViewGroup. Typically, if you override onDraw(android.graphics.Canvas) you should clear this flag.
如果这个视图自己不做任何绘图,设置这个标志以允许进一步优化。 默认情况下,此标志未在 View 上设置,但可以在某些 View 子类(例如 ViewGroup)上设置。 通常,如果您覆盖 onDraw(android.graphics.Canvas) 您应该清除此标志。

设置setWillNotDraw(false)后,ViewGrouponDraw方法就可以被调用了

具体的onDraw方法如下:

    @Overrideprotected void onDraw(Canvas canvas) {....float x = (mRight - mLeft) / 2;float y = mCurrentScrollOffset;// draw the virtual buttons pressed state if needed....// draw the selector wheelint[] selectorIndices = mSelectorIndices;for (int i = 0; i < selectorIndices.length; i++) {int selectorIndex = selectorIndices[i];String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);// Do not draw the middle item if input is visible since the input// is shown only if the wheel is static and it covers the middle// item. Otherwise, if the user starts editing the text via the// IME he may see a dimmed version of the old value intermixed// with the new one.if ((showSelectorWheel && i != SELECTOR_MIDDLE_ITEM_INDEX) ||(i == SELECTOR_MIDDLE_ITEM_INDEX && mInputText.getVisibility() != VISIBLE)) {//绘制文字canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);}y += mSelectorElementHeight;}// draw the selection dividers // 绘制分割线if (showSelectorWheel && mSelectionDivider != null) {// draw the top dividerint topOfTopDivider = mTopSelectionDividerTop;int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);mSelectionDivider.draw(canvas);// draw the bottom dividerint bottomOfBottomDivider = mBottomSelectionDividerBottom;int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);mSelectionDivider.draw(canvas);}}

概括来说就是遍历mSelectorIndices(第一次值为[4, 0, 1]),绘制text,绘制分割线。注意这里的y

float y = mCurrentScrollOffset; //初始值
...
y += mSelectorElementHeight; //每次循环后

结合上面的示意图,即可明白其中的含义