详见《Android群英传》
3.1Android控件架构
每一个activity都包含一个window对象,window设置DecorView作为顶层视图,所有View的监听事件由WindowManagerService接收,并通过Activity对象来回调,其中顶层视图由TitleView和ContentView组成,ContentView是一个framelayout
3.2View的测量
在布局中使用自定义View,View的大小通过自定义View类中的onMeasure方法来指定,但是该方法默认调用的是EXACTLY模式(进入super.onMeasure)可知)
对于自定义View的长宽为wrap_content,达不到意想的结果,(当然可以直接到布局中进行设置,就没有下面的了)
如下
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
我们要做到有所区分,就必须重写该方法,针对布局中指定的layout_width,layout_high来给出合适的大小
当布局中指定的是match_parent时,表名是EXACTLY模式,其要求和父类的值相同
当布局中指定的是wrap_content时,表名的是AT_MOST模式,要求最大不能超过默认的值(下面的函数中默认的值用result指定)
至于UNSPECIFIED模式,要求默认值是多大,就是多大,至于布局中如何指定,未知
这样我们可以通过获取其模式来判断布局中的方式,并做修改
如下
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureHeight(int heightMeasureSpec) {
int result=200;
int specMode=MeasureSpec.getMode(heightMeasureSpec);//获取测量模式
int specSize=MeasureSpec.getSize(heightMeasureSpec);//获取测量大小
/*
关于这三种模式,
*/
if(specMode==MeasureSpec.EXACTLY){
result=specSize;//如果等于精确模式,则使用父类指定的大小
}else if(specMode==MeasureSpec.AT_MOST){
result=Math.min(result,specSize);//如果为最大模式,则大小不能超过默认的大小
}else{
//其他使用默认的大小
}
return result;
}
private int measureWidth(int widthMeasureSpec) {
int result=200;
int specMode=MeasureSpec.getMode(widthMeasureSpec);//获取测量模式
int specSize=MeasureSpec.getSize(widthMeasureSpec);//获取测量大小
if(specMode==MeasureSpec.EXACTLY){
result=specSize;//如果等于精确模式,则使用父类指定的大小
}else if(specMode==MeasureSpec.AT_MOST){
result=Math.min(result,specSize);//如果为最大模式,则大小不能超过默认的大小
}else{
//其他使用默认的大小
}
return result;
}
3.6自定义View
自定义View要重写onDraw()方法,下面重写TextView的onDraw()方法
@Override
protected void onDraw(Canvas canvas) {
//回调父类方法前实现自己的逻辑
Paint mPaint1=new Paint();
mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
mPaint1.setStyle(Paint.Style.STROKE);//设置空心画笔
mPaint1.setStrokeWidth(10);//设置空心画笔的宽度
Paint mPaint2=new Paint();
mPaint2.setColor(Color.YELLOW);
mPaint2.setStyle(Paint.Style.STROKE);
mPaint2.setStrokeWidth(10);
//这个画矩形要说一下,画笔的宽度为10,从5开始画,即10分为左边的5和右边的5
canvas.drawRect(5, 5, getMeasuredWidth()-5, getMeasuredHeight()-5, mPaint1);
canvas.drawRect(15, 15, getMeasuredWidth() - 15, getMeasuredHeight() - 15, mPaint2);
//关于save和restore,save保存了此时的位置,然后将此时的位置平移到20,20,目的是不让原生实现和自定义的逻辑实现重叠,然后restore在返回到save的状态
canvas.save();
canvas.translate(20,20);
//原生的textview实现
super.onDraw(canvas);
canvas.restore();
}
下面继续自定义View,实现文字的闪烁效果
使用到Shader
可以参考以下两篇文章
http://www.tuicool.com/articles/RF7v2qY
http://www.jianshu.com/p/a9d09cb7577f
附上代码的解说
//onSizeChanged()方法是在onDraw()前执行的,也就是用view加载后会自动的执行一次onSizeChanged()方法
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.i("TextView","onSizeChanged");
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();//获取View的宽度
if (mViewWidth > 0) {
mPaint = getPaint();//获取画笔
/*
字体渐变,其中参数含义分别是:起始x,起始y,终点x,终点y,渐变的颜色组,对应颜色组渐变的位置值,平铺方式
*/
mLinearGradient = new LinearGradient(
0,
0,
mViewWidth,
0,
new int[]{
Color.BLUE, 0xffffffff,
Color.BLUE},
null,
Shader.TileMode.CLAMP);
mPaint.setShader(mLinearGradient);//通过setShader来设置这个渐变,因为LinearGradient本来就是继承Shader
mGradientMatrix = new Matrix();//矩阵
}
}
}
@Override
protected void onDraw(Canvas canvas) {
Log.i("TextView","onDraw");
super.onDraw(canvas);
if (mGradientMatrix != null) {
mTranslate += mViewWidth / 5;
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth;
}
mGradientMatrix.setTranslate(mTranslate, 0);//参数为x,y;设置矩阵以(x,y)形式转换
mLinearGradient.setLocalMatrix(mGradientMatrix);//设置矩阵
postInvalidateDelayed(100);//重绘,即过100毫秒后,系统会重新调用onDraw()方法,不会再执行onSizeChanged()方法
}
}