ViewGroup 子类 LinearLayout 的measure 流程

ViewGroup 子类 LinearLayout 的measure 流程

在笔记"View和ViewGroup 的measure过程"中已经提到 ViewGroup没有执行具体的测量过程,只是调用child view 的measure()方法.这是因为ViewGroup之类太多,不好统一处理.实际是各个之类去重写onMeasure来自己处理的.下面主要是分析ViewGroup 子类 LinearLayout 的measure 流程.
onMeasure() 开始
LinearLayout.java
   
   
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. if (mOrientation == VERTICAL) {
  3. measureVertical(widthMeasureSpec, heightMeasureSpec);//垂直布局
  4. } else {
  5. measureHorizontal(widthMeasureSpec, heightMeasureSpec);//水平布局
  6. }
  7. }
我这边这里只分析垂直布局 measureVertical() 的流程.
具体分析见代码中的注释.
LinearLayout.java
   
   
  1. void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
  2. mTotalLength = 0;//子view的高度和
  3. int maxWidth = 0;
  4. int childState = 0;
  5. int alternativeMaxWidth = 0;//子视图的最大宽度(不包含layout_weight>0的子view)
  6. int weightedMaxWidth = 0;//子视图的最大宽度(仅包含layout_weight>0的子view)
  7. boolean allFillParent = true;//子视图的宽度是否全是fillParent的,用于后续判断是否需要重新计算
  8. float totalWeight = 0; //所有子view的weight之和
  9. //子view的个数(仅包含直接子view,如LinearLayout1中
  10. //分别有Textview1,LinearLayout2,TextView2,
  11. //而LinearLayout2中又有多个子view。此时LinearLayout1的getVirtualChildCount();为3)
  12. final int count = getVirtualChildCount();
  13. //LinearLayout宽度模式
  14. final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  15. //LinearLayout高度模式
  16. final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  17. //子view的宽度是否要由父确定。如父LinearLayout为layout_width=wrap_content,
  18. //子view为fill_parent则matchWidth =true
  19. boolean matchWidth = false;
  20. boolean skippedMeasure = false;
  21. //以LinearLayout中第几个子view的baseLine作为LinearLayout的基准线
  22. final int baselineChildIndex = mBaselineAlignedChildIndex;
  23. final boolean useLargestChild = mUseLargestChild;
  24. int largestChildHeight = Integer.MIN_VALUE;
  25. if (sDebugLayout) {
  26. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.1, this=" + this);
  27. }
  28. // See how tall everyone is. Also remember max width.
  29. for (int i = 0; i < count; ++i) {
  30. final View child = getVirtualChildAt(i);
  31. if (child == null) {
  32. mTotalLength += measureNullChild(i);//默认都返回0,扩展预留,此处没用
  33. continue;
  34. }
  35. if (child.getVisibility() == View.GONE) {
  36. i += getChildrenSkipCount(child, i);//默认都返回0,扩展预留,此处没用
  37. continue;
  38. }
  39. if (sDebugLayout) {
  40. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Child in Pass1: " + i + ", child=" + child
  41. + ", this=" + this);
  42. }
  43. if (hasDividerBeforeChildAt(i)) {
  44. mTotalLength += mDividerHeight;
  45. }
  46. //child 的 LayoutParams
  47. LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
  48. totalWeight += lp.weight;
  49. if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
  50. // Optimization: don't bother measuring children who are going to use
  51. // leftover space. These views will get measured again down below if
  52. // there is any leftover space.
  53. final int totalLength = mTotalLength;
  54. //如果LinearLayout高度是已经确定的。并且这个子view的height=0,weight>0,
  55. //则mTotalLength只需要加上margin即可,
  56. //由于是weight>0;该view的具体高度等会还要计算
  57. mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
  58. if (sDebugLayout) {
  59. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-1: " + i
  60. + ", child=" + child + ", this=" + this + ", "
  61. + "status=Skipped, mTotalLength=" + mTotalLength
  62. + ", totalWeight=" + totalWeight);
  63. }
  64. skippedMeasure = true;
  65. } else {
  66. int oldHeight = Integer.MIN_VALUE;
  67. if (lp.height == 0 && lp.weight > 0) {
  68. // heightMode is either UNSPECIFIED or AT_MOST, and this
  69. // child wanted to stretch to fill available space.
  70. // Translate that to WRAP_CONTENT so that it does not end up
  71. // with a height of 0
  72. oldHeight = 0;
  73. lp.height = LayoutParams.WRAP_CONTENT;
  74. if (sDebugLayout) {
  75. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-2-1: " + i
  76. + ", child=" + child + ", this=" + this + ", "
  77. + "status=Modified, LayoutParams.height change to WRAP_CONTENT");
  78. }
  79. }
  80. // Determine how big this child would like to be. If this or
  81. // previous children have given a weight, then we allow it to
  82. // use all available space (and we will shrink things later
  83. // if needed).
  84. measureChildBeforeLayout(
  85. child, i, widthMeasureSpec, 0, heightMeasureSpec,
  86. totalWeight == 0 ? mTotalLength : 0);
  87. if (oldHeight != Integer.MIN_VALUE) {
  88. lp.height = oldHeight;
  89. }
  90. final int childHeight = child.getMeasuredHeight();
  91. final int totalLength = mTotalLength;
  92. mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
  93. lp.bottomMargin + getNextLocationOffset(child));
  94. if (useLargestChild) {//是否设置了android:measureWithLargestChild
  95. largestChildHeight = Math.max(childHeight, largestChildHeight);
  96. }
  97. if (sDebugLayout) {
  98. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1-2-2: " + i
  99. + ", child=" + child + ", this=" + this + ", "
  100. + "status=Measured, mTotalLength=" + mTotalLength
  101. + ", totalWeight=" + totalWeight);
  102. }
  103. }
  104. /**
  105. * If applicable, compute the additional offset to the child's baseline
  106. * we'll need later when asked {@link #getBaseline}.
  107. */
  108. if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
  109. mBaselineChildTop = mTotalLength;
  110. }
  111. // if we are trying to use a child index for our baseline, the above
  112. // book keeping only works if there are no children above it with
  113. // weight. fail fast to aid the developer.
  114. if (i < baselineChildIndex && lp.weight > 0) {//i 不能小于android:baselineAlignedChildIndex的值的同时weight >0
  115. throw new RuntimeException("A child of LinearLayout with index "
  116. + "less than mBaselineAlignedChildIndex has weight > 0, which "
  117. + "won't work. Either remove the weight, or don't set "
  118. + "mBaselineAlignedChildIndex.");
  119. }
  120. boolean matchWidthLocally = false;
  121. if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
  122. // The width of the linear layout will scale, and at least one
  123. // child said it wanted to match our width. Set a flag
  124. // indicating that we need to remeasure at least that view when
  125. // we know our width.
  126. //如果LinearLayout宽度不是已确定的,如是wrap_content,而子view是FILL_PARENT,
  127. //则做标记matchWidth=true; matchWidthLocally = true;
  128. matchWidth = true;
  129. matchWidthLocally = true;
  130. }
  131. final int margin = lp.leftMargin + lp.rightMargin;
  132. final int measuredWidth = child.getMeasuredWidth() + margin;
  133. maxWidth = Math.max(maxWidth, measuredWidth);//最大子view的宽度
  134. childState = combineMeasuredStates(childState, child.getMeasuredState());
  135. //子view宽度是否全是FILL_PARENT
  136. allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
  137. if (lp.weight > 0) {
  138. /*
  139. * Widths of weighted Views are bogus if we end up
  140. * remeasuring, so keep them separate.
  141. */
  142. //如父width是wrap_content,子是fill_parent,则子的宽度需要在父确定后才能确定。这里并不是真实的宽度
  143. weightedMaxWidth = Math.max(weightedMaxWidth,
  144. matchWidthLocally ? margin : measuredWidth);
  145. } else {
  146. alternativeMaxWidth = Math.max(alternativeMaxWidth,
  147. matchWidthLocally ? margin : measuredWidth);
  148. }
  149. if (sDebugLayout) {
  150. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass1: " + i + ", child=" + child
  151. + ", this=" + this + ", " + "matchWidth=" + matchWidth
  152. + ", matchWidthLocally=" + matchWidthLocally + ", "
  153. + "weightedMaxWidth=" + weightedMaxWidth
  154. + ", alternativeMaxWidth=" + alternativeMaxWidth);
  155. }
  156. i += getChildrenSkipCount(child, i);
  157. }
  158. if (sDebugLayout) {
  159. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.1, this=" + this + ", "
  160. + "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
  161. + ", alternativeMaxWidth=" + alternativeMaxWidth + ", "
  162. + "mTotalLength=" + mTotalLength + ", totalWeight=" + totalWeight);
  163. }
  164. if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
  165. mTotalLength += mDividerHeight;
  166. }
  167. if (useLargestChild &&
  168. (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
  169. if (sDebugLayout) {
  170. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.1.5, this=" + this);
  171. }
  172. mTotalLength = 0;
  173. for (int i = 0; i < count; ++i) {
  174. final View child = getVirtualChildAt(i);
  175. if (child == null) {
  176. mTotalLength += measureNullChild(i);
  177. continue;
  178. }
  179. if (child.getVisibility() == GONE) {
  180. i += getChildrenSkipCount(child, i);
  181. continue;
  182. }
  183. final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
  184. child.getLayoutParams();
  185. // Account for negative margins
  186. final int totalLength = mTotalLength;
  187. mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
  188. lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
  189. }
  190. if (sDebugLayout) {
  191. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.1.5, this=" + this
  192. + ", largestChildHeight=" + largestChildHeight
  193. + ", mTotalLength=" + mTotalLength);
  194. }
  195. }
  196. // Add in our padding
  197. mTotalLength += mPaddingTop + mPaddingBottom;
  198. int heightSize = mTotalLength;
  199. // Check against our minimum height
  200. heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
  201. // Reconcile our calculated size with the heightMeasureSpec
  202. int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
  203. heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
  204. // Either expand children with weight to take up available space or
  205. // shrink them if they extend beyond our current bounds. If we skipped
  206. // measurement on any children, we need to measure them now.
  207. int delta = heightSize - mTotalLength;
  208. if (sDebugLayout) {
  209. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.2, this=" + this + ", "
  210. + "delta=" + delta + ", " + "heightSize=" + heightSize
  211. + ", mTotalLength=" + mTotalLength);
  212. }
  213. //skippedMeasure=true:如果LinearLayout高度是已经确定的,并且有子view的height=0,weight>0,
  214. if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
  215. float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
  216. mTotalLength = 0;
  217. for (int i = 0; i < count; ++i) {
  218. final View child = getVirtualChildAt(i);
  219. if (child.getVisibility() == View.GONE) {
  220. continue;
  221. }
  222. if (sDebugLayout) {
  223. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Child in Pass2: " + i + ", "
  224. + "child=" + child + ", this=" + this);
  225. }
  226. LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
  227. float childExtra = lp.weight;
  228. if (childExtra > 0) {
  229. // Child said it could absorb extra space -- give him his share
  230. int share = (int) (childExtra * delta / weightSum);
  231. weightSum -= childExtra;
  232. delta -= share;
  233. final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
  234. mPaddingLeft + mPaddingRight +
  235. lp.leftMargin + lp.rightMargin, lp.width);
  236. // TODO: Use a field like lp.isMeasured to figure out if this
  237. // child has been previously measured
  238. if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
  239. // child was measured once already above...
  240. // base new measurement on stored values
  241. int childHeight = child.getMeasuredHeight() + share;
  242. if (childHeight < 0) {
  243. childHeight = 0;
  244. }
  245. child.measure(childWidthMeasureSpec,
  246. MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
  247. if (sDebugLayout) {
  248. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-1-1: " + i
  249. + ", child=" + child + ", this=" + this + ", "
  250. + "status=Measured, share=" + share
  251. + ", childHeight=" + childHeight
  252. + ", mTotalLength=" + mTotalLength);
  253. }
  254. } else {
  255. // child was skipped in the loop above.
  256. // Measure for this first time here
  257. child.measure(childWidthMeasureSpec,
  258. MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
  259. MeasureSpec.EXACTLY));
  260. if (sDebugLayout) {
  261. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-1-2: " + i
  262. + ", child=" + child + ", this=" + this + ", "
  263. + "status=Measured, share=" + share
  264. + ", mTotalLength=" + mTotalLength);
  265. }
  266. }
  267. // Child may now not fit in vertical dimension.
  268. childState = combineMeasuredStates(childState, child.getMeasuredState()
  269. & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
  270. } else {
  271. if (sDebugLayout) {
  272. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-1-2: " + i
  273. + ", child=" + child + ", this=" + this + ", "
  274. + "status=Skipped, ChildExtra=" + childExtra
  275. + ", mTotalLength=" + mTotalLength);
  276. }
  277. }
  278. final int margin = lp.leftMargin + lp.rightMargin;
  279. final int measuredWidth = child.getMeasuredWidth() + margin;
  280. maxWidth = Math.max(maxWidth, measuredWidth);
  281. boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
  282. lp.width == LayoutParams.MATCH_PARENT;
  283. alternativeMaxWidth = Math.max(alternativeMaxWidth,
  284. matchWidthLocally ? margin : measuredWidth);
  285. allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
  286. final int totalLength = mTotalLength;
  287. mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
  288. lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
  289. }
  290. // Add in our padding
  291. mTotalLength += mPaddingTop + mPaddingBottom;
  292. if (sDebugLayout) {
  293. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.2-1, this=" + this + ", "
  294. + "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
  295. + ", alternativeMaxWidth=" + alternativeMaxWidth + ", "
  296. + "delta=" + delta + ", mTotalLength=" + mTotalLength);
  297. }
  298. // TODO: Should we recompute the heightSpec based on the new total length?
  299. } else {
  300. alternativeMaxWidth = Math.max(alternativeMaxWidth,
  301. weightedMaxWidth);
  302. // We have no limit, so make all weighted views as tall as the largest child.
  303. // Children will have already been measured once.
  304. if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
  305. for (int i = 0; i < count; i++) {
  306. final View child = getVirtualChildAt(i);
  307. if (child == null || child.getVisibility() == View.GONE) {
  308. continue;
  309. }
  310. final LinearLayout.LayoutParams lp =
  311. (LinearLayout.LayoutParams) child.getLayoutParams();
  312. float childExtra = lp.weight;
  313. if (childExtra > 0) {
  314. child.measure(
  315. MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
  316. MeasureSpec.EXACTLY),
  317. MeasureSpec.makeMeasureSpec(largestChildHeight,
  318. MeasureSpec.EXACTLY));
  319. }
  320. if (sDebugLayout) {
  321. String measureString = (childExtra > 0)
  322. ? "status=Measured" : "status=Skipped";
  323. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Child in Pass2-2: " + i
  324. + ", child=" + child + ", this=" + this + ", " + measureString);
  325. }
  326. }
  327. }
  328. if (sDebugLayout) {
  329. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.2-2, this=" + this + ", "
  330. + "maxWidth=" + maxWidth + ", weightedMaxWidth=" + weightedMaxWidth
  331. + ", alternativeMaxWidth=" + alternativeMaxWidth + ", " + "delta=" + delta
  332. + ", mTotalLength=" + mTotalLength
  333. + ", useLargestChild=" + useLargestChild);
  334. }
  335. }
  336. if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
  337. maxWidth = alternativeMaxWidth;
  338. }
  339. maxWidth += mPaddingLeft + mPaddingRight;
  340. // Check against our minimum width
  341. maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
  342. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  343. heightSizeAndState);
  344. if (matchWidth) {
  345. if (sDebugLayout) {
  346. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] + Pass.3, this=" + this
  347. + ", uniformWidth=" + getMeasuredWidth() + ", this=" + this);
  348. }
  349. forceUniformWidth(count, heightMeasureSpec);
  350. if (sDebugLayout) {
  351. Xlog.d(DEBUG_LOG_TAG, "[LinearLayout] - Pass.3, this=" + this);
  352. }
  353. }
  354. }
