安卓进阶之自定义View
2016-02-01 23:53
337 查看
今天是小年,又赶上家里置办年货,真的是好忙,但是还是按照昨天的计划继续学习,今天主要是比着HongYang大神的博客和琪爷给的视频资料学习了一下
自定义的View,感觉上来有点摸不到头脑,还好终于在一番挣扎之后,终于有所突破,今天跟大家分享一下自己的所学。
首先是比着HongYang大神的博客的例子敲了一遍代码:现在附上HongYang大神的博客链接:/content/484705.html
首先归纳一下自定义View的几个步骤:
一、自定义View属性
二、在View中的构造方法中获得我们自己的属性
三、重写onMeasure方法
四、重写onDraw的方法
首先我先说一下我在看大神博客中遇到的几个盲点:
1.首先是对于继承View类之后的构造方法有些不明白
2.对于绘图的一些知识由于之前没有接触过,所以理解上有些费劲
3.对于onMeasure方法的重写更是一头雾水
边学习边查资料,在加上今晚上的视频讲解,终于解开了一个个的迷惑
首先先把代码粘贴一下,上面的注释可以让大家更加了解一下:
1>首先是自己定义的View属性
在res/values下新建一个attr.xml文件,进行我们自定义View的定义
<declare-styleable />向我们自定义的View中添加属性值必须要在这个标签的内部
2>在布局文件中声明我们自己定义的View
4>重写onMeasure
最后还要修改一下布局文件
这样的话一个自定义的View就基本上实现了,在这里回头去解释一下我刚刚的疑惑点
一、继承View类之后的构造方法
代码中一共有三个构造方法
其中一个是public CustomTitleView (Context context)
这个构造方法是用于在代码中创建View的方式,调用的构造方法。
还有一个就是public CustomTitleView(Context context, Attribute attr)
这个构造函数是如果要在layout xml中包含自定义的View必须要实现带有Attribute参数的构造方法
这个构造方法允许自定义的View在layout.xml中使用.
最后就是自己定义的一个构造方法,根据自己的所需去设计的,还有就是构造方法的实习父类的构造方法时也非常巧妙,为了避免调用系统的构造方法去实现
系统的View,我们采用了层层调用的方式,最终都实现了用我们自己定义的构造函数来构造我们自己的View
第二个盲点在于绘图知识的盲点,这会在下一篇博客五子棋盘的实现中去介绍
第三个盲点就是关于onMeasure()方法的重写
首先说一下此方法中的两个参数的意义
WidthMeadureSpec包含尺寸和模式
尺寸 :代表了父容器可以让我们控件有多大的尺寸
可以理解为父容器剩余的空间
模式:代表父容器希望我们控件如何排版
即使是我们设置了wrap_concent方法,而不进行重写的话,效果并不会像我们想象的那样,不进行重写只是按照父容器的
想法将剩余的空间全部给我们,而wrap_concent的真正的意思是在父容器给的空间 的前提下,尽可能的按照自己的实际大小来
布局,所以我们要进行实际的计算。
我们从onMeasure参数中获得的尺寸就是父容器留给我们的,也就是match_parent对应的尺寸的大小。
下面在对几种模式进行一下解说:
如果我们的布局文件中设置的我们自定义的空间的大小是具体的值或者是wrap_content,则对应的模式是AT_MOST模式,这种情况下
需要我们自己进行值得计算,而如果是match_parent则对应的是EXACTLY模式,则不需要进行计算。
最重要的一点是,onMeasure()方法需要调用
自定义的View,感觉上来有点摸不到头脑,还好终于在一番挣扎之后,终于有所突破,今天跟大家分享一下自己的所学。
首先是比着HongYang大神的博客的例子敲了一遍代码:现在附上HongYang大神的博客链接:/content/484705.html
首先归纳一下自定义View的几个步骤:
一、自定义View属性
二、在View中的构造方法中获得我们自己的属性
三、重写onMeasure方法
四、重写onDraw的方法
首先我先说一下我在看大神博客中遇到的几个盲点:
1.首先是对于继承View类之后的构造方法有些不明白
2.对于绘图的一些知识由于之前没有接触过,所以理解上有些费劲
3.对于onMeasure方法的重写更是一头雾水
边学习边查资料,在加上今晚上的视频讲解,终于解开了一个个的迷惑
首先先把代码粘贴一下,上面的注释可以让大家更加了解一下:
1>首先是自己定义的View属性
在res/values下新建一个attr.xml文件,进行我们自定义View的定义
<?xml version="1.0" encoding="utf-8"?> <!-- 声明控件属性以及属性的类型 --> <!-- 类似于我们的变量要先声明在使用 --> <resources> <attr name="titleText" format="string" /> <attr name="titleTextColor" format="color" /> <attr name="titleTextSize" format="dimension" /> <!-- 给我们的自定义控件添加属性值 --> <declare-styleable name="CustomTitleView"> <attr name="titleText" /> <attr name="titleTextColor" /> <attr name="titleTextSize" /> </declare-styleable> </resources>上面的代码可以看作两部分,其中上半部分是自定义控件的属性名称和属性类型的定义,下半部分是向我们的自定义控件中添加已经定义好的属性。
<declare-styleable />向我们自定义的View中添加属性值必须要在这个标签的内部
2>在布局文件中声明我们自己定义的View
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:custom="http://schemas.android.com/apk/res/com.example.myview" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- custom在自己的包中定义的属性 --> <!-- 我们只是添加属性,当然也可以用系统的属性 --> <com.example.myview.CustomTitleView android:layout_width="250dp" android:layout_height="250dp" custom:titleText="3712" android:layout_centerInParent="true" custom:titleTextColor="#ff0000" custom:titleTextSize="40sp" /> </RelativeLayout>注意标签名为包名+类名,如果存在嵌套包也要包含在其中,不能只写最顶层的包,也就是说类文件的相对路径,还要在最上面加上这一句:
xmlns:custom="http://schemas.android.com/apk/res/com.example.myview"否侧无法找到我们的包名,在使用我们自定义的View的时候,前面不是android,而是custom对应
xmlns:custom="http://schemas.android.com/apk/res/com.example.myview"前面的custom。
3>继承View并构造我们的View
<pre name="code" class="java">public class CustomTitleView extends 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); } /** * 默认的布局文件调用的是两个参数的构造方法, * 所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。 * @param context */ 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; } } //回收TypedArray,以便后面重用 a.recycle(); /** * 获得绘制文本的宽和高 */ //构建画笔的对象 mPaint = new Paint(); //设置字体的大小 mPaint.setTextSize(mTitleTextSize); // mPaint.setColor(mTitleTextColor); mBound = new Rect(); //设置边界 mPaint.getTextBounds(mTitleText,0, mTitleText.length(), mBound); }
<pre name="code" class="java"> 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); }
4>重写onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** 重写之前先了解MeasureSpec的specMode,一共三种类型: EXACTLY:一般是设置了明确的值或者是MATCH_PARENT AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT UNSPECIFIED:表示子布局想要多大就多大,很少使用 */ 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(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); }
最后还要修改一下布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:custom="http://schemas.android.com/apk/res/com.example.myview" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- custom在自己的包中定义的属性 --> <!-- 我们只是添加属性,当然也可以用系统的属性 --> <com.example.myview.CustomTitleView android:layout_width="wrap_content" android:layout_height="wrap_content" custom:titleText="3712" android:layout_centerInParent="true" custom:titleTextColor="#ff0000" custom:titleTextSize="40sp" /> </RelativeLayout>
这样的话一个自定义的View就基本上实现了,在这里回头去解释一下我刚刚的疑惑点
一、继承View类之后的构造方法
代码中一共有三个构造方法
其中一个是public CustomTitleView (Context context)
这个构造方法是用于在代码中创建View的方式,调用的构造方法。
还有一个就是public CustomTitleView(Context context, Attribute attr)
这个构造函数是如果要在layout xml中包含自定义的View必须要实现带有Attribute参数的构造方法
这个构造方法允许自定义的View在layout.xml中使用.
最后就是自己定义的一个构造方法,根据自己的所需去设计的,还有就是构造方法的实习父类的构造方法时也非常巧妙,为了避免调用系统的构造方法去实现
系统的View,我们采用了层层调用的方式,最终都实现了用我们自己定义的构造函数来构造我们自己的View
第二个盲点在于绘图知识的盲点,这会在下一篇博客五子棋盘的实现中去介绍
第三个盲点就是关于onMeasure()方法的重写
首先说一下此方法中的两个参数的意义
WidthMeadureSpec包含尺寸和模式
尺寸 :代表了父容器可以让我们控件有多大的尺寸
可以理解为父容器剩余的空间
模式:代表父容器希望我们控件如何排版
即使是我们设置了wrap_concent方法,而不进行重写的话,效果并不会像我们想象的那样,不进行重写只是按照父容器的
想法将剩余的空间全部给我们,而wrap_concent的真正的意思是在父容器给的空间 的前提下,尽可能的按照自己的实际大小来
布局,所以我们要进行实际的计算。
我们从onMeasure参数中获得的尺寸就是父容器留给我们的,也就是match_parent对应的尺寸的大小。
下面在对几种模式进行一下解说:
如果我们的布局文件中设置的我们自定义的空间的大小是具体的值或者是wrap_content,则对应的模式是AT_MOST模式,这种情况下
需要我们自己进行值得计算,而如果是match_parent则对应的是EXACTLY模式,则不需要进行计算。
最重要的一点是,onMeasure()方法需要调用
setMeasuredDimension(width, height)
将设置的数据进行修改,否则设置将无效
这样盲点就被一个个解开了,虽然还没有彻底明白原理是什么,但是通过之后的练习肯定会更加明白,
再难,也不要放弃!!!加油,明天,又是新的一天!!!
相关文章推荐
- 【C++探索之旅】第一部分第八课:传值引用,文件源头
- Android初学习 - 通知栏Notification的全面学习
- Click++项目
- JAVA-异常处理
- 【C++探索之旅】第一部分第八课:传值引用,文件源头
- ?id=1454320074805
- 密码学初级教程(二)对称密码
- 四、用busybox生成最小linux系统
- Android 系统广播机制
- Linux常识笔记
- 将delphi7代码移植到delphi10环境下出现的问题
- Codeforces Round #341 (Div. 2) A
- IOS Object-C与Swift相互调用
- UVa 11379 Chichi's Home Work
- 关于Redis中的字符串对象
- centos linux安全和调优 第四十一节课
- vim代码折叠功能
- 生产都消费者模式的一个demo,消费者设置缓存
- Unity IOS Android 消息推送原理分析
- uva 10714 Ants