Récemment, j'ai regardé la personnalisation de viwe et j'ai vu beaucoup d'exemples expliquant le ViewGroup personnalisé de FlowLayout. Ensuite, j'ai examiné l'exemple dans "Introduction et combat pratique du développement de contrôles personnalisés Android" et j'ai voulu en trouver un en kotlin pour y jeter un œil. , mais il n'y avait pas de Non, alors j'ai écrit ce blog pour l'enregistrer
Il devrait y avoir de nombreux articles sur la personnalisation des vues, je vais donc simplement enregistrer les points clés.
L'un est écrit en Java et l'autre en Kotlin. J'apprends actuellement Kotlin.
rendus
Relation de calcul de la position des coordonnées
1. Le dessin de View suit généralement
构造函数->onMeasure()(测量View的大小)-onSizeChanged()()->onLayout()(确定子View布局)->onDraw()(开始绘制内容)->invalidate()(重绘刷新)
view主要实现:onMeasure() + onDraw()
vierGroup主要实现:onMeasure()+onLayout()
(L'image est une capture d'écran de : https://blog.csdn.net/heng615975867/article/details/80379393)
2. Le point clé est que la méthode onMeasure() calcule la largeur et la hauteur du conteneur, la méthode onLayout() calcule la position (gauche, haut, droite, bas) de chaque enfantViwe et appelle view.onLayout() méthode pour dessiner
la première étape
Pour extraire la valeur de marge, vous devez d'abord réécrire la méthode generateLayoutParams().
Pour extraire la valeur de marge, vous devez d'abord réécrire la méthode generateLayoutParams().
Pour extraire la valeur de marge, vous devez d'abord réécrire la méthode generateLayoutParams().
/**
* 重写generateLayoutParams方法 为了提取Margin
*
* @param p
* @return
*/
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
Étape 2 : La méthode onMeasure() calcule la largeur et la hauteur du conteneur et la définit sur setMeasuredDimension()
Pour calculer la largeur et la hauteur du conteneur, vous devez parcourir toutes les vues enfants pour déterminer la largeur et la hauteur maximales. En même temps, vous devez juger en fonction de la mesure de la largeur. L'idée est d'enregistrer la largeur et la hauteur de la ligne actuelle
. , puis calculez la situation de chaque childView dans une boucle for, et retirez la largeur et la hauteur maximales finales, puis concentrez-vous sur l'appel de la méthode setMeasuredDimension()
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int lineWidth = 0;//记录每一行的宽度
int linHeight = 0;//记录每一行的高度
int totalWidth = 0;//记录整体的宽度
int totalHeight = 0;//记录整体的高度
int count = getChildCount();
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
//一定要先调用measureChild(),调用getMeasuredWidth() 才生效
measureChild(view, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
int viewWidth = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int viewHeight = view.getHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + viewWidth > measureWidth) { //当前的行宽+child的宽大于最大的测量宽度
//换行的情况
totalWidth = Math.max(lineWidth, viewWidth);
totalHeight += linHeight;
lineWidth = viewWidth;
linHeight = viewHeight;
} else {
//不换行的情况
linHeight = Math.max(linHeight, viewHeight);
lineWidth += viewWidth;
}
if (i == count - 1) {
totalHeight += linHeight;
totalWidth = Math.max(totalWidth, lineWidth);
}
}
//所以的工作都是为了确定容器的宽高
setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : totalWidth, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : totalHeight);
Étape 3 : Enregistrez et sauvegardez la position de chaque childView dans la méthode onLayout(), puis appelez view.layout(l,t,r,b) en fonction des coordonnées pour dessiner la vue
int count = getChildCount();
int lineWidth = 0;//累加当前行的行宽
int linwHeight = 0;//累加当前的行高
int top = 0, left = 0;//当前空间的top坐标和left坐标
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
int viewWidth = view.getMeasuredWidth() + lp.rightMargin + lp.leftMargin;
int viewHeight = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (viewWidth + lineWidth > getMeasuredWidth()) {
//如果换行
top += linwHeight;
left = 0;
linwHeight = viewHeight;
lineWidth = viewWidth;
} else {
linwHeight = Math.max(linwHeight, viewHeight);
lineWidth += viewWidth;
}
//计算view的left top right bottom
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + view.getMeasuredWidth();
int bc = tc + view.getMeasuredHeight();
view.layout(lc, tc, rc, bc);
//将left置为下一个子控件的起点
left += viewWidth;
utiliser
classe de version Kotlin FlowLayoutKotlin
package com.example.flowlayoutdemo
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
class FlowLayoutKotlin : ViewGroup {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, def: Int) : super(context, attrs, def)
override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
return MarginLayoutParams(p)
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
override fun generateDefaultLayoutParams(): LayoutParams {
return MarginLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val measureWidth = MeasureSpec.getSize(widthMeasureSpec)
val measureHeight = MeasureSpec.getSize(heightMeasureSpec)
val measureWidthMode = MeasureSpec.getMode(widthMeasureSpec)
val measureHeightMode = MeasureSpec.getMode(heightMeasureSpec)
var lineWidth = 0 //记录每一行的宽度
var linHeight = 0 //记录每一行的高度
var totalWidth = 0 //记录整体的宽度
var totalHeight = 0 //记录整体的高度
val count = childCount
for (i in 0 until count) {
val view = getChildAt(i)
//一定要先调用measureChild(),调用getMeasuredWidth() 才生效
measureChild(view, widthMeasureSpec, heightMeasureSpec)
val lp = view.layoutParams as MarginLayoutParams
val viewWidth = view.measuredWidth + lp.leftMargin + lp.rightMargin
val viewHeight = view.height + lp.topMargin + lp.bottomMargin
if (lineWidth + viewWidth > measureWidth) { //当前的行宽+child的宽大于最大的测量宽度
//换行的情况
totalWidth = Math.max(lineWidth, viewWidth)
totalHeight += linHeight
lineWidth = viewWidth
linHeight = viewHeight
} else {
//不换行的情况
linHeight = Math.max(linHeight, viewHeight)
lineWidth += viewWidth
}
if (i == count - 1) {
totalHeight += linHeight
totalWidth = Math.max(totalWidth, lineWidth)
}
}
//所以的工作都是为了确定容器的宽高
//所以的工作都是为了确定容器的宽高
setMeasuredDimension(
if (measureWidthMode == MeasureSpec.EXACTLY) measureWidth else totalWidth,
if (measureHeightMode == MeasureSpec.EXACTLY) measureHeight else totalHeight
)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//四个参数 当前行的宽高 容器的累计宽高 即宽度是取能获取的最大值,高度方向是累加的值
val count = childCount
var lineWidth = 0 //累加当前行的行宽
var linwHeight = 0 //累加当前的行高
var top = 0
var left = 0 //当前空间的top坐标和left坐标
for (i in 0 until count) {
val view = getChildAt(i)
val lp = view.layoutParams as MarginLayoutParams
val viewWidth = view.measuredWidth + lp.rightMargin + lp.leftMargin
val viewHeight = view.measuredHeight + lp.topMargin + lp.bottomMargin
if (viewWidth + lineWidth > measuredWidth) {
//如果换行
top += linwHeight
left = 0
linwHeight = viewHeight
lineWidth = viewWidth
} else {
linwHeight = Math.max(linwHeight, viewHeight)
lineWidth += viewWidth
}
//计算view的left top right bottom
val lc = left + lp.leftMargin
val tc = top + lp.topMargin
val rc = lc + view.measuredWidth
val bc = tc + view.measuredHeight
view.layout(lc, tc, rc, bc)
//将left置为下一个子控件的起点
left += viewWidth
}
}
}
Version Java de la classe FlowLayoutJava
package com.example.flowlayoutdemo;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public class FlowLayoutJava extends ViewGroup {
public FlowLayoutJava(Context context) {
super(context);
}
public FlowLayoutJava(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayoutJava(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 重写generateLayoutParams方法 为了提取Margin
*
* @param p
* @return
*/
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int lineWidth = 0;//记录每一行的宽度
int linHeight = 0;//记录每一行的高度
int totalWidth = 0;//记录整体的宽度
int totalHeight = 0;//记录整体的高度
int count = getChildCount();
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
//一定要先调用measureChild(),调用getMeasuredWidth() 才生效
measureChild(view, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
int viewWidth = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int viewHeight = view.getHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + viewWidth > measureWidth) { //当前的行宽+child的宽大于最大的测量宽度
//换行的情况
totalWidth = Math.max(lineWidth, viewWidth);
totalHeight += linHeight;
lineWidth = viewWidth;
linHeight = viewHeight;
} else {
//不换行的情况
linHeight = Math.max(linHeight, viewHeight);
lineWidth += viewWidth;
}
if (i == count - 1) {
totalHeight += linHeight;
totalWidth = Math.max(totalWidth, lineWidth);
}
}
//所以的工作都是为了确定容器的宽高
setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : totalWidth, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : totalHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
int lineWidth = 0;//累加当前行的行宽
int linwHeight = 0;//累加当前的行高
int top = 0, left = 0;//当前空间的top坐标和left坐标
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
int viewWidth = view.getMeasuredWidth() + lp.rightMargin + lp.leftMargin;
int viewHeight = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (viewWidth + lineWidth > getMeasuredWidth()) {
//如果换行
top += linwHeight;
left = 0;
linwHeight = viewHeight;
lineWidth = viewWidth;
} else {
linwHeight = Math.max(linwHeight, viewHeight);
lineWidth += viewWidth;
}
//计算view的left top right bottom
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + view.getMeasuredWidth();
int bc = tc + view.getMeasuredHeight();
view.layout(lc, tc, rc, bc);
//将left置为下一个子控件的起点
left += viewWidth;
}
}
}
1.Créer textview_shape
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
android:shape="rectangle">
<corners android:radius="3dp" />
<stroke android:color="#cc0033" android:width="1dp"/>
<padding android:top="2dp" android:bottom="2dp" android:left="2dp" android:right="2dp" />
</shape>
2. Référencé dans la mise en page
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.flowlayoutdemo.FlowLayoutJava
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="@drawable/textview_shape">
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="白茶清欢无别事"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="我在等风也等你"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="山高决定人为峰"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="海阔无涯天作岸"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="不应该"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="在"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="自怜中沉沦"
android:textColor="@android:color/black"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="每一次困难"
android:textSize="20sp" />
<TextView
style="@style/text_flow_style"
android:background="@drawable/textview_shape"
android:text="都看成是可以改变的机会"
android:textSize="20sp" />
</com.example.flowlayoutdemo.FlowLayoutJava>
</androidx.constraintlayout.widget.ConstraintLayout>