自定义View的步骤:
1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
[ 3、重写onMesure ]
4、重写onDraw
我把3用[]标出了,所以说3不一定是必须的,当然了大部分情况下还是需要重写的。
1.自定义View的属性,首先在res/values/ 下建立一个styleable.xml , 在里面定义我们的属性和声明我们的整个样式。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--定义一些基本的属性-->
<attr name="textTitle" format="string"/>
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
<declare-styleable name="CustomeView">
<attr name="textTitle"/>
<attr name="textColor"/>
<attr name="textSize"/>
</declare-styleable>
</resources>
我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型:
一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag
;
2.然后在布局中声明我们的自定义View:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.down.customeviewdemo_1.MainActivity">
<com.example.down.customeviewdemo_1.CustomeView
android:id="@+id/img"
android:layout_width="100dp"
android:layout_height="100dp"
app:textTitle="哈哈哈哈哈哈哈哈"
app:textColor="#009933"
app:textSize="10sp"
/>
</RelativeLayout>
**一定要引入 xmlns:custom=”http://schemas.android.com/apk/res/res-auto”我们的命名空间,后面的包路径指的是项目的package或者res-auto
**
3.自定义view代码:
//画笔
Paint mPaint =null;
//可视区域
Rect mRect =null;
//自己定义的基本属性
private int textSize;
private String textTitle;
private int textColor;
public void setTextColor(int textColor) {
this.textColor = textColor;
}
public void setTextSize(int textSize) {
this.textSize = textSize;
}
public void setTextTitle(String textTitle) {
this.textTitle = textTitle;
}
public CustomeView(Context context) {
this(context,null);
}
public CustomeView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取styleable.xml中定义的基本属性
TypedArray a=context.getTheme().obtainStyledAttributes(attrs,R.styleable.CustomeView,defStyleAttr,0);
int n=a.getIndexCount();
for (int i=0;i<n;i++){
int attr=a.getIndex(i);
switch (attr){
case R.styleable.CustomeView_textTitle:
textTitle=a.getString(attr);
break;
case R.styleable.CustomeView_textColor:
textColor=a.getColor(attr, Color.BLACK);//默认黑色字体
break;
case R.styleable.CustomeView_textSize://这里用的是px
textSize=a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getResources().getDisplayMetrics()));//默认16sp
break;
}
}
a.recycle();//释放回收
//基本初始化
mPaint =new Paint();
mRect =new Rect();
mPaint.setTextSize(textSize);
mPaint.getTextBounds(textTitle, 0, textTitle.length(), mRect);
}
//计算控件的绘制位置
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//绘制控件
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(textColor);
//绘制一个rectangle显示text
canvas.drawRect(0, 0, getMeasuredWidth(),getMeasuredHeight(), mPaint);
//在onMeasure没有做处理的时候,+getWidth()+"-"+getMeasuredWidth()是相等的
// Log.i("yqy", "" + getWidth() + "-" + getMeasuredWidth() + "---" + getHeight() + "," + getMeasuredHeight());
//绘制text
mPaint.setColor(Color.WHITE);
Log.i("yqy","要绘制的文本=="+textTitle+","+textSize+","+textColor+","+getWidth()+","+getHeight());
canvas.drawText(textTitle, getWidth() / 2-mRect.width()/2 , getHeight() / 2 + mRect.height() / 2, mPaint);
}
现在的效果是:
很明显文字不是在中间位置,原因是:
mPaint.setTextSize(textSize);这句话的位置,在ondraw里面就是这个样子,放在构造函数中就是显示中间位置
1.构造函数中:
mPaint.setTextSize(textSize);
mPaint.getTextBounds(textTitle,0,textTitle.length(),mRect);
Log.i("yqy",mRect.width()+"----"+getWidth());//315----768
2.构造函数中:
mPaint.getTextBounds(textTitle,0,textTitle.length(),mRect);
mPaint.setTextSize(textSize);
Log.i("yqy",mRect.width()+"----"+getWidth());//95----768
3.onDraw()中是一样的结果
Log.i("yqy",mRect.width()+"----"+getWidth());//95----768
总结:大小一定要在ondraw方法之前就计算好
当我们在xml中将width或者height设置成warp_content时会全屏铺展,感觉是match_parent的效果
这时的处理是在onMeasure中重新计算要绘制的宽高
首先要了解的是,我们怎么知道用户设置的width是什么样子的?
这个可以通过一个类MeasureSpec的specMode来判断
MeasureSpec的specMode有三种类型:
EXACTLY:一般是设置了明确的值或者Match_parent
AT_MOST:表示子布局限制在一个最大值内,一般是wrap_content布局
UNSPECIFIED:表示子布局想要多大就有多大,很少使用
所以当用户设置match_parent的时候就让其自动显示,除此之外我们来设置它要绘制的范围;
所以重写onMeasure:
int widthSise=MeasureSpec.getSize(widthMeasureSpec);
int heightSize=MeasureSpec.getSize(heightMeasureSpec);
int widthMode=MeasureSpec.getMode(widthMeasureSpec);
int heightMode=MeasureSpec.getMode(heightMeasureSpec);
//最终要显示的宽和高
int width;
int height;
if(widthMode==MeasureSpec.EXACTLY){
width=widthSise;
}else{//自定义
mPaint.setTextSize(textSize);
mPaint.getTextBounds(textTitle,0,textTitle.length(),mRect);
width=getPaddingLeft()+mRect.width()+getPaddingRight();
}
if(heightMode==MeasureSpec.EXACTLY){
height=heightSize;
}else{
mPaint.setTextSize(textSize);
mPaint.getTextBounds(textTitle,0,textTitle.length(),mRect);
height=getPaddingTop()+mRect.height()+getPaddingBottom();
}
//这句话一定要写
setMeasuredDimension(width,height);
最终效果如下: