您的位置:首页 > 移动开发 > Android开发

Android自定义View入门

2016-04-16 18:13 447 查看

View架构简介:

在Android中,控件主要以ViewGroup和View的形式存在。ViewGroup控件可以包含多个View控件,该复合控件负责其内部所有子控件的测量和绘制,并传递交互事件。如图,



在Android的移动开发中,每个Activity都包含了一个PhoneWindow对象,该对象将DecorView设置为应用窗口的根View。该视图上的所有监听事件都通过WindowManagerService来进行接收,并通过Activity来回调相应的onClickListener。DecorView将屏幕分为了两部分,TitleView和ContentView,而我们的setContentView方法则用于处理这个位置的布局显示。我们的activity_main即处于这样一个ID为content的FrameLayout中。如图:



自定义View的实现

自定义View流程,

自定义View初始化(创建类,引入自定义属性等)

View的测量(重写onMeasure获取自定义的大小并设定)

View的绘制(重写onDraw进行View的绘制)

设置自定义View的监听接口

View的测量

MeasureSpec

一个32位int值,高2位代表测量模式,低30位代表测量的大小,用于辅助View的测量,模式如下:

EXACTLY(精准模式):表示父控件已经确切的指定了子View的大小。

AT_MOST(最大模式):依据子控件的变化而变化,但存在上限

UNSPECIFIED(自定义模式):大小设置无限制,比较扯淡吧我觉得[code]


首先必须重写onMeasure方法,至于为什么,源码走一波

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}


在AS中通过Ctrl+B来观看onMeasure的源码,

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


显然,这里将自定义的MeasureSpec对象作为参数传递给setMeasuredDimension.

再来看看getDefaultSize的作用

public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}


getDefaultSize则是根据对应的测量模式选取对应的Size。

在该方法的第二个参数,是一个getSuggestedMinimumWidth()或getSuggestedMinimumHeight()方法,以getSuggestedMinimumWidth()为例,

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}


官方的解释是:Returns the suggested minimum width that the view should use. This

returns the maximum of the view’s minimum width。最合适的宽度就这样诞生了,,

由于View类默认支持EXACTLY模式,所以为了实现自定义View的wrapContent,就必须使用其他模式,即重写onMeasure方法。

其实onMeasure的重写很简单,就是通过Google提供的MeasureSpec获取Mode和Size,之后依据获得的Mode选取xml设定的数据,之后将数据传递给setMeasuredDimension即可。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
//从MeasureSpec中获取Mode和Size
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height ;
//依据模式获取预设的width和height
if (widthMode == MeasureSpec.EXACTLY)
{
width = widthSize;
} else
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
float textWidth = mBound.width();
int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}

if (heightMode == MeasureSpec.EXACTLY)
{
height = heightSize;
} else
{
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);
float textHeight = mBound.height();
int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
//殊途同归,传递我们自定义的数据
setMeasuredDimension(width, height);
}


View的绘制

对于Android的2D绘图,我们主要借助于graphics包,其中主要包含了Canvas类、Paint类、Color类和Bitmap类等。

绘制图形必然要用到颜色,在Android开发中对颜色的使用和获取有疑问的看这篇文章:颜色

Paint类

要绘制图形,首先得调整画笔,按照自己的开发需要设置画笔的相关属性。

setAntiAlias: 设置画笔的锯齿效果。

setColor: 设置画笔颜色 。

setARGB: 设置画笔的a,r,p,g值。

setAlpha: 设置Alpha值 。

setTextSize: 设置字体尺寸。

setStyle: 设置画笔风格,空心或者实心。

setStrokeWidth: 设置空心的边框宽度。

getColor: 得到画笔的颜色 。

getAlpha: 得到画笔的Alpha值

详细参见:Paint详解

CanVas类

Canvas我们可以称之为画布,能够在上面绘制各种东西,是安卓平台2D图形绘制的基础,在使用该类前需要设置好paint。

Cnavas图形绘制参考:



除了进行基本的绘制外,Canvas也可以进行一些基本操作。位移、缩放、旋转、倾斜以及快照和回滚。

有需要自行参见:Canvas的基本操作

一个简单的圆形绘制示例

Paint paint = new Paint();
paint.setColor(Color.CYAN);
paint.setStrokeWidth(3);
paint.setAntiAlias(true);
//空心
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(160,60,50,paint);

paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(160,180,50,paint);


自定义属性

自定义属性流程

自定义一个CustomView类

创建attrs.xml文件,并添加所需的Item和declare-styleable

在布局文件中引用自定义属性并初始化

在CustomView类中的构造方法中通过TypedArray获取自定义属性和其值

该类的构造方法最多可有四个参数,

public void CustomView(Context context) {}

public void CustomView(Context context, AttributeSet attrs) {}

public void CustomView(Context context, AttributeSet attrs, int defStyleAttr) {}

public void CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {}

在构造方法中AttributeSet中保存的是该自定义View所有属性的值名称和值,获取其中属性代码:

int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
String attrName = attrs.getAttributeName(i);
String attrVal = attrs.getAttributeValue(i);
Log.e(TAG, "attrName = " + attrName + " , attrVal = " + attrVal);
}


通过这种方式我们只可以获取那些在xml中直接定义的属性,比如
android:text = "horzion"
,但对于诸如android:text = “@String/name”则会直接反馈给你一段R文件中的标识码,为了简化工作这里引入TypeArray。

TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);


此时,typeArray中的集合便是经过简化处理,可以直接获取使用。

一个完整的示例:

public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
//获得我们所定义的自定义样式属性
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
int n = typedArray.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = typedArray.getIndex(i);
switch (attr)
{
case R.styleable.CustomTitleView_titleText:
mTitleText = typedArray.getString(attr);
break;
case R.styleable.CustomTitleView_titleTextColor1:
// 默认颜色设置为黑色
mTitleTextColor = typedArray.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomTitleView_titleTextSize:
// 默认设置为16sp,TypeValue也可以把sp转化为px
mTitleTextSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}
typedArray.recycle();//记得回收
}


此外,declare-styleable标签既能告诉系统这是自定义的属性,又会帮助我们在R.java中生成一些常量方便我们使用。

最后,在获取完所需的属性后,我们将其设置到对应的paint上即可,创建paint并调用onDraw方法进行重绘。

在UI线程外,使用postInvalidate方法启动绘制,当需要重绘的时候则可以直接在UI线程中中使用inValidate方法实现重绘。

详细参见:postinvalidate 和invalidate的区别

设置监听接口

自定义完View之后,一般会对外暴露一些接口,用于控制View的状态等,或者监听View的变化.

直接在CustomView的构造方法在即可为View设置监听事件

//设置监听事件
this.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
mTitleText = randomText();
postInvalidate();
}

});


一个验证码小示例:



源码地址:Custom

原来学RecyclerView,走到半截发觉对自定义分割线有很多不理解的地方,于是有转过头来重学自定义View,看了群英传和开发艺术探索,以及网络博客诸多,终于写够了这篇基础的自定义。

关于更深入的Canvas学习,可以参加这位大牛的最后一篇文章,Canvas之图片以及张洪洋一整个系列自定义View实战

路漫漫其修远兮,吾将上下而求索。纪念第一篇MarkDown博客
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: