自定义View入门 - 自定义TextView
2018-07-06 本文已影响1人
Peakmain
做之前需要了解一些知识
- 文字宽高获取:一般有以下几种方式
①:设置文字矩形,Paint.getTextBounds方法得到宽高
String test = "Android世界";
Rect rect = new Rect();
mPaint.getTextBounds(text, 0, test.length(), rect);
int width = rect.width();//文字宽
int height = rect.height();//文字高
②获取文字的宽高
首先宽:
String text = getProgress() + "%";
int textWidth = (int) mPaint.measureText(text);
然后高
结论:
//文字的高:
int textHeight = (int) (mPaint.descent() - mPaint.ascent());
分析(参考文章:http://blog.csdn.net/u012551350/article/details/51361778):
基线.png
ascent = ascent线的y坐标 - baseline线的y坐标;//负数
descent = descent线的y坐标 - baseline线的y坐标;//正数
top = top线的y坐标 - baseline线的y坐标;//负数
bottom = bottom线的y坐标 - baseline线的y坐标;//正数
leading = top线的y坐标 - ascent线的y坐标;//负数
文字的高应该等于asecnt+descent,但是ascent是负的所以mPaint.descent() - mPaint.ascent()
结论:
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
//获取中心(fontMetrics.bottom - fontMetrics.top) / 2
int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
int baseLine = getHeight() / 2 + dy;
自定义TextView
属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--name 自定义View的名字 TextView-->
<declare-styleable name="TextView">
<!-- name 属性名称
format 格式: string 文字 color 颜色
dimension 宽高 字体大小 integer 数字
reference 资源(drawable)
-->
<attr name="peakmainText" format="string"/>
<attr name="peakmainTextColor" format="color"/>
<attr name="peakmainTextSize" format="dimension"/>
<attr name="peakmainMaxLength" format="integer"/>
<!-- background 自定义View都是继承自View , 背景是由View管理的-->
<!--<attr name="peakmainBackground" format="reference|color"/>-->
<!-- 枚举 -->
<attr name="peakmaininputType">
<enum name="number" value="1"/>
<enum name="text" value="2"/>
<enum name="password" value="3"/>
</attr>
</declare-styleable>
</resources>
使用
<com.peakmain.view.TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:background="@color/colorPrimary"
app:peakmainText="Peakmain"
android:padding="10dp"
app:peakmainTextColor="@color/colorAccent" />
自定义view
public class TextView extends View {
private int mTextSize;
private int mTextColor;
private String mText;
private Paint mPaint;
/**
* 构造函数会在new的时候调用
*/
public TextView(Context context) {
this(context, null);
}
/**
* 在布局中使用
*/
public TextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* 布局layout中调用,但是会有style
*/
public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
mText = array.getString(R.styleable.TextView_peakmainText);
// 15 15px 15sp
mTextColor = array.getColor(R.styleable.TextView_peakmainTextColor, mTextColor);
mTextSize = array.getDimensionPixelSize(R.styleable.TextView_peakmainTextSize, sp2x(15));
// 回收
array.recycle();
mPaint = new Paint();
//设置抗锯齿
mPaint.setAntiAlias(true);
//设置字体和颜色
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
}
/**
* sp
*/
public int sp2x(int sp){
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
sp,getResources().getDisplayMetrics());
}
/**
* 自定义view的测量方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//布局的宽高都是有这个方法指定
//指定控件的宽高,需要测量
//获取宽高的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
/**
* MeasureSpec.AT_MOST : 在布局中指定了wrap_content
* MeasureSpec.EXACTLY : 在不居中指定了确切的值 100dp match_parent fill_parent
* MeasureSpec.UNSPECIFIED : 尽可能的大,很少能用到,ListView , ScrollView 在测量子布局的时候会用UNSPECIFIED
*/
//获取宽高的大小
//1.确定的值,这时候不需要计算的
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//2.不确定的值,wrap_content 需要计算
if (widthMode == MeasureSpec.AT_MOST) {
@SuppressLint("DrawAllocation") Rect bounds = new Rect();
//计算的宽度 与字体的长度有关 用画笔测量
mPaint.getTextBounds(mText, 0, mText.length(), bounds);
width = bounds.width()+getPaddingLeft()+getPaddingRight();
}
if (heightMode == MeasureSpec.AT_MOST) {
@SuppressLint("DrawAllocation") Rect bounds = new Rect();
//计算的宽度 与字体的长度有关 用画笔测量
mPaint.getTextBounds(mText, 0, mText.length(), bounds);
height = bounds.height()+getPaddingTop()+getPaddingBottom();
}
//设置宽高
setMeasuredDimension(width, height);
}
/**
* 用于绘制
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画文字
//x是开始的位置
//y是基线
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
//底部到基线的位置
int dy = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
//中心线
int baseLine = getHeight()/2 + dy;
canvas.drawText(mText, getPaddingLeft(), baseLine, mPaint);
}
/**
* 处理用户客户交互的
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 手指按下
Log.e("TAG", "手指按下");
break;
case MotionEvent.ACTION_MOVE:
// 手指移动
Log.e("TAG", "手指移动");
break;
case MotionEvent.ACTION_UP:
// 手指抬起
Log.e("TAG", "手指抬起");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
android高级面试:ViewGroup中没有onDraw方法
我们从View的绘制流程源码中我们可以知道实际调用的是ViewRootImpl中的performDraw,最终调用的是
//最后一行看drawSoftware
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
//最后在方法中我们会看到 mView.draw(canvas);(27版本的源码在19161行)
我们可以从View的draw方法中可以看到这行代码
// Step 4, draw the children
dispatchDraw(canvas);
if (!dirtyOpaque) onDraw(canvas);
也就是说只有dirtyOpaque为false的时候才会去调用onDraw方法
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
实际最主要看的是这个参数mPrivateFlags
那这个参数怎么赋值的呢,实际调用了View的构造函数的时候调用了computeOpaqueFlags()这个方法
protected void computeOpaqueFlags() {
// Opaque if:
// -有背景
// -背景是不透明
// - 没有滚动条或者滚动没有被覆盖
if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
} else {
mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
}
final int flags = mViewFlags;
if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
} else {
mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
}
}
那么为什么viewGroup中没有onDraw方法?ViewGroup构造函数的时候会调用 initViewGroup()
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {//默认debugDraw()这是false
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
mGroupFlags |= FLAG_CLIP_CHILDREN;
mGroupFlags |= FLAG_CLIP_TO_PADDING;
mGroupFlags |= FLAG_ANIMATION_DONE;
mGroupFlags |= FLAG_ANIMATION_CACHE;
mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
}
setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
mChildren = new View[ARRAY_INITIAL_CAPACITY];
mChildrenCount = 0;
mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
}
从名字我们可以知道WILL_NOT_DRAW是将不再绘制onDraw方法,上面的注释也是说明了
解决办法
思路: 目的就是改变 mPrivateFlags
1.dispatchDraw()
2.可以背景透明的背景
3.setWillNotDraw()