自定义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
方法.