「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」
Android View和ViewGroup的关系
是不是经常在自定义View中看到View 和ViewGroup,如果你对自定义View很熟悉,则可以跳过这篇文章了, 如果你只是初步了解,这篇文章或许能让你理解的更透传一些。
关系
View 是单个的组件,类似于文字组件、图片组件、输入框组件;
ViewGroup 是一个容器,类似于线性布局、相对布局等
所以在自定义View的时候,取决于你想自定义一组组件,还是单个组件。
但是要注意的是,ViewGroup的父类是View,所以它也具有View的特征,可以实现View的方法,但它主要用来充当View的容器。
自定义View
其实我们平时的用的组件,都算是自定义View,只是系统帮我们实现好了而已。来看看TextView的实现方法,它也是继承了VIew
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
...
}
复制代码
我们来模仿者实现一个自适应的自定义VIew文字组件吧。
在撸代码前,先来了解一下基本概念和View中常用的方法。
我们想一下,我们自己在涂鸦之前,是不是要知道在哪里画、画多大、画成什么样,然后带着这些理解下面的方法。
-
onLayout: 决定组件的位置。当此视图应为其每个子项分配大小和位置时,从布局调用。带有子级的派生类应该覆盖此方法并在其每个子级上调用布局。
-
onMeasure:决定组件的大小。测量视图及其内容以确定测量的宽度和测量的高度。此方法measure(int, int)由子类调用并应由子类覆盖,以提供对其内容的准确有效测量。
-
onDraw: 组件画成什么样。 执行此操作以进行绘图。
准备工作都齐全了,现在就准备画图。
- 自定义属性定义。在value文件夹下创建attrs.xml文件
<resources>
<declare-styleable name="MyTextView">
<attr name="myText" format="string" />
<attr name="myTextSize" format="dimension" />
</declare-styleable>
</resources>
复制代码
- 在layout文件中写入自定义的View
<com.demo.demo.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:myText="测试"
app:myTextSize="30sp"
android:background="@color/purple_200"
/>
复制代码
- 自定义View的类
public class MyTextView extends AppCompatTextView {
private String text; // 文字
private int textSize; // 文字大小
private Rect bound;
private Paint paint;
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
//获取自定义属性的值
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
text = a.getString(R.styleable.MyTextView_myText);
textSize = (int) a.getDimension(R.styleable.MyTextView_myTextSize, 100);
a.recycle();
paint = new Paint();
paint.setTextSize(textSize);
//获得绘制文本的宽和高
bound = new Rect();
Log.d("text:", "text:" + text);
paint.getTextBounds(text, 0, text.length(), bound);
}
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//指定控件的宽高,需要测量
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST) {
//在布局中指定了wrap_content
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
widthSize = bounds.width() + getPaddingLeft() + getPaddingRight();
} else if (widthMode == MeasureSpec.EXACTLY) {
} else if (widthMode == MeasureSpec.UNSPECIFIED) {//尽可能的大,很少能用到
}
if (heightMode == MeasureSpec.AT_MOST) {
//在布局中指定了wrap_content
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
heightSize = bounds.height() + getPaddingTop() + getPaddingBottom();
} else if (heightMode == MeasureSpec.EXACTLY) {//在布局中指定了确定的值 比如:100dp / match_parent
} else if (heightMode == MeasureSpec.UNSPECIFIED) {//尽可能的大,很少能用到
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
int dy = (int) ((fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);
//得到基线(BaseLine)
int baseLine = getHeight() / 2 + dy;
int x = getPaddingLeft();
canvas.drawText(text, x, baseLine, paint);
}
}
复制代码