LinearLayout onMeasure源码阅读

onMeasure()方法解读
measureWithLargestChild 作用 : 该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸; 且只有当父view布局方向上的宽度或高度为wrap_content才有效

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  // 根据VERTICAL或者HORIZONTAL 分别不同的去计算相应布局
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

跟踪measureVertical()VERTICAL垂直方向的看看
自定义View的onMeasure()方法中,最后要记得调用setMeasuredDimension()更新你计算好的大小信息等

    /**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #VERTICAL}.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int)
     */
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        mTotalLength = 0;   //记录总长度
        int maxWidth = 0;  //最大宽
        int childState = 0;  //子View的状态
        int alternativeMaxWidth = 0; //改变源生的最大宽度
        int weightedMaxWidth = 0;  //加权最大宽度
        boolean allFillParent = true; //填充满父布局
        float totalWeight = 0; //总加权数

        final int count = getVirtualChildCount(); //获取子view的数量

        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    //部分节点标识
        boolean matchWidth = false;
        boolean skippedMeasure = false;

        final int baselineChildIndex = mBaselineAlignedChildIndex;
        final boolean useLargestChild = mUseLargestChild;

        int largestChildHeight = Integer.MIN_VALUE;
        int consumedExcessSpace = 0;//消耗多余的控件

        int nonSkippedChildCount = 0; //没有跳过测量的孩子的总数

        // See how tall everyone is. Also remember max width.
        //开始遍历  看到每个控件的高度都是多高和还记得最大宽度
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
      //如果为空 则加0 measureNullChild()返回的是 0
            if (child == null) {
                mTotalLength += measureNullChild(i);
                continue;
            }
      //如果为不可见 则i 加0 
            if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
        //除去上面的判断条件
       // 下面的为没有跳过的子布局控件
            nonSkippedChildCount++;
      //hasDividerBeforeChildAt 查看又没有divider分割线在子控件前面
            if (hasDividerBeforeChildAt(i)) { 
                mTotalLength += mDividerHeight;
            }
          //获取控件的属性信息  
            final LayoutParams lp =(LayoutParams)child.getLayoutParams();
    //下面开始根据测量模式进行逻辑运算
        //将weight信息提出来加到totalWeight 
            totalWeight += lp.weight;
      //如果高度为0 且设置了比重weight useExcessSpace= true
            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        // 高度为精确 且使用了比重
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) { 
               
//优化:不用费心测量那些只使用多余空间(比重)的孩子。如果我们有空间分配,这些视图将在稍后测量。
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true; //这里设置跳过测量的标识符
            } else {
              
                if (useExcessSpace) {
  //高度模式不是UNSPECIFIED就是AT_MOST,并且这个孩子只是用多余的空间布置的。测量使用wrap_content以便我们可以找到视图的最佳高度。我们将恢复0的原始高度测量后
                    lp.height = LayoutParams.WRAP_CONTENT;
                }
                //不使用比重的情况
                //确定这个孩子想要多大。如果这个或以前的孩子已经给了一个比重,那么我们允许它使用所有可用的空间(如果需要,我们将在以后缩小东西)。
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                //在layout之前测量子控件
              measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    //恢复原来的高度和记录我们分配多少空间excess-only孩子,这样我们可以匹配精确测量的行为。
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                if (useLargestChild) {
                    largestChildHeight = Math.max(childHeight, largestChildHeight);
                }
            }

            /**
             * If applicable, compute the additional offset to the child's baseline
             * we'll need later when asked {@link #getBaseline}.
             */
            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
               mBaselineChildTop = mTotalLength;
            }

            // if we are trying to use a child index for our baseline, the above
            // book keeping only works if there are no children above it with
            // weight.  fail fast to aid the developer.
            if (i < baselineChildIndex && lp.weight > 0) {
                throw new RuntimeException("A child of LinearLayout with index "
                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
                        + "won't work.  Either remove the weight, or don't set "
                        + "mBaselineAlignedChildIndex.");
            }

            boolean matchWidthLocally = false;
            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
                // The width of the linear layout will scale, and at least one
                // child said it wanted to match our width. Set a flag
                // indicating that we need to remeasure at least that view when
                // we know our width.
                matchWidth = true;
                matchWidthLocally = true;
            }

            final int margin = lp.leftMargin + lp.rightMargin;
            final int measuredWidth = child.getMeasuredWidth() + margin;
            maxWidth = Math.max(maxWidth, measuredWidth);
            childState = combineMeasuredStates(childState, child.getMeasuredState());

            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
  //计算比重的大小
            if (lp.weight > 0) {
                /*
                 * Widths of weighted Views are bogus if we end up
                 * remeasuring, so keep them separate.
                 */
                weightedMaxWidth = Math.max(weightedMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            } else {
                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);
            }

            i += getChildrenSkipCount(child, i);
        }
    //将分割器的高度也加到总高度中
        if (nonSkippedChildCount > 0 && hasDividerBeforeChildAt(count)) {
            mTotalLength += mDividerHeight;
        }
    //这个是重新计算一下总高度信息
