Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to use streaming layout FlowLayout in Android

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

Editor to share with you how to use the streaming layout FlowLayout in Android. I hope you will get something after reading this article. Let's discuss it together.

Introduction

The following figure shows the implementation effect.

Customize the process of View

Think about what we all need to do to customize view.

Layout, we need to determine the size of the view and the location to be placed, that is, onMeasure () and onLayout ()

Show, after the layout is how to display it, mainly using onDraw, may be used: canvas paint matrix clip rect animation path (Bezier) line

Interaction, onTouchEvent

What this article wants to do is streaming layout, inherited from ViewGroup, the main implementation functions are onMeasure (), onLayout (). The following figure is a flow chart.

OnMeasure

OnMeasure is the method of measurement, so what is the measurement? We have already written the size of view in xml, so why do we need to measure it?

There are a few questions. We did write width and height of view when we wrote view in xml, but is that a specific dp? We might write match_parent, it could be wrap_content, it could be weight, it could be specific length. What size are we going to give it if it is not a definite value? Can you give it even if you write a certain value? what if the maximum width of the parent layout is 100dp and the child layout says 200dp? For multi-level view, do we just call the onMeasure method of this view?

Take the picture below as chestnut

If the red View in the picture above is the view we want to customize, how should we measure it?

First of all, we need to know how much space its parent layout can give it.

For view of container type, calculate the required size of view based on the space required by all its child view

First of all, it should be clear: measurement is a top-down recursive process! Taking the height of FlowLayout as an example, how appropriate is its height? The required height is obtained by measuring the height of each row one by one according to the placement of the layout, and then the measured height is calculated according to the reference given by the parent layout, and finally the real height is obtained. In the measurement of quantum view, the sub-view may also be a container, and there are many view inside it. Its own uncertainty needs to traverse its sub-layout, which is a recursive process!

Let's begin our measurement process, assuming that the parent layout of FlowLayout is LinearLayout, and the overall UI layout is as follows

How much space is given to it by LinearLayout? remember the two parameters of onMeasure, which are the reference values given by the parent layout and the constraints imposed by the parent layout.

Protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

MeasureSpec consists of two parts. The high 2 bits represent mode and the lower 30 bits represent the reference width and height given by the size; parent layout.

Int selfWidth = MeasureSpec.getSize (widthMeasureSpec); int selfHeight = MeasureSpec.getSize (heightMeasureSpec)

From this we can get the size of the space given by the parent layout, which is the maximum space of the FlowLayout. So how much space do we actually need? we need to measure all the sub-view.

Placement logic of sub-view:

If the bank can be put down, it will be put into the bank, that is, it meets the condition lineUsed + childWidthMeasured + mHorizontalSpacing.

< selfWidth 本行放不下则另起一行 摆放逻辑有了,怎么测量子view 获得子view的LayoutParams从而获得xml里设置的layout_width与layout_height 调用getChildMeasureSpec方法算出MeasureSpec 子view调用measure方法测量 // 获得LayoutParamsLayoutParams childParams = childView.getLayoutParams();// 计算measureSpecint childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentLeft + parentRight, childParams.width);int childHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentTop + parentBottom, childParams.height);// 测量childView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 下面是 getChildMeasureSpec 内部实现,以横向尺寸为例 // 以横向尺寸为例,第一个参数是父布局给的spec,第二个参数是扣除自己使用的尺寸,第三个是layoutParams 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) { // Parent has imposed an exact size on us // 老王的钱是确定的,小王有三种可能 case MeasureSpec.EXACTLY: if (childDimension >

= 0) {resultSize = childDimension; resultMode = MeasureSpec.EXACTLY;} else if (childDimension = = LayoutParams.MATCH_PARENT) {/ / Child wants to be our size. So be it. ResultSize = size; resultMode = MeasureSpec.EXACTLY;} else if (childDimension = = LayoutParams.WRAP_CONTENT) {/ / Child wants to determine its own size. It can't be / / bigger than us. ResultSize = size; resultMode = MeasureSpec.AT_MOST;} break; / / Parent has imposed a maximum size on us / / how much money Lao Wang has, Xiao Wang has three possibilities: 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) {/ / Child wants to determine its own size. It can't be / / bigger than us. ResultSize = size; resultMode = MeasureSpec.AT_MOST;} break; / / Parent asked to see how big we want to be / / Lao Wang's money is uncertain. Xiao Wang has three possibilities: 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);}

