自定义一个TextView
package com.nyw.myviewdemo1.view;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
/**
* 在 MyTextView myTextView=new MyTextView(this);的时候调用
*/
public class MyTextView extends View {
public MyTextView(Context context) {
super(context);
}
/**
*在页面布局Layout中调用
* <com.nyw.myviewdemo1.view.MyTextView
* android:layout_width="match_parent"
* android:layout_height="wrap_content"
* android:text="Hello World!"
* tools:ignore="MissingConstraints" />
* @param context
* @param attrs
*/
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
/**
* 在布局layout中调用,有自定义style
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* 自定义View测量布局的宽高方法,我们这里重写
* 布局的宽高都是由这个方法指定
* 指定控件的宽高,需要测量
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取宽高模式
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int hightMode=MeasureSpec.getMode(heightMeasureSpec);
// MeasureSpec.AT_MOST;//在布局中指定控件宽高为wrap_content
// MeasureSpec.EXACTLY;//在布局中指定控件宽 高为固定大小
// MeasureSpec.UNSPECIFIED;//在布局中指定控件宽高为match_parent ,这个是尽可能大
}
/**
* 用于绘制方法
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// //画文本
// canvas.drawText();
// //根据图片Bitmap画出来
// canvas.drawBitmap();
// //画孤
// canvas.drawArc();
// //画圆
// canvas.drawCircle();
}
/**
* 处理用户交互的,手指点击移动等
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
}
自定义属性
在values中新建一个attrs的文件,在这个文件 中写一些自定义属性
<!--name 是自定义属性名称-->
<!--下面的name是属性名称,format是格式,string格式是文字,color颜色,dimension 宽高、字体大小,integer数字,reference资源(drawable)-->
注意name取名时不要与系统相同,比如TextView_text_background,刚开始写background就报错了,提示 Duplicate value for resource 'attr/background' with config 'DEFAULT' and product ''. Resource was previously defined here: ,后来改成TextView_text_background就正常运行起来了
<attr> </attr> 这是枚举
<attr name="inputType"> <enum name="number" value="1"/> <enum name="text" value="2"/> <enum name="password" value="3"/> </attr>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="myTextView_style">
<attr name="TextView_text" format="string"/>
<attr name="TextView_textColor" format="color"/>
<attr name="TextView_textSize" format="dimension"/>
<attr name="TextView_maxLength" format="integer"/>
<attr name="TextView_text_background" format="reference|color"/>
<attr name="TextView_inputType">
<enum name="number" value="1"/>
<enum name="text" value="2"/>
<enum name="password" value="3"/>
</attr>
</declare-styleable>
</resources>
在页面中调用 自定义属性
使用
xmlns:app="http://schemas.android.com/apk/res-auto"
app:text ="我是自定义TextView"
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<com.nyw.myviewdemo1.view.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:TextView_text ="我是自定义TextView"
app:TextView_textColor ="@android:color/background_dark"
app:TextView_textSize ="20sp"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:background="@color/teal_200"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
把上面的自定义TextView修改一下,代码如下
package com.nyw.myviewdemo1.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.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import com.nyw.myviewdemo1.R;
/**
* 在 MyTextView myTextView=new MyTextView(this);的时候调用
* extends View就可以了,extends LinearLayout 低版本可能不执行onDraw,最新版本测试可以使用
*/
public class MyTextView extends View {
//显示文本
private String text;
//文本显示大小
private int text_size=25;
//文本颜色显示,默认为黑色
private int text_color= Color.BLACK;
//画笔
private Paint paint;
public MyTextView(Context context) {
//这里设置为this,并且设置null,才执行第二个构造 方法
this(context,null);
}
/**
*在页面布局Layout中调用
* <com.nyw.myviewdemo1.view.MyTextView
* android:layout_width="match_parent"
* android:layout_height="wrap_content"
* android:text="Hello World!"
* tools:ignore="MissingConstraints" />
* @param context
* @param attrs
*/
public MyTextView(Context context, @Nullable AttributeSet attrs) {
//这里设置为this,然后设置0,执行到第三个构造方法
this(context, attrs,0);
}
/**
* 在布局layout中调用,有自定义style
* @param context
* @param attrs
* @param defStyleAttr
*/
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性
TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.myTextView_style);
text=typedArray.getString(R.styleable.myTextView_style_TextView_text);
text_color= typedArray.getColor(R.styleable.myTextView_style_TextView_textColor,text_color);
text_size=typedArray.getDimensionPixelOffset(R.styleable.myTextView_style_TextView_textSize,text_size);
//回收
typedArray.recycle();
paint=new Paint();
//抗锯齿,文字显示比较清楚
paint.setAntiAlias(true);
paint.setTextSize(text_size);
paint.setColor(text_color);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* 自定义View测量布局的宽高方法,我们这里重写
* 布局的宽高都是由这个方法指定
* 指定控件的宽高,需要测量
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取宽高模式
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int hightMode=MeasureSpec.getMode(heightMeasureSpec);
// MeasureSpec.AT_MOST;//在布局中指定控件宽高为wrap_content
// MeasureSpec.EXACTLY;//在布局中指定控件宽 高为固定大小
// MeasureSpec.UNSPECIFIED;//在布局中指定控件宽高为match_parent ,这个是尽可能大
//页面中宽度写固定大小的,直接获取到页面中的值,不需要计算
int width=MeasureSpec.getSize(sp2px(widthMeasureSpec));
//页面中使用了wrap_content需要计算 宽高
if (widthMode==MeasureSpec.AT_MOST){
//计算的宽度与字体长度、字体大小有关,使用画笔来测量
Rect rect=new Rect();
//获取文本的rect
paint.getTextBounds(text,0,text.length(),rect);
//这里添加上getPaddingLeft()和getPaddingRight() 在页面中设置paddingRight和paddingLeft才生效
width=rect.width()+getPaddingLeft()+getPaddingRight();
}
//页面中高度写固定大小的,直接获取到页面中的值,不需要计算
int height=MeasureSpec.getSize(sp2px(heightMeasureSpec));
//页面中使用了wrap_content需要计算 宽高
if (hightMode==MeasureSpec.AT_MOST){
//计算的高度与字体长度、字体大小有关,使用画笔来测量
Rect rect=new Rect();
//获取文本的rect
paint.getTextBounds(text,0,text.length(),rect);
//这里加上getPaddingBottom和getPaddingTop在页面中设置paddingBottom和paddingTop才生效
height=rect.height()+getPaddingBottom()+getPaddingTop();
}
//设置控件的宽高
setMeasuredDimension(width,height);
}
/**
* 将sp转成px
* @param sp
* @return
*/
private int sp2px(int sp){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,getResources().getDisplayMetrics());
}
/**
* 用于绘制方法,查看View源码,可以看到是在Draw中调用 onDraw
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// //画文本,x是开始位置 ,y是基线
//dy代表是:高度的一半到baseLine的距离,这里的计算baseLine值可以百度查询android 自定义View 基线就看到具体算法
Paint.FontMetricsInt fontMetricsInt= paint.getFontMetricsInt();
//top是一个负值,bottom是一个正值
int dy= (fontMetricsInt.bottom-fontMetricsInt.top)/2-fontMetricsInt.bottom;
int baseLine=getHeight()/2+dy;
//如果x设置为0,从第一位开始,通过getPaddingLeft()设置,在页面中就根据paddingLeft值设置来开始显示
int x=getPaddingLeft();
canvas.drawText(text,x,baseLine,paint);
// //根据图片Bitmap画出来
// canvas.drawBitmap();
// //画孤
// canvas.drawArc();
// //画圆
// canvas.drawCircle();
}
/**
* 处理用户交互的,手指点击移动等
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
}
extends LinearLayout 低版本SDK无法执行onDraw方法,无法实现绘制文字出来,我们查看一下View源码和LinearLayout发现,LinearLayout是extends ViewGrou的,我们在看View源码,onDraw是在View源码中的Draw中执行的,当final int privateFlags = mPrivateFlags; 为false时才执行onDraw。当然我使用targetSdk 32 的,是可以使用extends LinearLayout 正常实现的,我查看View源码,发现View源码中无判断条件就执行了onDraw 方法了,所以extends LinearLayout 也可以实现。如下图
如果低版本LinearLayout无法实现执行onDrwaw方法,方案一,可以在自定义View中实现dispatchDraw方法代替onDraw方法 ,方案二调用View中的setWillNotDraw方法修改为false。
新版本可以实现onDraw方法