从上面可以看到,实际分配LinearLayout的各个child view 的高度的时候是先分配没有设置weight和height=0属性的控件并执行该child view的measure流程,然后给那些设置了weight>0同时height=0的子child view.这个也就是79行的 skippedMeasure = true 最终会在256的if判断中执行的工作.
为了验证,这里创建几个例子分析.
下面的480dip 在我测试的手机上面就是480*3= 1140px.
   
   
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:id="@+id/doov_ll_root"
  3. android:layout_width="fill_parent"
  4. android:layout_height="480dip" >
  5. <LinearLayout
  6. android:id="@+id/doov_linear1"
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent"
  9. android:background="#ff888888"
  10. android:orientation="vertical" >
  11. <TextView
  12. android:id="@+id/doov_id1"
  13. android:layout_width="fill_parent"
  14. android:layout_height="0dip"
  15. android:layout_weight="2"
  16. android:background="#ff765423"
  17. android:text="11111" />
  18. <TextView
  19. android:id="@+id/doov_id2"
  20. android:layout_width="fill_parent"
  21. android:layout_height="0dip"
  22. android:layout_weight="1"
  23. android:background="#ffff0000"
  24. android:text="aaaaa" />
  25. <TextView
  26. android:id="@+id/doov_id3"
  27. android:layout_width="fill_parent"
  28. android:layout_height="90dip"
  29. android:background="#ff234532"
  30. android:text="2222222" />
  31. </LinearLayout>
  32. </LinearLayout>
