自定义view系列---继承view实现TextView效果

自定义view系列,前面讲述的案例大部分是通过View的onDraw()来绘制view外观,没有考虑view的测量部分。本篇我们就来重点关注下view的测量。
自定义view是要关注三个方法的重写。

1,构造方法

// 在代码中创建组件时使用 Button	btnOK	=	new	Button(this)
public	FirstView(Context	context)
// 在layout布局文件中使用
public	FirstView(Context	context,	AttributeSet	attrs)
// 在两个参数的构造方法中手动调用(当我们在Theme中定义了Style属性时)
public	FirstView(Context	context,	AttributeSet	attrs,	int	defStyleAttr)

2,绘图

protected	void	onDraw(Canvas	canvas)

这个方法我们在熟悉不过了,前面的案例都在重写这个方法,用于显示组件的外观。在View类中,该方法并没有任何默认的实现。

3,测量尺寸

protected	void	onMeasure(int	widthMeasureSpec,	int	heightMeasureSpec)

自定义view的大小都是用过自身onMeasure()进行测量的,不管界面多复杂,每个组件都负责计算自己的大小。

下面就来重点讲解onMeasure方法

View.java的onMeasure的具体实现如下

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

通过setMeasuredDimension(width,height)设置测量后的高度和宽度。以后我们就可以通过getMeasureWidth 和 getMeasureHeight方法获取这个组件的宽和高了。

当然不同的组件,宽高的计算也就不同了,我们自定义组件onMeasure时就是要根据实际的需求来计算组件的宽高了。

这里插播一个知识点:onMeasure中传入的宽度和高度,包含了两个信息:模式和大小。
模式有3种,也分别对应大小的3种获取方式:

模式大小
match_parent父布局的大小 决定自定义view的大小(onMeasure传入)
wrap_content根据自定义view的实际情况来
具体值view大小就是这个具体值了(onMeasure传入)

其中 “根据自定义view的实际情况来” 就是说,比如自定义view是TextView,那么就是根据TextView中的Text文本长度来决定最终view的宽高,如果自定义view是ImageView,那么就是根据设置的图片大小来决定自定义view的宽高了。

那么我们如何从onMeasure中传入int widthMeasureSpec, int heightMeasureSpec两个值,去跟分别获取 宽高的模式和大小呢?

这里有个小技巧:int 类型占用4个字节,一共32位,参数widthMeasureSpec 和 heightMeasureSpec的前两位代表模式,后30为代表大小
在这里插入图片描述
这样就能做到一个值包含两个信息了。

知道了这个信息,接下来就是要如何将这两个值计算出来,这里涉及到位运算,相信大家都知道位运算的左移 和 右移吧。但是这里系统考虑到这是一个公共且会被频繁使用的操作,就给我们提供了现成的计算方法。

int	mode =	MeasureSpec.getMode(widthMeasureSpec);
int	size =	MeasureSpec.getSize(widthMeasureSpec);

这样我们就能得到了 模式 和 大小 两个信息。在根据上面的表格,我们就能写出计算自定义view大小的逻辑啦。

@Override
protected void onMeasure(int widthMeasureSpec,	int	heightMeasureSpec)	{
	 int width = measureWidth(widthMeasureSpec);
	 int height = measureHeight(heightMeasureSpec);
	 setMeasuredDimension(width, height);
}

//在分别给出measureWidth 和 measureHeight
private	int	measureWidth(int widthMeasureSpec){
	 int mode =	MeasureSpec.getMode(widthMeasureSpec);
	 int size =	MeasureSpec.getSize(widthMeasureSpec);
	 int width	=	0;
	 if(mode ==	MeasureSpec.EXACTLY){
		 //宽度为 match_parent 和具体值时,直接将 size 作为组件的宽度
		 width	=	size;
	 }else	if(mode	==	MeasureSpec.AT_MOST){
	 	//宽度为 wrap_content,宽度需要计算
	 }
	 return	width;
}


