之前学自定义View的时候,都是看一些博客文章,然后下载相关demo去学习。但是时间久了,再重新提起自定View,还是很模糊,想写个自定View一时还不知从哪下手。实在惭愧。
现在想在这里记录一下每一次学习自定View的过程。方便以后查看,反思和总结。算是一种积累吧,不然过段时间又忘了,又得去找资源。而通过记录,可以回翻,温故而知新,说不定能纠正自己以前的错误理解呢。
嗯,那,就开始了。
这篇主要是记录一下:看完这篇文章后(鸿洋大神写的)https://blog.csdn.net/lmj623565791/article/details/24252901,觉得例子虽然不难,但是对整个自定义过程写得很清楚。特此记录。
首先先了解一下自定义View的步骤吧:
1.自定义View的属性
2.在View的构造方法中获得我们自定义的属性
3.重写onMesure(此步骤不一定是必须的,但大多数情况下还是需要重写的)
4.重写onDraw
1.自定义View的属性
在res/values/下新建attrs.xml,在里面定义属性和声明样式
属性取值类型有以下几种:string,color,demension,integer,enum,reference,float,boolean,fraction,flag
接着在布局中声明我们的自定义View
一定要引入 xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"我们的命名空间,后面的包路径指的是项目的package
2.在View的构造方法中,获得我们的自定义的样式和属性
/**
* 文本
*/
private String mTitleText;
/**
* 文本的颜色
*/
private int mTitleTextColor;
/**
* 文本的大小
*/
private int mTitleTextSize;
/**
* 绘制时控制文本绘制的范围
*/
private Rect mBound;
private Paint mPaint;
public CustomTitleView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public CustomTitleView(Context context)
{
this(context, null);
}
/**
* 获得我自定义的样式属性
*
* @param context
* @param attrs
* @param defStyle
*/
public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
/**
* 获得我们所定义的自定义样式属性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.CustomTitleView_titleText:
mTitleText = a.getString(attr);
break;
case R.styleable.CustomTitleView_titleTextColor:
// 默认颜色设置为黑色
mTitleTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomTitleView_titleTextSize:
// 默认设置为16sp,TypeValue也可以把sp转化为px
mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}
a.recycle();
/**
* 获得绘制文本的宽和高
*/
mPaint = new Paint();
mPaint.setTextSize(mTitleTextSize);
// mPaint.setColor(mTitleTextColor);
mBound = new Rect();
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
}
我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。
3.重写onDraw,onMeasure调用系统提供的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas)
{
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(mTitleTextColor);
canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
}
效果如下:
但现在有一个问题,当我们把布局文件中该View的宽高改为wrap_content的时候,效果如下(像系统控件TextView和Button等,即使宽高设置wrap_content,但是我们会发现它们也是有一个默认的大小的):
系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。
所以我们需要重写onMeasure方法,来处理WRAP_CONTENT的情况。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height ;
if (widthMode == MeasureSpec.EXACTLY)
{
width = widthSize;
} else
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textWidth = mBounds.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY)
{
height = heightSize;
} else
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
float textHeight = mBounds.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
setMeasuredDimension(width, height);
}
修改后效果:
4.为View添加点击事件
在构造方法中添加:
this.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
mTitleText = randomText();
postInvalidate();
}
});
随机生成四个数:
private String randomText()
{
Random random = new Random();
Set set = new HashSet();
while (set.size() < 4)
{
int randomInt = random.nextInt(10);
set.add(randomInt);
}
StringBuffer sb = new StringBuffer();
for (Integer i : set)
{
sb.append("" + i);
}
return sb.toString();
}
好了,记录到此结束。自己的思路果然清晰了很多~~
再次提供原文链接:
Android 自定义View (一) - CSDN博客 https://blog.csdn.net/lmj623565791/article/details/24252901
送上源码:https://github.com/huangyula/CustomView.git