1.自定义属性
新建attrs.xml文件(res->values->attrs.xml),定义要自定义的TextView属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
<!--name 属性名称 format 格式-->
<attr name="myText" format="string" />
<attr name="myTextColor" format="color" />
<attr name="myTextSize" format="dimension" />
</declare-styleable>
</resources>
2.在布局中使用,对比系统TextView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<com.future.coding.custom_view.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
app:myText="set a flag"
app:myTextColor="#D81B60"
app:myTextSize="16sp" />
<!--与上面自定义TextView的对比-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="set a flag"
android:textColor="#D81B60"
android:textSize="16sp" />
</LinearLayout>
3.通过继承View,实现自定义TextView
public class MyTextView extends View {
private String mText;
private int mTextSize = 16;
private int mTextColor = Color.BLACK;
private Paint mPaint;
private static final String TAG = "MyTextView";
//在代码中使用
public MyTextView(Context context) {
this(context, null);
}
//在布局layout中使用
public MyTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
//在布局layout中使用,但会有style
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
mText = typedArray.getString(R.styleable.MyTextView_myText);
mTextColor = typedArray.getColor(R.styleable.MyTextView_myTextColor, mTextColor);
Log.d(TAG, "MyTextView: " + sp2px(mTextSize));
mTextSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_myTextSize, sp2px(mTextSize));
/**
* 解析:TypedArray 为什么需要调用recycle()
* https://blog.csdn.net/Monicabg/article/details/45014327
*/
typedArray.recycle();
mPaint = new Paint();
mPaint.setAntiAlias(true);//抗锯齿
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
}
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
getResources().getDisplayMetrics());
}
/**
* 自定义View的测量方法
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@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();
mPaint.getTextBounds(mText, 0, mText.length(), bounds);
widthSize = bounds.width() + getPaddingLeft() + getPaddingRight();
} else if (widthMode == MeasureSpec.EXACTLY) {//在布局中指定了确定的值 比如:100dp / match_parent
} else if (widthMode == MeasureSpec.UNSPECIFIED) {//尽可能的大,很少能用到
//ListView、ScrollView在测量子布局的时候会用
}
if (heightMode == MeasureSpec.AT_MOST) {//在布局中指定了wrap_content
Rect bounds = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), bounds);
heightSize = bounds.height() + getPaddingTop() + getPaddingBottom();
} else if (heightMode == MeasureSpec.EXACTLY) {//在布局中指定了确定的值 比如:100dp / match_parent
} else if (heightMode == MeasureSpec.UNSPECIFIED) {//尽可能的大,很少能用到
//ListView、ScrollView在测量子布局的时候会用
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 自定义控件之绘图篇( 五):drawText()详解
* https://blog.csdn.net/harvic880925/article/details/50423762
*/
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
//中心线到基线的距离(当前情况不适用)
//int dy = (int) ((fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom);
//更正
//onMeasure()方法设置的文字范围对应着“当前绘制顶线(ascent)”和“当前绘制底线(descent)”,并非“可绘制最顶线(top)”和“可绘制最底线(bottom)”
//getHeight()得到的onMeasure()方法中的高度,此时中心线并不是bottom-top的中心(如果使用会出现文字显示不全的现象),而应该是descent-ascent的中心
//中心线到基线的距离
int dy = (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;
//得到基线(BaseLine)
int baseLine = getHeight() / 2 + dy;
int x = getPaddingLeft();
canvas.drawText(mText, x, baseLine, mPaint);
}
}
--END
