自定义View的流程?

  1. 自定义属性(在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" />
  1. 测量 重写onMeasure方法,只需要测量当前自定义的控件
  • 测量当前自定义控件的宽高
  1. 绘制 重写onDraw方法,绘制当前控件
  • 布局确定自定义控件在屏幕的坐标,由于我们的自定义控件一般都是嵌套在xml布局文件中使用,在xml布局文件中可以确定自定义控件的坐标,所以我们在自定义View的时候可以不用关心布局onLayout(),只需要关心绘制onDraw()
  1. 交互

自定义ViewGroup的流程?

利用流式布局FlowLayout来举例说明

  1. 自定义属性,然后在xml中使用
  2. 测量onMeasure
  • 根据测量的子View宽高,来计算父容器的宽高,然后通过setMeasureDimension()保存宽高,给自定义ViewGrouponLayout()使用,通过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_MOSTMeasureSpec.UNSPECIFIED,根据子View测量的宽高来计算自定义ViewGroup的宽高,然后保存给onLayout()使用.

总结二:
自定义ViewGroup 在测量时候,ViewGroup在测量时需要注意padding,而里面的子View在测量的时需要注意margin,否则会导致测量的控件大小不对;
在布局时候,在布局子View时,仍然需要注意marginpadding的问题

  1. 布局onLayout
  • 根据计算规则确定子View的位置
  • 具体步骤:
  • 获取到每一个子View,实现方式:child = getChildAt(i);
  • 确定每一个子View的坐标;
  • 确定子View的坐标时,就会用到上面onMeasure方法中测量的子View宽和高,来确定下一个子View的坐标
  • 依次确定将每一个子View布局的位置,实现方式:child.layout(left, top, right, bottom)

总结:
依次将每一个子View布局在屏幕的具体坐标上;
所谓换行,就是将子View布局的坐标更该,即可达到换行的效果

  1. 绘制 一般情况下都是不会使用绘制方法
  • onDraw正常情况下是不会调用的,而是调用dispatchDraw方法,所以要绘制的话可以重写dispatchDraw方法;
  1. 交互

总结:

  1. 自定义View中需要处理自己的padding,而由父容器处理自定义View设置的margin;
  2. ViewGroup中重写generateLayoutParams(LayoutInflater.inflat()方法中会调用到该方法)方法并返回创建的MarginLayoutParams,然后通过子View(MarginLayoutParams)child.getLayoutParams()可以获取到上下左右间距(leftMargin…).
    注意:
    ViewGrouponMeasure()测量子View需要用到measureChildWithMargins方法,而不是measureChild方法.