onMeasure方法的使用和字体的baseline, ascent, descent

自定义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);
        }
    }
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值