自定义View有下面几个方法需要去关注:
onFinishInflate() 回调方法,当应用从XML加载该组件并用它构建界面之后调用的方法
onMeasure() 检测View组件及其子组件的大小
onLayout() 当该组件需要分配其子组件的位置、大小时
onSizeChange() 当该组件的大小被改变时
onDraw() 当组件将要绘制它的内容时
onKeyDown 当按下某个键盘时
onKeyUp 当松开某个键盘时
onTrackballEvent 当发生轨迹球事件时
onTouchEvent 当发生触屏事件时
onWindowFocusChanged(boolean) 当该组件得到、失去焦点时
onAtrrachedToWindow() 当把该组件放入到某个窗口时
onDetachedFromWindow() 当把该组件从某个窗口上分离时触发的方法
onWindowVisibilityChanged(int): 当包含该组件的窗口的可见性发生改变时触发的方法
今天在学习Android下拉列表实现时,发现理解onMeasure方法的原理对自定义View尤为重要。
在View绘制过程中,系统通常会调用measure, layout, draw方法去进行控件的大小计算,布局及绘制,这些方法里面分别会调用下面三个方法。
1. onMeasure 用来决定View的实际大小(包括宽和高),View的大小由父View的规格限制和当前View自身的大小共同决定。
2. onLayout 用来对子View进行布局,它用来决定当前View(ViewGroup)中各个子控件的位置。在自定义View时,这个方法通常为空。
3. onDraw 进行控件的实际绘制工作。
今天主要学习一下onMeasure方法,下面是onMeasure方法的原型。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
widthMeasureSpec指定了父View对子View宽度的一个限定。子View可以结合
父View对自己的规格限制以及
显示自身内容需要的宽度来决定最后实际的宽度。
heightMeasureSpec的作用于widthMeasureSpec一致。
MeassureSpec标识父View对子View宽或高的一个限定,这个限定包括两部分:模式和大小。最高两位表示模式,低30位表示限定的大小。
通常,我们会根据父View传进来的模式来决定如何使用父View传进来的大小。
AT_MOST:表示当前View的宽或高可以为任意值,但是必须小于或者等于父View传进来的大小。
EXACTLY:当前View的大小必须等于父View传进来的大小。
UNSPECIFIED:当前View的大小可以是任意值,与父View传进来的大小无关。
另外,我们在使用Paint绘制字体时,需要搞清楚baseline, ascent, descent的关系,其中,ascent是一个负值。
按照我的理解:一行字的高度 = paint.descent - paint.ascent
下面自己实现一个LabelView:
values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="textSize" format="dimension"/>
<attr name="textColor" format="color"/>
<attr name="text" format="string"/>
<declare-styleable name="LabelView">
<attr name="textSize" />
<attr name="textColor" />
<attr name="text" />
</declare-styleable>
</resources>
layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<span style="color:#ff0000;"><strong>xmlns:tianbei="http://schemas.android.com/apk/res/com.tianbei.pull2refresh"</strong></span>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:orientation="vertical"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.tianbei.pull2refresh.MainActivity">
<com.tianbei.pull2refresh.LabelView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tianbei:textColor="#FF00FF00"
tianbei:text="alibaba-inc.com"/>
</LinearLayout>
LabelView.java
package com.tianbei.pull2refresh;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* Created by freei on 2016/1/23.
*/
public class LabelView extends View {
private static final String TAG = "mycustomview";
private Paint mTextPaint = null;
private String mText = "";
public LabelView(Context context) {
this(context, null);
}
public LabelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LabelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initLabelView();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelView);
mText = a.getString(R.styleable.LabelView_text);
setText(mText);
setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000));
setTextSize((int) a.getDimension(R.styleable.LabelView_textSize, 16 * getResources().getDisplayMetrics().density));
a.recycle();
}
private void initLabelView() {
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
mTextPaint.setColor(0xFF000000);
setPadding(3, 3, 3, 3);
}
public void setText(String text) {
if(text != null) {
mText = text;
requestLayout();
invalidate();
}
}
public void setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
}
public void setTextSize(int textSize) {
if(textSize > 0){
mTextPaint.setTextSize(textSize);
requestLayout();
invalidate();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
Log.d(TAG, "width = " + getMeasuredWidth() + " ,height = " + getMeasuredHeight());
}
private int measureWidth(int widthMeasureSpec) {
int width = 0;
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int actualWidth = getPaddingLeft() + getPaddingRight();
if(mText != null && mText.length() > 0) {
actualWidth = (int) (mTextPaint.measureText(mText) + actualWidth);
}
switch (specMode) {
case MeasureSpec.AT_MOST:
width = Math.min(actualWidth, specWidth);
break;
case MeasureSpec.EXACTLY:
width = specWidth;
break;
case MeasureSpec.UNSPECIFIED:
width = actualWidth;
break;
}
return width;
}
private int measureHeight(int heightMeasureSpec) {
int height = 0;
int specHeight = MeasureSpec.getSize(heightMeasureSpec);
int specMode = MeasureSpec.getMode(heightMeasureSpec);
Log.d(TAG, "ascent = " + mTextPaint.ascent() + " ,descent = " + mTextPaint.descent());
int actualHeight = (int) ((-mTextPaint.ascent() + mTextPaint.descent())) + getPaddingTop() + getPaddingBottom();
switch (specMode) {
case MeasureSpec.AT_MOST:
height = Math.min(actualHeight, specHeight);
break;
case MeasureSpec.EXACTLY:
height = specHeight;
break;
case MeasureSpec.UNSPECIFIED:
height = actualHeight;
break;
}
return height;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mText != null) {
float textHeight = (int) (mTextPaint.descent() - mTextPaint.ascent());
int y = (int) (textHeight / 2 + (-mTextPaint.ascent() - textHeight / 2));
int x = (int) ((getMeasuredWidth() - mTextPaint.measureText(mText)) / 2);
canvas.drawText(mText, x, y, mTextPaint);
}
}
}