分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
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(自定义模式):大小设置无限制,比较扯淡吧我觉得
首先必须重写onMeasure方法,至于为什么,源码走一波
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
- 1
- 2
- 3
- 4
在AS中通过Ctrl+B来观看onMeasure的源码,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
- 1
- 2
- 3
- 4
显然,这里将自定义的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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
getDefaultSize则是根据对应的测量模式选取对应的Size。
在该方法的第二个参数,是一个getSuggestedMinimumWidth()或getSuggestedMinimumHeight()方法,以getSuggestedMinimumWidth()为例,
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
- 1
- 2
- 3
官方的解释是: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); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
自定义属性
自定义属性流程
- 自定义一个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); }
- 1
- 2
- 3
- 4
- 5
- 6
通过这种方式我们只可以获取那些在xml中直接定义的属性,比如android:text = "horzion"
,但对于诸如android:text = “@String/name”则会直接反馈给你一段R文件中的标识码,为了简化工作这里引入TypeArray。
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
- 1
此时,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();//记得回收 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
此外,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(); } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
一个验证码小示例:
源码地址:Custom
原来学RecyclerView,走到半截发觉对自定义分割线有很多不理解的地方,于是有转过头来重学自定义View,看了群英传和开发艺术探索,以及网络博客诸多,终于写够了这篇基础的自定义。
关于更深入的Canvas学习,可以参加这位大牛的最后一篇文章,Canvas之图片以及张洪洋一整个系列自定义View实战
路漫漫其修远兮,吾将上下而求索。纪念第一篇MarkDown博客
给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/2e19ffb3303a490d20ea5d00dd397156.gif)