Every accumulation can let you have a lot of growth, with the growth of age, social pressure, pressure at home, work pressure, as well as the drive to their own, will make themselves more and more uncomfortable, but hold on, come on, brothers, slowly, it will be ok, really, it will be ok.Copy the code
Before we explained some basic knowledge of custom View, I will use the custom View I wrote (used in our project), to analyze, by the way, we have learned before. Here I did not write source code analysis, such as LinearLayout, the principle is the same, we should also be able to understand, we only need to focus on onMeasure and onLayout on the line.
This custom View implements GridLayout functionality, which equalizes the specified width for a GridLayout. But I’m not thinking about Margin here.
open class GridFlowLayout : ViewGroup { constructor(context: Context?) : super(context) constructor(context: Context? , attrs: AttributeSet?) : super(context, attrs) constructor(context: Context? , attrs: AttributeSet? , defStyleAttr: Int) : super(context, attrs, defStyleAttr) companion object { private const val DEFAULT_COUNT_EACH_LINE = 3 } var itemCountEachLine = DEFAULT_COUNT_EACH_LINE var itemHorizontalDistance = 0 var itemVerticalDistance = 0 Override Fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) Var sizeWidth = MeasureSpec. GetSize (widthMeasureSpec) val sizeHeight = MeasureSpec MeasureSpec.getSize(heightMeasureSpec) val modeHeight = MeasureSpec.getMode(heightMeasureSpec) var childView: View // Now that we want to achieve equality, calculate the width that a single grid row needs to occupy. val eachChildWidth = (sizeWidth - paddingLeft - paddingRight - (itemCountEachLine - 1) * itemHorizontalDistance) / ItemCountEachLine var totalHeight = 0 Var itemLineIndex = 0 var itemLineMaxHeight = 0 var itemLineMaxHeight = 0 For (I in 0 until childCount) {++itemLineIndex childView = getChildAt(I) val lp = childView.layoutParams // The first measurement is to get the height, at this time the width may be inaccurate, because we want a row of views divided into a row of width // grid layout, more constraints of the parent View, MeasureChild (childView, widthMeasureSpec, heightMeasureSpec) = measureChild(childView, widthMeasureSpec, heightMeasureSpec) So in the parent View with the specified width, Child View measurement mode / / type is exactly childView. LayoutParams = childView. LayoutParams. Apply {width = eachChildWidth} / / the second measure is to, // You manually changed the layout parameters to make the current child View work, ChildWidthMeasureSpec = if (lp.width == layoutparams.match_parent) {val width == = layoutparams.match_parent Math.max(0, measuredWidth - paddingLeft - paddingRight) MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) } else { getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, Lp. Width)} / / get the height of the MeasureSpec val childHeightMeasureSpec = the if (lp) height = = FrameLayout. LayoutParams. MATCH_PARENT) { val height = Math.max(0, measuredHeight - paddingTop - paddingBottom) MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) } else { getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, Lp.height)} // If the current View is a ViewGroup and its layout is changed, the inner View will be affected. ChildView. Measure (childWidthMeasureSpec, childHeightMeasureSpec) if (itemLineIndex == itemCountEachLine) { itemLineMaxHeight = Math.max(childView.measuredHeight, TotalHeight += itemLineMaxHeight itemLineMaxHeight = 0 itemLineMaxHeight = 0 itemLineIndex = 0} else {// The highest altitude itemLineMaxHeight = Math. Max (childView. MeasuredHeight, itemLineMaxHeight)}} / / if there are no line breaks, If (itemLineIndex < itemCountEachLine) {totalHeight += itemLineMaxHeight} val lineCount = if (childCount % itemCountEachLine == 0) childCount / itemCountEachLine else childCount / itemCountEachLine + 1 val measHeight = if (modeHeight == MeasureSpec.EXACTLY) sizeHeight else totalHeight + paddingTop + paddingBottom + (lineCount - 1) * ItemVerticalDistance setMeasuredDimension(sizeWidth, measHeight)} override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var left = paddingLeft var top = paddingTop var maxCountEachLine = 0 var childView: View for (i in 0 until childCount) { childView = getChildAt(i) ++maxCountEachLine childView.layout(left, top, left + childView.measuredWidth, top + childView.measuredHeight) left += childView.width + itemHorizontalDistance if (maxCountEachLine >= ItemCountEachLine) {// The current number of lines is enough, Left = paddingLeft maxCountEachLine = 0 top += childView.measuredHeight + itemVerticalDistance}}}}Copy the code
Summary: Here, we need to pay attention to the generation of MeasureSpec, as well as the measurement of the ViewGroup sub-view, and finally determine the size of the ViewGroup, the middle has experienced several measurements, to understand its content, why to measure many times, LinearLayout is the same.
At the same time, based on this component, in order to facilitate the use of the outside world, I carried out a secondary packaging, simulation RecyclerView, you can also have a look.
abstract class CacheContainer<T, R : CacheContainer.BaseItemViewHolder<T>> : GridFlowLayout { constructor(context: Context?) : super(context) constructor(context: Context? , attrs: AttributeSet?) : super(context, attrs) constructor(context: Context? , attrs: AttributeSet? , defStyleAttr: Int) : super(context, attrs, Private val showItemViewHolderList by lazy {showItemViewHolderList by lazy { Holder private Val cacheItemViewHolderList by lazy {mutableListOf<R>()} Private var dataList: MutableList<T>? = null fun bindData(newDataList: MutableList<T>?) {bindData(newDataList, null)} /** * bindData(newDataList, null) {bindData(newDataList, null)} MutableList<T>? , payloads: MutableList<Any>? = null) {/ / determine if data source (newDataList = = null | | newDataList. IsEmpty ()) {/ / this time, to show the View of the cache, At the same time for empty display View (itemViewHolder showItemViewHolderList) in {cacheItemViewHolderList. Add (itemViewHolder)} RemoveAllViews showItemViewHolderList. The clear () () return} / / populate the data Holder inflateAllViews (showItemViewHolderList, NewDataList) // Theoretically size is the same as the if (showItemViewHolderList. Size = = newDataList. Size) {/ / the bound data source dataList = newDataList / / add the View and rendering data for step by step ((index, itemViewHolder) in showItemViewHolderList.withIndex()) { itemViewHolder.update(index, newDataList[index], Payloads)}}} / / notifyDataSetChanged(payloads: MutableList<Any>? /** * notifyItemChanged(position: Int, payloads: MutableList<Any>? = null) { dataList? .let { list -> if (position < 0 || position >= showItemViewHolderList.size || position >= list.size) return showItemViewHolderList[position].update(position, list[position], / / private fun inflateAllViews(showDataList: MutableList<R>, newDataList: MutableList<T>) { var diffCount = showDataList.size - newDataList.size var itemViewHolder: For (I in 0 until diffCount) {itemViewHolder = showItemViewHolderList.removeAt(showItemViewHolderList.size - 1) removeViewAt(childCount - 1) CacheItemViewHolderList. Add (itemViewHolder)}} else if (diffCount < 0) {/ / indicates that the current quantity is not enough, Need to increase the diffCount = - diffCount / / first lookup cache if (diffCount < = cacheItemViewHolderList. Size) {/ / cache enough, First take the cache for (I in 0 until diffCount) {itemViewHolder = cacheItemViewHolderList. RemoveAt (cacheItemViewHolderList. Size - 1) AddView (itemViewHolder itemView) showItemViewHolderList. Add (itemViewHolder)}} else {/ / cache the number is not enough, take the cache first, Later created directly for (cacheItemViewHolder cacheItemViewHolderList) in {addView (cacheItemViewHolder. ItemView) ShowItemViewHolderList. Add (cacheItemViewHolder)} / / the rest of the created for (I in 0 until (diffCount - cacheItemViewHolderList.size)) { itemViewHolder = createItemViewHolder(LayoutInflater.from(context).inflate(getItemLayoutId(), this, False)) addView (itemViewHolder. ItemView) showItemViewHolderList. Add (itemViewHolder)} / / the cache, Clears out cacheItemViewHolderList cache list. The clear ()}} requestLayout ()} / * * * succeeded by external * / open class BaseItemViewHolder < T > (val itemView: View) { open fun update(position: Int, item: T?) {} open fun update(position: Int, item: T?, payloads: MutableList<Any>?) {update(position, item)}} /** * Layout id of each sub-view */ abstract fun getItemLayoutId(): CreateItemViewHolder (itemView: View): R}Copy the code
The code does not explain much, through the generic, similar RecyclerView way, to achieve the grid layout. You can imagine why I’m using generics, why I’m inheriting, why I’m caching, but it’s a good place to learn.