private	int	measureHeight(int	heightMeasureSpec){
	 int mode =	MeasureSpec.getMode(heightMeasureSpec);
	 int size =	MeasureSpec.getSize(heightMeasureSpec);
	 int height	= 0;
	 if(mode ==	MeasureSpec.EXACTLY){
		 //宽度为 match_parent 和具体值时,直接将 size 作为组件的高度
		 height	=	size;
	 }else	if(mode	== MeasureSpec.AT_MOST){
		 //高度为 wrap_content,高度需要计算
	 }
	 return	height;
}

以上代码给出了自定义view的onMeasure的标准写法,我们只需要根据实际情况在mode == MeasureSpec.AT_MOST中给出该view的计算逻辑即可。

现在回到我们本篇的目标:继承view实现TextView的效果(单行TextView)。
对于match_parent 和 具体数值 来说,不用计算宽高,我们只用考虑 wrap_content的情况。那么对于TextView来说,wrap_content的是宽高其实就是其包含的文字宽高了,所以问题就转到怎么计算文字的宽高。

又是一个关于文字的知识点了。
在这里插入图片描述

  • baseline:基准点;
  • ascent:baseline 之上至字符最高处的距离;
  • descent:baseline 之下至字符最低处的距离;
  • top:字符可达最高处到 baseline 的值,即 ascent 的最大值;
  • bottom:字符可达最低处到 baseline 的值,即 descent 的最大值

文字的这些信息使用Paint.FontMetrics类来表示

public static class FontMetrics	{
	public	float top;
	public	float ascent;
	public	float descent;
	public	float bottom;
	public	float leading;
}

在调用Canvas的drawText方法,传入的x值就是文字的宽度,传入的y值就是baseline,
文字宽度计算方法如下:

//矩形宽度即是文字的宽度
private	Rect getTextRect(){
	 //根据 Paint 设置的绘制参数计算文字所占的宽度
	 Rect rect = new	Rect();
	 //文字所占的区域大小保存在 rect 中
	 paint.getTextBounds(TEXT,	0,	TEXT.length(),	rect);
	 return	rect;
}

basline的计算公式如下:

int	baseline = height /	2 + (fontMetrics.descent - fontMetrics.ascent) / 2	- fontMetrics.descent

到这里,onMeasure中需要的所有信息都得到了,现在我们将自定义的TextView代码完整的贴出来,其中的关键部分前面都提到了。

package com.example.cyy.customerview.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.example.cyy.customerview.R;

/**
 * Created by cyy on 2020/7/23.
 * 实现单行TextView
 */

public class MyTextView extends View {

    private String text = "Android 自定义组件";
    private Paint paint;

    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setTextSize(60);
        paint.setColor(Color.RED);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
        String customerText = typedArray.getString(R.styleable.MyTextView_text);
        Log.e("cyy","customerText:"+customerText);
        if(customerText != null){
            text = customerText;
        }

        typedArray.recycle();
    }
    //int baseline = height	/ 2	+ (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Rect textRect = getTextRect();

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();

        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        int x = (width - textRect.width()) / 2;
        int y = (int) (height / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);

        canvas.drawText(text,x,y,paint);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Rect rect = getTextRect();
        int textWidth = rect.width();
        int textHeight = rect.height();

        int width = measureWidth(widthMeasureSpec,textWidth);
        int height = measureHeight(heightMeasureSpec,textHeight);

        setMeasuredDimension(width,height);

    }

    private Rect getTextRect(){

        Rect rect = new Rect();
        paint.getTextBounds(text,0,text.length(),rect);

        return rect;
    }


    private int measureWidth(int widthMeasureSpec, int textWidth){

        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        int width = 0;
        if(mode == MeasureSpec.EXACTLY){
            width = size;

        }else if(mode == MeasureSpec.AT_MOST){
            width = textWidth;
        }
        return width;
    }

    private int measureHeight(int heightMeasureSpec, int textHeight){
        int height = 0;
        int size = MeasureSpec.getSize(heightMeasureSpec);
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        if(mode == MeasureSpec.EXACTLY){
            height = size;
        }else if(mode == MeasureSpec.AT_MOST){
            height = textHeight;
        }

        return height;
    }

}

DONE
此系列后续持续更新。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值