The above algorithm is actually very simple. According to the size of mode and size given by the parent layout, we can calculate our own mode and size. The specific rules are as follows.

The implementation of the algorithm is that there are three possibilities for parent layout and three possibilities for child layout, a total of nine possibilities. For example, Xiao Wang below wants to borrow money from Lao Wang to buy a house. How many possibilities are there?

How to determine the size of the layout after measuring the sub-view?

Measure all rows to get the maximum value as the width of the layout

Measure the height of all rows. The sum of the height is the height of the layout.

Call the setMeasuredDimension function to set the final size

OnLayout

Based on the measurement work, we have basically determined the location of all the sub-view. What we need to do at this stage is to put all the view on it and call the layout function of the sub-view.

The specific code implements public class FlowLayout extends ViewGroup {private int mHorizontalSpacing = dp2px (16); / / each item horizontal spacing private int mVerticalSpacing = dp2px (8); / / each item horizontal spacing / / record all rows private List allLines = new ArrayList (); / / record all row heights private List lineHeights = new ArrayList () / * new FlowLayout (context) is used with * @ param context * / public FlowLayout (Context context) {super (context);} / * xml is a serialized format with key-value pairs All are parsed in LayoutInflater * reflection * * @ param context * @ param attrs * / public FlowLayout (Context context, AttributeSet attrs) {super (context, attrs) } / * * topic style * @ param context * @ param attrs * @ param defStyleAttr * / public FlowLayout (Context context, AttributeSet attrs, int defStyleAttr) {super (context, attrs, defStyleAttr) } / * * Custom attributes * @ param context * @ param attrs * @ param defStyleAttr * @ param defStyleRes * / public FlowLayout (Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super (context, attrs, defStyleAttr, defStyleRes) } / * onMeasure may be called multiple times * / private void clearMeasureParams () {/ / keep creating recycling will cause memory jitter, clear can allLines.clear (); lineHeights.clear ();} / * metric-most measure the child first and then measure yourself. The size of the child may be changing all the time, and the parent layout changes with it * only ViewPager measures himself first and then the child * spec is a reference value, not a specific value * @ param widthMeasureSpec. This is a recursive process * @ param heightMeasureSpec parent layout given * / @ Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {clearMeasureParams (); / / first measure the child int childCount = getChildCount (); int parentTop = getPaddingTop (); int parentLeft = getPaddingLeft (); int parentRight = getPaddingRight (); int parentBottom = getPaddingBottom () / / reference values given by Grandpa int selfWidth = MeasureSpec.getSize (widthMeasureSpec); int selfHeight = MeasureSpec.getSize (heightMeasureSpec); / / Save all view List lineViews = new ArrayList () of a row; / / record how wide this line has been used size int lineWidthUsed = 0; / / High int lineHeight of a row = 0 / / during the measure process, the width and height of the parent layout required by the child view int parentNeedWidth = 0; int parentNeedHeight = 0; for (int I = 0; I)

< childCount; i++) { View childView = getChildAt(i); LayoutParams childParams = childView.getLayoutParams(); // 将LayoutParams转为measureSpec /** * 测量是个递归的过程,测量子View确定自身大小 * getChildMeasureSpec的三个参数,第一个是父布局传过来的MeasureSpec,第二个参数是去除自身用掉的padding,第三个是子布局需要的宽度或高度 */ int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentLeft + parentRight, childParams.width); int childHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentTop + parentBottom, childParams.height); childView.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 获取子View测量的宽高 int childMeasuredWidth = childView.getMeasuredWidth(); int childMeasuredHeight = childView.getMeasuredHeight(); // 需要换行 if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing >

SelfWidth) {/ / determine the width and height currently needed when wrapping parentNeedHeight = parentNeedHeight + lineHeight + mVerticalSpacing; parentNeedWidth = Math.max (parentNeedWidth, lineWidthUsed + mHorizontalSpacing); / / store the data for each row! The last line will be omitted allLines.add (lineViews); lineHeights.add (lineHeight); / / data emptying lineViews = new ArrayList (); lineWidthUsed = 0; lineHeight = 0;} lineViews.add (childView); lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing LineHeight = Math.max (lineHeight, childMeasuredHeight); / / processing the last line of data if (I = = childCount-1) {allLines.add (lineViews); lineHeights.add (lineHeight); parentNeedHeight = parentNeedHeight + lineHeight + mVerticalSpacing; parentNeedWidth = Math.max (parentNeedWidth, lineWidthUsed + mHorizontalSpacing) }} / / measure yourself int widthMode = MeasureSpec.getMode (widthMeasureSpec); int heightMode = MeasureSpec.getMode (heightMeasureSpec) after measuring the child; / / if the parent layout is given an exact value, the quantum view becomes meaningless int realWidth = (widthMode = = MeasureSpec.EXACTLY)? SelfWidth: parentNeedWidth; int realHeight = (heightMode = = MeasureSpec.EXACTLY)? SelfHeight: parentNeedHeight; setMeasuredDimension (realWidth, realHeight);} / * * layout * / @ Override protected void onLayout (boolean changed, int l, int t, int r, int b) {int currentL = getPaddingLeft (); int currentT = getPaddingTop (); for (int I = 0; I < allLines.size (); iTunes +) {List lineViews = allLines.get (I) Int lineHeight = lineHeights.get (I); for (int j = 0; j < lineViews.size ()) {View view = lineViews.get (j); int left = currentL; int top = currentT; / / Why is int right = view.getWidth () not used here GetWidth is only available after calling onLayout: int right = left + view.getMeasuredWidth (); int bottom = top + view.getMeasuredHeight (); / / view.layout (left, top, right, bottom) in sub-view location; currentL = right + mHorizontalSpacing;} currentT = currentT + lineHeight + mVerticalSpacing; currentL = getPaddingLeft () }} public static int dp2px (int dp) {return (int) TypedValue.applyDimension (TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem (). GetDisplayMetrics ();}}

