Android中初步自定义view
在研究了几个星期的view之后,打算自定义个view巩固检验一下最近学的知识,view知识相关博文
Android6.0源码分析之View(一)
Android6.0源码分析之View(二)--measure
Android中View研究自学之路
Chapter One,自定义一个显示文本的自定义view
首先,定义一个继承自view的子类Customview
public class CustomView extends View {
private String mCustomTitle;//标题文本
private int mTitleTxtSize;//标题字体大小
private int mTitleTxtColor;//标题文本颜色
private String mCustomCont;//正文文本
private int mContTxtSize;//正文字体大小
private int mContTxtColor;//正文文本颜色
private Rect mBounds;
private TextPaint mPaint;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new TextPaint();
mBounds = new Rect();
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomViewStyle);
//从添加view的xml文件中获取到view的相关属性信息
//标题相关属性
mCustomTitle = array.getString(R.styleable.CustomViewStyle_customTitle);
mTitleTxtSize = array.getDimensionPixelSize(R.styleable.CustomViewStyle_titleTxtSize,
(int) getResources().getDimension(R.dimen.title_default_size));
mTitleTxtColor= array.getColor(R.styleable.CustomViewStyle_titleTxtColor,
Color.BLACK);
//正文文本相关属性
mCustomCont = array.getString(R.styleable.CustomViewStyle_customCont);
mContTxtSize = array.getDimensionPixelSize(R.styleable.CustomViewStyle_contTxtSize,
(int) getResources().getDimension(R.dimen.cont_default_size));
mContTxtColor = array.getColor(R.styleable.CustomViewStyle_contTxtColor,Color.GRAY);
array.recycle();
}
对于一个view在xml中相关的 属性有限,我们需要添加自己想要的属性,添加方式也很简单,
第一步那就是在values目录下创建一个resource为节点的资源文件,把想要的属性添加进去
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomViewStyle">
<attr name="customTitle" format="string"/>
<attr name="customCont" format="string"/>
<attr name="titleTxtSize" format="dimension"/>
<attr name="contTxtSize" format="dimension"/>
<attr name="titleTxtColor" format="color"/>
<attr name="contTxtColor" format="color"/>
</declare-styleable>
</resources>
第二步,在添加属性成功后,在构造方法中引用该styleable
第三步,现在,可以在xml文件中使用了
<com.fang.zrf.customview.widges.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:customTitle="@string/custom_view_title"
custom:customCont="@string/custom_view_cont"
android:paddingLeft="50dp"/>
使用的时候是以custom开头,一般开发工具会提示你添加命名空间
xmlns:custom="http://schemas.android.com/apk/res-auto"
把这句话添加在根节点下,到现在,你就可以为你的view自定义属性了
属性添加成功后可以进行测量,布局和绘制了。
即需要重写onMeasure和onDraw方法。
这样整体来看,其实自定义view也不是很麻烦。总结下来就是
第一,先定义自己的view类
第二,创建资源文件添加view的属性
第三,在onMeasure方法中测量view所需要显示的大小
第四,在onDraw中借助画笔和画布把view绘制出来。
恩~看着确实挺简单,实现起来真是问题层出不穷
Chapter Two,所遇到的问题
转载请注明出处
Android中初步自定义view
问题1 ,Paint画笔对象为null的异常
FATAL EXCEPTION: main
Process: com.fang.zrf.mycustomview, PID: 12882
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
at android.graphics.Canvas.drawText(Canvas.java:1656)
at com.fang.zrf.mycustomview.widgets.CustomViewSec.onDraw(CustomViewSec.java:76)
at android.view.View.draw(View.java:16207)
刚开始怎么也想不明白,明明创建了Paint的对象,为什么还会为null。到最后发现真是自己的粗心大意。看看我的代码,你能看出来问题吗???
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomViewStyle);
//从添加view的xml文件中获取到view的相关属性信息
//标题相关属性
mCustomTitle = array.getString(R.styleable.CustomViewStyle_customTitle);
mTitleTxtSize = array.getDimensionPixelSize(R.styleable.CustomViewStyle_titleTxtSize,
(int) getResources().getDimension(R.dimen.title_default_size));
mTitleTxtColor= array.getColor(R.styleable.CustomViewStyle_titleTxtColor,
Color.BLACK);
//正文文本相关属性
mCustomCont = array.getString(R.styleable.CustomViewStyle_customCont);
mContTxtSize = array.getDimensionPixelSize(R.styleable.CustomViewStyle_contTxtSize,
(int) getResources().getDimension(R.dimen.cont_default_size));
mContTxtColor = array.getColor(R.styleable.CustomViewStyle_contTxtColor,Color.GRAY);
array.recycle();
}
我自定义view时实现了三个构造方法,使用的是as中的快捷键创建的,以至于第三个构造方法根本就没调用,所以做什么都是错的,解决方案很简单,那就是把第二个构造方法的方法体改一下即可
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
利用this自己手动调用一下第三个构造方法即可。问题解决
问题2,绘制上发现所自定义的 view进行了全屏显示,打开手机上显示布局边界的功能之后可以发现我所自定义的view进行了全屏显示,占据了一个界面上父view所剩余的所有空间。
通过前几篇对于view的分析可以得出,这个draw绘制出来的大小跟onMeasure方法是分不开的,所以呢,重点是对所测量的宽和高进行重新计算 利用paint画笔对象可以直接对文本的宽高进行计算:
private Rect mBounds;
mBounds = new Rect();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth;
int measuredHeight;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取测量规范,对于这些不懂得可参考博文
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
mPaint.getTextBounds(mCustomTitle,0,mCustomTitle.length(),mBounds);
if (widthMode != MeasureSpec.EXACTLY){
measuredWidth = mBounds.width();
}else{//在xml文件中规定了准确的值
measuredWidth = width;
}
if (heightMode != MeasureSpec.EXACTLY){
Log.i("fang","---");
measuredHeight = mBounds.height();
}else{
measuredHeight = height;
}
setMeasuredDimension(measuredWidth + getPaddingLeft() + getPaddingRight(),
measuredHeight + getPaddingTop() + getPaddingBottom());
}
然后进行绘制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setTextSize(mTitleTxtSize);
mPaint.setColor(mTitleTxtColor);
canvas.drawText(mCustomTitle,getPaddingLeft(),
getHeight()/2 + mBounds.height()/2,mPaint);
}
绘制时第二个和第三个参数决定了内容的x和y的坐标
绘制后结果如下图
第一张图是每次重新oncreate界面时的效果,(在此声明一下设置的view是paddingleft为50dp) 第二张图是每次onPause然后onResume之后的效果。 由以上这两张图可以发现两个问题 第一,每次oncreate时view所绘制出的大小并不正确 第二,view没有自动换行,view的绘制已经超出了父view的边界 是不是发现问题层出不穷??慢慢来吧 问题3,在oncreate时view所绘制的大小不正确 问题4,view需要换行
问题3和问题4待解决中,估计需要点儿时间,有解决方案的请留言,谢谢,也欢迎各位分享你自定义view时所遇到的问题
- R语言与机器学习学习笔记(分类算法
- 54. 心跳的实现 | 厚土Go学习笔记
- 通过错误的sql来测试推理sql的解析过程(二) (r8笔记第7天)
- 53. Socket服务三次握手的示例 | 厚土Go学习笔记
- R分词继续,"不|知道|你在|说|什么"分词添加新词
- Java开发Spring第一天
- 关于R安装中文分词包安装不上的问题install.packages("tm")
- dataguard备库的数据文件的迁移实战(r8笔记第24天)
- Hive的left join、left outer join和left semi join三者的区别
- 52. Socket Server 自定义协议的简单实现 | 厚土Go学习笔记
- dataguard备库的数据文件的迁移(r8笔记第22天)
- 46. 实现一个简单的网络爬虫 | 厚土Go学习笔记
- 45. sync.Mutex 互斥和互斥锁 | 厚土Go学习笔记
- golang进度条
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Typecho配置多国语支持,检查客户端语言自动切换翻译
- NgModule imports定义的运行时数据结构
- 用Windows电脑训练深度学习模型?超详细配置教程来了
- Angular No provider for EffectsRootModule错误消息
- Leetcode No.15 三数之和
- 正则表达式介绍与使用
- Angular StoreFeatureModule
- Angular Component之间的事件通知机制
- 如何将你的Python项目全面自动化?
- 正则表达式介绍与使用
- Angular ERROR NullInjectorError: R3InjectorError(AppModule)的错误分析
- 2.4-Air302(NB-IOT)-基础外设-延时,定时器
- Shell正则表达式一览表
- YAML基础语法
- 详解Kubernetes存储体系