Measurement process of ViewGroup subclass LinearLayout

Keywords: Android Attribute Java less

Measurement process of ViewGroup subclass LinearLayout

In the note "View and ViewGroup's measurement process", it has been mentioned that ViewGroup does not perform a specific measurement process, but only calls the measure() method of child view. This is because there are too many ViewGroup and other methods, which are not easy to handle uniformly. In fact, each of them rewrite onMeasure from its own processing. The following is mainly to analyze the measurement process of LinearLayout, a subclass of ViewGroup.
Start with onMeasure()
LinearLayout.java
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. if (mOrientation == VERTICAL) {
  3. measureVertical(widthMeasureSpec, heightMeasureSpec);//Vertical layout
  4. } else {
  5. measureHorizontal(widthMeasureSpec, heightMeasureSpec);//Horizontal layout
  6. }
  7. }
Here I will only analyze the process of vertical layout measureVertical().
For detailed analysis, see the comments in the code.
LinearLayout.java
  1. void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
  2. mTotalLength = 0;//Height of child view
  3. int maxWidth = 0;
  4. int childState = 0;
  5. int alternativeMaxWidth = 0;//Maximum width of subview (excluding subview of layout_weight > 0)
  6. int weightedMaxWidth = 0;//Maximum width of the subview (including only the subview of layout_weight > 0)
  7. boolean allFillParent = true;//Whether the width of the subview is all fillParent is used for subsequent judgment on whether recalculation is necessary
  8. float totalWeight = 0; //The sum of weight s of all child view s
  9. //Number of child views (including only direct child views, such as Linear Layout 1)
  10. //There are Textview 1, Linear Layout 2, TextView 2, respectively.
  11. //Linear Layout 2 has multiple sub-view s. At this point, LinearLayout1's getVirtualChildCount(); 3)
  12. final int count = getVirtualChildCount();
  13. //Linear Layout Width Mode
  14. final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  15. //Linear Layout Height Mode
  16. final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  17. //The width of the child view is determined by the parent. If the parent LinearLayout is layout_width=wrap_content,
  18. //If the child view is fill_parent, matchWidth =true
  19. boolean matchWidth = false;
  20. boolean skippedMeasure = false;
  21. //baseLine of the number of sub view s in LinearLayout is used as the baseLine of 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);//By default, all return 0. Extended reservation is useless here.
  33. continue;
  34. }
  35. if (child.getVisibility() == View.GONE) {
  36. i += getChildrenSkipCount(child, i);//By default, all return 0. Extended reservation is useless here.
  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. //Layout Params of child
  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. //If the Linear Layout height is determined. And the height of this child view is equal to 0, weight > 0,
  55. //Then mTotal Length only needs to add margin.
  56. //Because it's weight > 0; the view's specific height will be calculated later.
  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) {//Whether android: measure WithLargestChild is set
  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 cannot be less than android: baseline Aligned ChildIndex with simultaneous 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. //If the LinearLayout width is not determined, such as wrap_content, and the child view is FILL_PARENT,
  127. //Then mark 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);//Width of Maximum Subview
  134. childState = combineMeasuredStates(childState, child.getMeasuredState());
  135. //Is the child view width all 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. //If the parent width is wrap_content and the child fill_parent, the width of the child needs to be determined by the parent. This is not the true width.
  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. //SkppedMeasure = true: If the height of LinearLayout is determined and there are child view s with 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. }
As you can see from the above, when you actually assign the height of each child view of LinearLayout, you first assign the control without setting weight and height=0 attribute and execute the measure ment process of the child view, and then give those child views with weight > 0 and height=0. This is skippedMeasure of 79 lines. = true will eventually perform the work in the if judgment of 256.
To verify, here are a few examples.
The following 480dip is 480*3= 1140px on the phone I tested.
  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>
Let's start with the actual results:
doov_ll_root:1440
doov_linear1:1440
doov_id3:270
doov_id1:(1440-270)=1170   ;1170*2/3=760
doov_id2:380

The calculation process is as follows:
256 lines: delta = 1170, heightSize = 1440, mTotal Length = 270
280 rows - child =0: share = 780, mTotal Length = 0, delta=1170-780=390
280 lines - child =1: share=390, mTotalLength=780,delat=0
child =2 cannot enter 257 lines of judgment, and no other processing is performed.
The actual effect of the above layout is as follows:

In another case:
If the doov_id1 and doov_id2 configurations are changed as follows, that is, the height is changed to match_parent.
At this time, the height ratio of doov_id 1 to doov_id2 is obviously not 2:1, but 1:2. Let's analyze the specific calculation steps.
At 256 lines: delta=-1710, heightSize=1440, mTotal Length=3150 (mTotal Length=1440*2+270)
When 300 rows of child=0: share = - 1140, child Height = 300, mTotal Length = 0
When 300 rows of child=1: share = - 570, child Height = 870, mTotal Length = 300
The height of doov_id1 is 1440+(-1140)=300.
The height of doov_id2 is 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>
The actual display effect of the above layout file is as follows:


Summary: When using Linear Layout, if the weight attribute of child view is set, it is better to set the height (or width) attribute of view to 0dp, so as to avoid the actual displaying of the wrong proportion, and set the weight of two child views to 1 in time. Although the proportion may be 1:1 when displaying, it will actually measure more than once (specifically reflected in lines 63 and 102). So setting the height (or width) attribute of the child view to 0dp not only enables the view to display correctly, but also provides code efficiency.

Posted by ramram on Fri, 29 Mar 2019 11:36:30 -0700