The effect is as at the beginning of the article.

When was the onMeasure method of FlowLayout called?

FlowLayout's onMeasure is called on something above, definitely when its parent layout does measurement recursion. For example, the parent layout of FlowLayout is LinearLayout. Let's go to LinearLayout to find the implementation.

LinearLayout.onMeasure ()

Protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation = = VERTICAL) {measureVertical (widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal (widthMeasureSpec, heightMeasureSpec);}} void measureVertical (widthMeasureSpec, heightMeasureSpec) {/ / LayoutParams final LayoutParams lp = (LayoutParams) child.getLayoutParams () of the child view ... / start measuring measureChildBeforeLayout (child, I, widthMeasureSpec, 0Hight measure Spec, usedHeight);} void measureChildBeforeLayout (View child, int childIndex,int widthMeasureSpec, int totalWidth, int heightMeasureSpec,int totalHeight) {measureChildWithMargins (child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight) } protected void measureChildWithMargins (View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams (); / / remove your own use, and give the rest of padding and margin to sub-view 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); / / here view calls its measurement function, that is, FlowLayout's measurement child.measure (childWidthMeasureSpec, childHeightMeasureSpec);} some other concepts

What is MeasureSpec?

MeasureSpec, a common attribute of custom view, is the inner class of View, which encapsulates the layout requirements of sub-View and consists of size and pattern. Because the int type is made up of 32 bits, he uses the high 2 bits for Mode and the low 30 bits for Size.

There are three kinds of MeasureMode 00 01 11

UNSPECIFIED: no restriction on the size of View, which is used by the system

EXACTLY: the exact size, such as 100dp

AT_MOST: the size cannot exceed a certain value, such as matchParent, and the maximum size cannot exceed the parent layout.

The relationship between LayoutParams and MeasureSpec

The key-value pairs we write in xml can not be directly converted into specific dp. Our own MeasureSpec is calculated according to the size and pattern given by the parent layout, and the final measurement value is obtained through continuous recursive measurement. What LayoutParams.width gets is that what is written in xml may be match_parent or wrap_content, which cannot be used directly. According to the reference value given by the parent layout, a specific measurement can only be obtained by measuring the size of the quantum layout.

Why onLayout uses getMeasuredWidth instead of int right = view.getWidth ()

This can only be answered if you have a complete understanding of the whole process. GetWidth is the value after the onLayout call, and getMeasuredWidth is the value after the measurement.

After reading this article, I believe you have a certain understanding of "how to use FlowLayout in Android". If you want to know more about it, you are welcome to follow the industry information channel. Thank you for reading!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report