自定义View的流程?
- 自定义属性(在
attrs.xml
中定义),然后在布局文件(如:activity_main.xml
)中使用
- 在
attrs.xml
中通过declare-styleable
标签定义自定义控件的名称,然后再定义一个个自定义控件的属性,代码举例:
//在attrs.xml中定义属性
<declare-styleable name="CustomTextView">
<attr name="customColor1" format="color" />
<attr name="customColor2" format="color" />
</declare-styleable>
//在自定义控件中使用我们的属性,如:CustomTextView.java
//在三参构造函数中定义我们的属性
public CustomTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取属性集合
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustommTextView);
//获取属性集合中具体的属性
int customColor1 = typedArray.getColor(R.styleable.CustommTextView_customColor1, getTextColors().getDefaultColor());
int customColor2 = typedArray.getColor(R.styleable.CustommTextView_customColor2, getTextColors().getDefaultColor());
//回收TypedArray
typedArray.recycle();
}
//在xml布局文件中使用
<com.tangkun.customView.CustomTextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自定义控件属性举例"
android:textSize="20sp"
app:customColor1="@color/black"
app:customColor2="@color/white" />
- 测量 重写
onMeasure
方法,只需要测量当前自定义的控件
- 测量当前自定义控件的宽高
- 绘制 重写
onDraw
方法,绘制当前控件
- 布局确定自定义控件在屏幕的坐标,由于我们的自定义控件一般都是嵌套在
xml
布局文件中使用,在xml
布局文件中可以确定自定义控件的坐标,所以我们在自定义View
的时候可以不用关心布局onLayout()
,只需要关心绘制onDraw()
- 交互
自定义ViewGroup的流程?
利用流式布局
FlowLayout
来举例说明
- 自定义属性,然后在
xml
中使用 - 测量
onMeasure
- 根据测量的子
View
宽高,来计算父容器的宽高,然后通过setMeasureDimension()
保存宽高,给自定义ViewGroup
的onLayout()
使用,通过getMeasuredWidth()
和getMeasuredHeight()
- 具体步骤:
- 确定
ViewGroup
的测量模式和测量大小;测量模式分为宽和高的测量模式,测量大小分为宽和高的测量大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//扩展:我们可以通过下面的方式更改ViewGroup的测量模式,达到我们控件的展示效果
//heightMode = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY)
- 获取到每一个子
View
;
- 实现方式:
child = getChildAt(i)
; - 通过
getChildCount
获取到ViewGroup
中子View
的数量,然后根据索引得到每一个子View
,通过getChildAt(i)
得到具体的子View
- 测量每个子
View
的宽高;
- 通过
measureChild(child, parentWidthMeasureSpec, parentHeightMeasureSpec)
方法;
- 根据子
View
的宽高计算自定义ViewGroup
的宽高 - 保存测量的自定义
ViewGroup
宽和高;
- 通过这个方法来保存:
setMeasuredDimension(measuredWidth, measuredHeight);
总结一:
如果自定义ViewGroup
宽和高的测量模式是MeasureSpec.EXACTLY
,那么无需根据子View
测量的宽高来计算自定义ViewGroup
的宽高,直接使用自定义ViewGroup
的宽和高的测量大小保存即可;
如果自定义ViewGroup
宽和高的测量模式是MeasureSpec.AT_MOST
或MeasureSpec.UNSPECIFIED
,根据子View
测量的宽高来计算自定义ViewGroup
的宽高,然后保存给onLayout()
使用.总结二:
自定义ViewGroup
在测量时候,ViewGroup
在测量时需要注意padding
,而里面的子View
在测量的时需要注意margin
,否则会导致测量的控件大小不对;
在布局时候,在布局子View
时,仍然需要注意margin
和padding
的问题
- 布局
onLayout
- 根据计算规则确定子
View
的位置 - 具体步骤:
- 获取到每一个子
View
,实现方式:child = getChildAt(i)
; - 确定每一个子
View
的坐标;
- 确定子
View
的坐标时,就会用到上面onMeasure
方法中测量的子View
宽和高,来确定下一个子View
的坐标
- 依次确定将每一个子
View
布局的位置,实现方式:child.layout(left, top, right, bottom)
总结:
依次将每一个子View
布局在屏幕的具体坐标上;
所谓换行,就是将子View
布局的坐标更该,即可达到换行的效果
- 绘制
一般情况下都是不会使用绘制方法
-
onDraw
正常情况下是不会调用的,而是调用dispatchDraw
方法,所以要绘制的话可以重写dispatchDraw
方法;
- 交互
总结:
- 自定义
View
中需要处理自己的padding
,而由父容器处理自定义View
设置的margin
;- 在
ViewGroup
中重写generateLayoutParams
(LayoutInflater.inflat()方法中会调用到该方法)方法并返回创建的MarginLayoutParams
,然后通过子View
的(MarginLayoutParams)child.getLayoutParams()
可以获取到上下左右间距(leftMargin
…).
注意:
在ViewGroup
中onMeasure()
测量子View
需要用到measureChildWithMargins
方法,而不是measureChild
方法.