// useLargestChild 作用 : 该属性为true的时候, 所有带权重的子元素都会具有最大子元素的最小尺寸; 且只有当父view布局方向上的宽度或高度为wrap_content才有效(网上说的 我并没有验证)
        if (useLargestChild &&
                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
            mTotalLength = 0;

            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }

                if (child.getVisibility() == GONE) {
                    i += getChildrenSkipCount(child, i);
                    continue;
                }

                final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                        child.getLayoutParams();
                // Account for negative margins
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }
        }

        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
          //要么扩大儿童比重可用空间或缩小他们是否超出我们目前的界限。如果我们跳过测量任何孩子,现在我们需要衡量他们。
// 剩余可用空间
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
        if (skippedMeasure
                || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)) {
            float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

            mTotalLength = 0;
    //这里是第二次测量控件的大小
            for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
                if (child == null || child.getVisibility() == View.GONE) {
                    continue;
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final float childWeight = lp.weight;
                if (childWeight > 0) {
                    final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
                    remainingExcess -= share;
                    remainingWeightSum -= childWeight;

                    final int childHeight;
                    if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
                        childHeight = largestChildHeight;
                    } else if (lp.height == 0 && (!mAllowInconsistentMeasurement
                            || heightMode == MeasureSpec.EXACTLY)) {
                        // This child needs to be laid out from scratch using
                        // only its share of excess space.
                        childHeight = share;
                    } else {
                        // This child had some intrinsic height to which we
                        // need to add its share of excess space.
                        childHeight = child.getMeasuredHeight() + share;
                    }

                    final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.max(0, childHeight), MeasureSpec.EXACTLY);
                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                            lp.width);

                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Child may now not fit in vertical dimension.
                    childState = combineMeasuredStates(childState, child.getMeasuredState()
                            & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                }

                final int margin =  lp.leftMargin + lp.rightMargin;
                final int measuredWidth = child.getMeasuredWidth() + margin;
                maxWidth = Math.max(maxWidth, measuredWidth);

                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                        lp.width == LayoutParams.MATCH_PARENT;

                alternativeMaxWidth = Math.max(alternativeMaxWidth,
                        matchWidthLocally ? margin : measuredWidth);

                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            }

            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
            // TODO: Should we recompute the heightSpec based on the new total length?
        } else {
            alternativeMaxWidth = Math.max(alternativeMaxWidth,
                                           weightedMaxWidth);


            // We have no limit, so make all weighted views as tall as the largest child.
            // Children will have already been measured once.
            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
                for (int i = 0; i < count; i++) {
                    final View child = getVirtualChildAt(i);
                    if (child == null || child.getVisibility() == View.GONE) {
                        continue;
                    }

                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();

                    float childExtra = lp.weight;
                    if (childExtra > 0) {
                        child.measure(
                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                        MeasureSpec.EXACTLY),
                                MeasureSpec.makeMeasureSpec(largestChildHeight,
                                        MeasureSpec.EXACTLY));
                    }
                }
            }
        }

        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
            maxWidth = alternativeMaxWidth;
        }

        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);

        if (matchWidth) {
            forceUniformWidth(count, heightMeasureSpec);
        }
    }

