In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article will explain in detail how to achieve the waterfall flow effect of FlowLayout components in Android. The editor thinks it is very practical, so I share it with you as a reference. I hope you can get something after reading this article.
Paper will sleep shallow, never know the matter want to practice.
Hands-on practice is the best way to learn. For custom View, listening and reading can only go through the process. It is good to master 30% or 40%, and you will soon forget. If you want to become your own thing, you must write it several times and carefully understand the details and the mystery and true meaning of the system API.
To get to the topic, let's hand-write a waterfall flow component FlowLayout today to review the process and key points of customizing view. Let's start with an effect picture.
Key steps of FlowLayout implementation: 1. Create a view that inherits from ViewGroupclass ZSFlowLayout: ViewGroup {constructor (context: Context): super (context) {} / * necessary constructor The system will call this constructor through reflection to complete the creation of view * / constructor (context: Context, attr: AttributeSet): super (context, attr) {} constructor (context: Context, attr: AttributeSet, defZStyle: Int): super (context, attr, defZStyle) {}}
Note that the constructor of the two parameters is a required constructor, and the system will call this constructor through reflection to complete the creation of view. The specific call location is in the createView method of LayoutInflater, as follows (based on android-31):
Some irrelevant codes have been omitted and important comments have been written, please note
Public final View createView (@ NonNull Context viewContext, @ NonNull String name, @ Nullable String prefix, @ Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException {Objects.requireNonNull (viewContext); Objects.requireNonNull (name); / / fetch the corresponding constructor Constructor [] mConstructorSignature = new Class [] {/ / Context.class, AttributeSet.class} from the cache / / see, this is our second constructor constructor = clazz.getConstructor (mConstructorSignature); constructor.setAccessible (true); / / cache constructor sConstructorMap.put (name, constructor) } else {...} try {/ / execute the constructor to create view final View view = constructor.newInstance (args);. Return view;} finally {mConstructorArgs [0] = lastContext;}} catch (Exception e) {...} finally {...}}
Those who are interested in LayoutInflater and the whole set of source code processes related to setContentView, DecorView and PhoneWindow can take a look at my article:
A series of source code analysis behind Activity setContentView
2. Rewrite and implement the onMeasure method override fun onMeasure (widthMeasureSpec: Int, heightMeasureSpec: Int) {}
(1) understand the meaning of MeasureSpec first.
MeasureSpec is an inner class in View, which is basically a binary operation. Because int is 32-bit, the high two bits represent mode and the low 30 bits represent size.
(2) focus on explaining how the next two parameters widthMeasureSpec and heightMeasureSpec come from.
This is the size rule passed to us by the parent class, so how does the parent class generate widthMeasureSpec and heightMeasureSpec according to what rules?
A: the parent class is generated in combination with its own situation and the case of the child view (whether the width of the subclass is match_parent, wrap_content, or a dead value). For more information on the generated logic, please see the getChildMeasureSpec method of ViewGroup.
The relevant instructions are written in the comments. Please check out:
/ * here spec and padding are the size rules of the parent class, and childDimension is the size of the subclass * for example, if the FlowLayout we write is wrapped by LinearLayout, then spec and padding are LinearLayout * spec can be widthMeasureSpec or heightMeasureSpec width and height are calculated separately ChildDimension * is the corresponding width and height set for FlowLayout in the layout file * / public static int getChildMeasureSpec (int spec, int padding, int childDimension) {/ / get the size pattern of the parent class int specMode = MeasureSpec.getMode (spec) / / get the size of the parent class int specSize = MeasureSpec.getSize (spec); / / the minimum size after removing padding cannot be less than 0 int size = Math.max (0, specSize-padding); int resultSize = 0; int resultMode = 0 Switch (specMode) {/ / if the mode of the parent class is MeasureSpec.EXACTLY (precise mode, the value of the parent class can be determined) case MeasureSpec.EXACTLY: if (childDimension > = 0) {/ / the size of the child view is the value we set at this time, and it's okay to exceed the parent class. Developers customize / / for example, the width of the parent view is 100dp. Sub-view wide if you have to set 200dp, then give it to 200dp, what's the point of doing so? This is extensible and not limited to death, for example, sub-view may have scrolling properties or other advanced games resultSize = childDimension; resultMode = MeasureSpec.EXACTLY } else if (childDimension = = LayoutParams.MATCH_PARENT) {/ / MATCH_PARENT then the size of the child view and the parent view are the same size pattern is determined resultSize = size; resultMode = MeasureSpec.EXACTLY } else if (childDimension = = LayoutParams.WRAP_CONTENT) {/ / WRAP_CONTENT then the size of the child view is consistent with that of the parent view. The maximum size is no more than this value resultSize = size; resultMode = MeasureSpec.AT_MOST;} break / / Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension > = 0) {/ / execute according to the child view value, and determine the mode resultSize = childDimension; resultMode = MeasureSpec.EXACTLY } else if (childDimension = = LayoutParams.MATCH_PARENT) {/ / the execution mode according to the parent view value is no more than the specified value mode resultSize = size; resultMode = MeasureSpec.AT_MOST } else if (childDimension = = LayoutParams.WRAP_CONTENT) {/ / the execution mode according to the parent view value is no more than the specified value mode resultSize = size; resultMode = MeasureSpec.AT_MOST;} break / / Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension > = 0) {/ / execute according to the child view value, and determine the mode resultSize = childDimension; resultMode = MeasureSpec.EXACTLY } else if (childDimension = = LayoutParams.MATCH_PARENT) {/ / execution mode based on parent view value is undefined resultSize = View.sUseZeroUnspecifiedMeasureSpec? 0: size; resultMode = MeasureSpec.UNSPECIFIED } else if (childDimension = = LayoutParams.WRAP_CONTENT) {/ / the execution mode according to the parent view value is undefined resultSize = View.sUseZeroUnspecifiedMeasureSpec? 0: size; resultMode = MeasureSpec.UNSPECIFIED;} break;} / / noinspection ResourceType return MeasureSpec.makeMeasureSpec (resultSize, resultMode);}
In fact, it is this picture on the Internet.
3. Rewrite and implement the onLayout method
In this method, we need to determine the location of all the sub-view that have been added to our FlowLayout. There is nothing special to pay attention to here, just control the details.
After the introduction of the three key steps, the following is the actual combat code:
ZSFlowLayout:
/ * Custom Waterfall flow layout system Core method * ViewGroup getChildMeasureSpec acquires the MeasureSpec information of the child view * after View measure measures the view, we will know that after the view size is measured, we can obtain its width and height through getMeasuredWidth and getMeasuredHeight * View MeasureSpec.getMode acquires the width or height mode (MeasureSpec.EXACTLY, MeasureSpec.AT_MOST, MeasureSpec.UNSPECIFIED) * View MeasureSpec.getSize obtains the width that the parent layout can give us, Height and size * View setMeasuredDimension setting measurement result * View layout (left Top Right Bottom) set the layout location * * several verification points getMeasuredHeight, When does getHeight have value conclusion: after onMeasure and onLayout * No problem if sub-view is relativeLayout and has sub-view * adding ok through addView has been verified * / class ZSFlowLayout: ViewGroup {/ / Save all sub-view save each line may have multiple view all is a list var allViews: MutableList = mutableListOf () / / horizontal spacing val horizontalSpace between each sub-view : Int = resources.getDimensionPixelOffset (R.dimen.zs_flowlayout_horizontal_space) / / spacing between lines val verticalSpace: Int = resources.getDimensionPixelOffset (R.dimen.zs_flowlayout_vertical_space) / / the required constructor var lineHeights: MutableList = mutableListOf () constructor (context: Context): super (context) {} / * is used when recording the row height of each row. The system will call this constructor through reflection to complete the creation of view * / constructor (context: Context, attr: AttributeSet): super (context, attr) {} constructor (context: Context, attr: AttributeSet, defZStyle: Int): super (context, attr, defZStyle) {} override fun onMeasure (widthMeasureSpec: Int) HeightMeasureSpec: Int) {/ / measures the allViews.clear () lineHeights.clear () / / saves the view var everyLineViews of each line: MutableList = mutableListOf () / / records the current width of each line Used to determine whether to wrap the line var curLineHasUsedWidth: Int = paddingLeft + paddingRight / / the width that the parent layout can give val selfWidth: Int = MeasureSpec.getSize (widthMeasureSpec) / / the high val selfHeight that the parent layout can give: Int = MeasureSpec.getSize (heightMeasureSpec) / / We measure the width we need (wrap_content will use this if the user sets the width of the ZSFlowLayout in the layout ) var selfNeedWidth = 0 / / We measure the desired height ourselves (wrap_content will use this if the user sets the height of ZSFlowLayout in the layout) var selfNeedHeight = paddingBottom + paddingTop var curLineHeight = 0 / / the first step to measure the quantum view core system method is the View measure method / / (1) because there are many sub-view So loop through the execution of for (I in 0 until childCount) {val childView = getChildAt (I) if (childView.visibility = = GONE) {continue} / / before measuring the view, prepare the parameters needed for the measurement to get the MeasureSpec information of the child view through ViewGroup getChildMeasureSpec val childWidthMeasureSpec = getChildMeasureSpec (widthMeasureSpec PaddingLeft + paddingRight, childView.layoutParams.width) val childHeightMeasureSpec = getChildMeasureSpec (heightMeasureSpec, paddingTop + paddingBottom, childView.layoutParams.height) / / call the measure method of the sub-view to measure the childView.measure of the sub-view (childWidthMeasureSpec ChildHeightMeasureSpec) / / you can get the width and height of the sub-view after measurement, and save it to determine whether to wrap the line and the total height needed val measuredHeight = childView.measuredHeight val measuredWidth = childView.measuredWidth / / before saving the view by line to determine whether a new line is needed If necessary, save if (curLineHasUsedWidth + measuredWidth > selfWidth) {/ / to wrap in the list of the next line. Record the data before the line break lineHeights.add (curLineHeight) selfNeedHeight + = curLineHeight + verticalSpace allViews.add (everyLineViews) / / and then process the view correlation number of the current line break. According to curLineHeight = measuredHeight everyLineViews = mutableListOf () curLineHasUsedWidth = paddingLeft + paddingRight + measuredWidth + horizontalSpace} else {/ / the height of each line is the highest curLineHeight = curLineHeight.coerceAtLeast (measuredHeight) curLineHasUsedWidth + = measuredWidth + horizontalSpace} everyLineViews in this line of view. .add (childView) selfNeedWidth = selfNeedWidth.coerceAtLeast (curLineHasUsedWidth) / / process the last line if (I = = childCount-1) {curLineHeight = curLineHeight.coerceAtLeast (measuredHeight) allViews.add (everyLineViews) selfNeedHeight + = curLineHeight lineHeights.add (curLineHeight)}} / / step 2 measure yourself / / according to the size rules passed in by the parent class widthMeasureSpec, HeightMeasureSpec gets the layout pattern that it should follow / / take widthMeasureSpec as an example to show that this is passed in by the parent class. So how does the parent class generate widthMeasureSpec according to what rules? / / the parent class will combine its own situation And combined with the case of sub-view (the widths of subclasses are match_parent, wrap_content, For the specific logic of generating / / generating by writing dead values, please see ViewGroup's getChildMeasureSpec method / / (1) get the size pattern val widthMode = MeasureSpec.getMode (widthMeasureSpec) val heightMode = MeasureSpec.getMode (heightMeasureSpec) / / (2) the final width and height val widthResult = if (widthMode) passed by the parent class. = = MeasureSpec.EXACTLY) selfWidth else selfNeedWidth val heightResult = if (heightMode = = MeasureSpec.EXACTLY) selfHeight else selfNeedHeight / / step 3 set your own measurement result setMeasuredDimension (widthResult HeightResult)} override fun onLayout (changed: Boolean, l: Int, t: Int, r: Int B: Int) {/ / set the location of all view var curT = paddingTop for (I in allViews.indices) {val mutableList = allViews [I] / / record the current position of each row of view to the left of the parent layout. The initial value is the paddingLeft var curL = paddingLeft if (I! = 0) { CurT + = lineHeights [I-1] + verticalSpace} for (j in mutableList.indices) {val view = mutableList [j] val right = curL + view.measuredWidth val bottom = curT + view.measuredHeight view.layout (curL CurT, right, bottom) / / prepare for the next view curL + = view.measuredWidth + horizontalSpace}
Use in layout files:
You can also dynamically add view to your code (closer to actual combat, where most of the data is requested by the background)
Class FlowActivity: AppCompatActivity () {@ BindView (id = R.id.activity_flow_flowlayout) var flowLayout: ZSFlowLayout? = null Override fun onCreate (savedInstanceState: Bundle?) {super.onCreate (savedInstanceState) setContentView (R.layout.activity_customview_flow) BindViewInject.inject (this) for (I in 1 until 50) {val tv:TextView = TextView (this) tv.text = "TextView $I" flowLayoutflows. AddView (tv)}}
BindViewInject is a gadget class implemented with reflection and annotations.
Object BindViewInject {/ * inject * * @ param activity * / @ JvmStatic fun inject (activity: Activity) {inject (activity, false)} fun inject (activity: Activity) IsSetOnClickListener: Boolean) {/ / the first step is to get the class object val aClass: Class = activity.javaClass / / the second step is to get all the member variables defined by the class itself val declaredFields = aClass.declaredFields / / the third step is to traverse to find the annotated attribute for (I in declaredFields.indices) {val field = declaredFields [I] / / determine whether to use BindView to annotate if (field.isAnnotationPresent (BindView::class.java)) {/ / get the annotation object val bindView = field.getAnnotation (BindView::class.java) / / get the id value on the annotation object. This is the id val id = bindView.id if (id) of view.
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.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.