最近在看viwe的自定义,看到蛮多讲解FlowLayout自定义ViewGroup的例子,然后回顾了一下《Android自定义控件开发入门与实战》里面的这个例子,想找一个kotlin的顺便看看,但是都没有,所以就写了这个博客记录一下
自定义view的文章应该很多,我就简单记录一下重点
构造函数->onMeasure()(测量View的大小)-onSizeChanged()()->onLayout()(确定子View布局)->onDraw()(开始绘制内容)->invalidate()(重绘刷新)
view主要实现:onMeasure() + onDraw()
vierGroup主要实现:onMeasure()+onLayout()
(图是截图自:https://blog.csdn.net/heng615975867/article/details/80379393)
要提取margin值,就一定要先重写generateLayoutParams()方法
要提取margin值,就一定要先重写generateLayoutParams()方法
要提取margin值,就一定要先重写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);
}
计算容器的宽高,需要根据遍历所有的childView来确定最大的宽高,同时需要根据measureWidthMode来判断
思路就是记录当前行的宽高,然后for循环计算每个childView的情况,取出最后的最大width和height,然后重点调用 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);
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;
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
}
}
}
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.创建textview_shape
android:shape="rectangle">
2.布局中引用