自定义View的基本方法
自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw();
View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。
- 测量:onMeasure()决定View的大小;
- 布局:onLayout()决定View在ViewGroup中的位置;
- 绘制:onDraw()决定绘制这个View。
自定义控件分类
- 自定义View: 只需要重写onMeasure()和onDraw()
- 自定义ViewGroup: 则只需要重写onMeasure()和onLayout()
自定义View基础
View的分类
视图View主要分为两类
类别 | 解释 | 特点 |
---|---|---|
单一视图 | 即一个View,如TextView | 不包含子View |
视图组 | 即多个View组成的ViewGroup,如LinearLayout | 包含子View |
View类简介
- View类是Android中各种组件的基类,如View是ViewGroup基类
- View表现为显示在屏幕上的各种视图
Android中的UI组件都由View、ViewGroup组成。
- View的构造函数:共有4个
// 如果View是在Java代码里面new的,则调用第一个构造函数
public CarsonView(Context context) {
super(context);
}
// 如果View是在.xml里声明的,则调用第二个构造函数
// 自定义属性是从AttributeSet参数传进来的
public CarsonView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 不会自动调用
// 一般是在第二个构造函数里主动调用
// 如View有style属性时
public CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//API21之后才使用
// 不会自动调用
// 一般是在第二个构造函数里主动调用
// 如View有style属性时
public CarsonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
AttributeSet与自定义属性
系统自带的View可以在xml中配置属性,对于写的好的自定义View同样可以在xml中配置属性,为了使自定义的View的属性可以在xml中配置,需要以下4个步骤:
- 通过
<declare-styleable>
为自定义View添加属性 - 在xml中为相应的属性声明属性值
- 在运行时(一般为构造函数)获取属性值
- 将获取到的属性值应用到View
代码如下
package com.zthx.deviceui.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.zthx.deviceui.R;
import java.util.ArrayList;
import java.util.List;
/**
* 类名称: FlowLayout.java<p>
* 类描述: 自定义流式ViewGroup<p>
*
*
* @author darryrzhong
* @since 2019/12/11 9:37
*/
public class FlowLayout extends ViewGroup {
/**
* 每一行的子view
* */
private List<View> lineView;
/**
* 整个布局的所有行
* */
private List<List<View>> lines;
/**
* 每一行的高度
* */
private List<Integer> heights;
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init(){
lineView = new ArrayList<>();
lines = new ArrayList<>();
heights = new ArrayList<>();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//测量自身
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取限制信息的 mode和 size
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//记录当前行的宽度个高度
//宽度是当前行子view的宽度之和
int lineWidth = 0;
//高度是当前行所有子view中高度的最大值
int lineHeight = 0;
//整个流式布局的宽度和高度
//所有行中宽度的最大值
int flowLayoutWidth = 0;
//所有行的高度叠加
int flowLayoutHeight = 0;
//初始化参数列表
init();
//遍历所有的子view,对子view进行measure,分配到具体的行
int countView = getChildCount();
for (int i = 0;i<countView;i++){
View child = this.getChildAt(i);
//测量子View 获取到当前子View的测量的宽度 /高度
measureChild(child,widthMeasureSpec,heightMeasureSpec);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
LayoutParams lp = (LayoutParams) child.getLayoutParams();
//看下当前的行的剩余的宽度是否可以容纳下一个子View
//如果放不下,换行 保存当前行的所有子View,累加行高,
//将当前行的 高度 和宽度 置零
if (lineWidth+childWidth > widthSize){
//换行
lines.add(lineView);
lineView = new ArrayList<>();
flowLayoutWidth = Math.max(flowLayoutWidth,lineWidth);
flowLayoutHeight +=lineHeight;
heights.add(lineHeight);
lineHeight = 0;
lineWidth = 0;
}
lineView.add(child);
lineWidth +=childWidth;
lineHeight = Math.max(lineHeight,childHeight);
}
//FlowLayout最终宽高
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY? widthSize:flowLayoutWidth,
heightMode == MeasureSpec.EXACTLY?heightSize:flowLayoutHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int lineCount = lines.size();
int currX = 0;
int currY = 0;
//所有的子view,一行一行的布局
for (int i = 0;i<lineCount;i++){
//取出一行
List<View> lineViews = lines.get(i);
//取出这一行的高度值
int lineHeight = heights.get(i);
int lineSize = lineViews.size();
//遍历当前行的子View
for (int j=0;j<lineSize;j++){
View child = lineViews.get(j);
int left = currX;
int top = currY;
int right = left+child.getMeasuredWidth();
int bottom = top+child.getMeasuredHeight();
child.layout(left,top,right,bottom);
//确定下一个view的left
currX +=child.getMeasuredWidth();
}
currY += lineHeight;
currX = 0;
}
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(),attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return super.checkLayoutParams(p) && p instanceof LayoutParams;
}
public static class LayoutParams extends MarginLayoutParams{
public int gravity = -1;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
try {
gravity = array.getInt(R.styleable.FlowLayout_android_gravity,-1);
}finally {
array.recycle();
}
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
@Override
public String toString() {
return "LayoutParams{" +
"gravity=" + gravity +
", bottomMargin=" + bottomMargin +
", leftMargin=" + leftMargin +
", rightMargin=" + rightMargin +
", topMargin=" + topMargin +
", height=" + height +
", layoutAnimationParameters=" + layoutAnimationParameters +
", width=" + width +
'}';
}
}
}
布局如下:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<com.zthx.deviceui.view.FlowLayout 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">
<Button
android:layout_width="wrap_content"
android:layout_height="55dp"
android:text="view1" />
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="第二个view" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="三个三个view"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="90dp"
android:text="哈哈哈" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="不知道是谁" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="啦啦啦啦啦" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="滴答滴答"
android:layout_gravity="bottom"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="哈哈哈" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="啦啦啦啦啦" />
<Button
android:layout_width="wrap_content"
android:layout_height="45dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="他是谁,我是谁" />
</com.zthx.deviceui.view.FlowLayout>
</ScrollView>
欢迎关注作者darryrzhong,更多干货等你来拿哟.
请赏个小红心!因为你的鼓励是我写作的最大动力!
更多精彩文章请关注