measureChildBeforeLayout()方法

  /**
    * child :当前要测量的子控件
    * totalWidth 父布局中已经被其它子控件使用的宽度
    * childIndex:当前子控件存在的下标
    * widthMeasureSpec:父控件的宽度大小参数
    * heightMeasureSpec:父控件的高度大小参数
   * totalHeight 已经使用的高度
  **/
   void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

measureChildWithMargins()

/**
     *  child :当前要测量的子控件
     *  parentWidthMeasureSpec:父控件的宽度大小参数
     *  widthUsed :父布局中已经被其它子控件使用的宽度
     *  parentHeightMeasureSpec :父控件的高度大小参数
     *  heightUsed :父布局中已经被其它子控件使用的高度
     */
    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //计算获取控件的测量值
     //传入( 父控件的宽度测量值,父控件的padding左右值+Margin左右值+加上已经被使用的宽度值,本控件的宽度)
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
      //计算出子控件的大小信息 并交给子控件
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

getChildMeasureSpec(int spec, int padding, int childDimension) 计算出子控件的测量参数

  /**  spec :父控件的测量信息
     * padding : 父控件padding+这个子控件的margin的信息
     * childDimension : 子控件的需要的测量大小
     **/ 
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
      //获取父控件的测量信息 --大小跟测量模式
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //剩下的空间( 父控件的最大值 -  已经用去的控件)
        int size = Math.max(0, specSize - padding);
        //待返回的结果的测量大小跟测量模式信息
        int resultSize = 0;
        int resultMode = 0;
    //  接下来重点!!!! 根据父控件的测量模式 对应求出对应的大小值
        switch (specMode) {
      // 精确模式的时候
        case MeasureSpec.EXACTLY:
        //  子控件大小>=0  
            if (childDimension >= 0) { 
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //给最大值 填充满
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子控件不能大于父控件的剩余空间
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父母对我们实施了一个最大尺寸
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                //子控件想要一个特定的大小……所以要把它赋值进去
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                //子空间想要我们的规模,但我们的大小不是固定的.将剩余的空间都给予它
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //这个跟MATCH_PARENT是一样的大小 且模式一样
                // Child wants to determine its own size. It can't be
                // bigger than us. 
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父布局未定具体大小 看子布局需要的具体大小
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

总结:当父布局三个模式对应的子控件三个模式的大小为:

  • MeasureSpec.EXACTLY:

childDimension为精确值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY

LayoutParams.MATCH_PARENT:返回父布局剩下的空间+ MeasureSpec.EXACTLY

LayoutParams.WRAP_CONTENT: 返回父布局剩下的空间+ MeasureSpec.AT_MOST

  • MeasureSpec.AT_MOST

childDimension为精确值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY

LayoutParams.MATCH_PARENT:返回父布局剩下的空间+MeasureSpec.AT_MOST

LayoutParams.WRAP_CONTENT: 返回父布局剩下的空间+MeasureSpec.AT_MOST

  • MeasureSpec.UNSPECIFIED
    根据sUseZeroUnspecifiedMeasureSpec决定返回的大小

childDimension为精确值>0: 返回控件需要的大小 +MeasureSpec.EXACTLY

LayoutParams.MATCH_PARENT: 根据sUseZeroUnspecifiedMeasureSpec决定返回的大小 0或者剩余空间大小 + MeasureSpec.UNSPECIFIED

LayoutParams.WRAP_CONTENT: 根据sUseZeroUnspecifiedMeasureSpec决定返回的大小 0或者剩余空间大小 + MeasureSpec.UNSPECIFIED

measure(int widthMeasureSpec, int heightMeasureSpec) 这个是用于父控件轮训调用各个子类 将子类的大小信息传入到子控件中

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      //  这个是光学边界的判断,有没有阴影什么的那些
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        //抑制符号扩展的低字节 
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
      // 这个onMeasure()便是我们这篇文章的起点阅读,它是在measure()中调用到的 也就是说measure先与 onMeasure()
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

总结:measure(int widthMeasureSpec, int heightMeasureSpec) 这个方法中有调用到的onMeasure()便是我们这篇文章的起点阅读,既然 它是在measure()中调用到的 也就是说measure()先于onMeasure()被调用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值