先说一下实际的结果:
doov_ll_root:1440
doov_linear1:1440
doov_id3:270
doov_id1:(1440-270)=1170   ;1170*2/3=760
doov_id2:380

计算过程如下:
256行的时候: delta=1170, heightSize=1440, mTotalLength=270
280行-child =0的时候: share=780, mTotalLength=0, delta=1170-780=390
280行-child =1的时候:share=390, mTotalLength=780,delat=0
child =2的时候不能进入257行的判断,不执行其他处理.
上面的布局实际效果图如下:

另外一种情况:
如果将的 doov_id1和 doov_id2 配置改成如下,也就是高度改成match_parent.
这个时候 doov_id1和 doov_id2 高度比例看起来的时候显然不是2:1,反而像是1:2.还是分析一下具体计算步骤:
256行的时候:delta=-1710, heightSize=1440, mTotalLength=3150( mTotalLength=1440*2+270 )
300行child=0的时候:share=-1140, childHeight=300, mTotalLength=0
300行child=1的时候:share=-570, childHeight=870, mTotalLength=300
其中 doov_id1的高度为:1440+(-1140)=300
     doov_id2的高度为: 1440+(-570 )=870
   
   
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:id="@+id/doov_ll_root"
  3. android:layout_width="fill_parent"
  4. android:layout_height="480dip" >
  5. <LinearLayout
  6. android:id="@+id/doov_linear1"
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent"
  9. android:background="#ff888888"
  10. android:orientation="vertical" >
  11. <TextView
  12. android:id="@+id/doov_id1"
  13. android:layout_width="fill_parent"
  14. android:layout_height="match_parent"
  15. android:layout_weight="2"
  16. android:background="#ff765423"
  17. android:text="11111" />
  18. <TextView
  19. android:id="@+id/doov_id2"
  20. android:layout_width="fill_parent"
  21. android:layout_height="match_parent"
  22. android:layout_weight="1"
  23. android:background="#ffff0000"
  24. android:text="aaaaa" />
  25. <TextView
  26. android:id="@+id/doov_id3"
  27. android:layout_width="fill_parent"
  28. android:layout_height="90dip"
  29. android:background="#ff234532"
  30. android:text="2222222" />
  31. </LinearLayout>
  32. </LinearLayout>
上面的布局文件实际显示效果如下:


总结:在使用LinearLayout 的时候,如果设置了child view 的weight 属性,最好将对于的view 的height(或者width)属性设置为0dp,这样可以避免实际显示的比例不对,及时将2个child view 的weight都设置了为1,虽然显示的时候可能是1:1 的比例,但是实际却会多measure一次(具体体现在63和102行).所以想相关child view的 height(或者width)属性设置为0dp不仅可以让视图正确显示,还可以提供代码的效率.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值