NumberPicker分析(二)

文章详细分析了Android中的NumberPicker组件的生命周期,特别是onMeasure、onLayout和onDraw方法的执行过程。在onMeasure阶段,NumberPicker根据其最大和最小尺寸建议调整大小;onLayout阶段负责子视图的布局,包括输入文本的居中以及初始化选择轮和边缘渐变效果;onDraw阶段则涉及选择器轮的文字绘制和分割线的显示。文章还探讨了Fade效果的实现及其相关设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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方法如下:

    @Override
    protected 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
    @Override
    protected 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 size
            initializeSelectorWheel();
            initializeFadingEdges();
            //分割线位置
            mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
                    - mSelectionDividerHeight;
            mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
                    + mSelectionDividersDistance;
        }
    }

initializeSelectorWheel方法

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

initializeFadingEdges

initializeFadingEdges方法设置竖直方向上的Fade效果

    private void initializeFadingEdges() {
        setVerticalFadingEdgeEnabled(true);
        setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
    }

如果要Fade效果有效,还需要设置getSolidColor

    @Override
    public int getSolidColor() {
        return mSolidColor;
    }

Fade效果其它相关设置:

    @Override
    protected float getTopFadingEdgeStrength() {
        return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
    }
    @Override
    protected float getBottomFadingEdgeStrength() {
        return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
    }

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方法如下:

    @Override
    protected void onDraw(Canvas canvas) {
      	....
        float x = (mRight - mLeft) / 2;
        float y = mCurrentScrollOffset;

        // draw the virtual buttons pressed state if needed
        ....

        // draw the selector wheel
        int[] 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 divider
            int topOfTopDivider = mTopSelectionDividerTop;
            int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
            mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
            mSelectionDivider.draw(canvas);

            // draw the bottom divider
            int 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; //每次循环后

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值