自定义View初级
来源:互联网 发布:日程提醒软件 编辑:程序博客网 时间:2024/06/10 19:37
对于自定义View,一开始用的都是别人的,当时刚学Android的时候,想到自定义View,觉得是挺难的,也比较抵触,不愿意去看,到后来觉得,不可能一直用别人的,得学会自己开发,然后,就试着学了。我学习的历程,首先肯定是在网上查阅资料,看博客,看视频,跟着敲代码,然后,就自己试着写了。
下面来写一个比较简单易懂的例子,实现一个类似TextView的控件:
自定义属性:
为一个View提供可自定义的属性非常简单,只需要在res资源目录的values目录下创建一个attr.xml属性定义文件,并在该文件中通过如下定义相应的属性即可。
<declare-styleable name="MyTextView"> <attr name="title_text" format="string" /> <attr name="title_color" format="color" /> <attr name="title_size" format="dimension" /> <attr name="background_color" format="color" /></declare-styleable>
在自定义属性的时候,我一开始犯了一个错误,这里得注意一下,在attr标签中定义的属性名不能是已经存在的,不然会报找不到这个属性的错误,这里一定要谨记。
- 继承View,实现其构造方法:
public MyTextView(Context context) { this(context, null);}public MyTextView(Context context, AttributeSet attrs) { this(context, attrs, 0);}public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); obtainStyledAttrs(context, attrs); //初始化,获取自定义属性 }
这儿有一个小技巧,在写构造方法的时候,一个参数的使用this调用两个参数的构造方法,以此类推,最后的初始化在三个参数的构造方法中完成,这样,只需把初始化的代码放入三个参数的构造方法中,就可以统一在其他构造方法调用,这样写的好处在于比较简洁,避免在每一个构造方法中都要单独的写上初始化的代码。
还有一点需要特别注意,上面叙述中的是使用this进行构造方法的调用,千万不要用super,一定要注意!!!后面会有讲到。
获取自定义属性值:
在获取自定义属性值之前,还需要做一件事,就是为属性值设置默认值以及参数的声明,如下:
private static final int DEFAULT_TITLE_SIZE = 10;private static final int DEFAULT_TITLE_COLOR = 0x000000;private static final int DEFAULT_BACKGROUND_COLOR = 0xffffff;private int titleSize = sp2px(DEFAULT_TITLE_SIZE);private int titleColor = DEFAULT_TITLE_COLOR;private int background_color = DEFAULT_BACKGROUND_COLOR;private String titleText = "";
获取属性值的代码如下:
/** - 获取自定义属性 - * @param attrs */private void obtainStyledAttrs(Context context, AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextView); titleSize = (int) array.getDimension(R.styleable.MyTextView_title_size, titleSize); titleColor = array.getColor(R.styleable.MyTextView_title_color, titleColor); titleText = array.getString(R.styleable.MyTextView_title_text); background_color = array.getColor(R.styleable.MyTextView_background_color, background_color); array.recycle();}
接下来是View的测量,相信大家都知道,在自定义View中需要完成几件事,分别是onMeasure、onDraw(尤为重要),下面,进入onMeasure,也就是测量View。
那么问题来了,如何测量呢?在Android中方为我们提供了一个设计短小精悍却功能强大的类——MeasureSpec类,通过它来帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。
测量模式分为三种:EXACTLY:即精确模式,当我们将控件的layout_width属性或layout_height属性指定为具体数值时,比如android:layout_width=”100dp”,或者指定为match_parent时(占据父View的大小),系统使用的是EXACTLY模式。
AL_MOST:即最大模式,当控件的layout_width属性或layout_height属性指定为wrap_content时,控件大小一般随着控件的子空间或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。
UPSPECIFIED:这个属性比较奇怪——它不指定其大小测量模式,View想多大就多大。
简单的介绍完了MeasureSpec的三种测量模式之后,下面上代码:
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(measureDimension(200, widthMeasureSpec), measureDimension(100, heightMeasureSpec));}public int measureDimension(int defaultSize, int measureSpec) { int result; //获取测量模式 int specMode = MeasureSpec.getMode(measureSpec); //获取值 int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { result = defaultSize; //UNSPECIFIED if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result;}
- 最后一步便是onDraw,代码如下:
@Overrideprotected void onDraw(Canvas canvas) { canvas.save(); //保存绘制状态 Paint paint = new Paint(); //画笔 paint.setColor(background_color); //设置绘制的颜色 canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint); canvas.save(); //此处绘制矩形 paint.setColor(titleColor); paint.setTextSize(titleSize); Rect rect = new Rect(); //将文本内容放入Rect中,以便获取文本的宽高 paint.getTextBounds(titleText, 0, titleText.length(), rect); canvas.drawText(titleText, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint); //绘制文本 canvas.restore(); }
到此,自定义View已完成,此处需特别注意一个问题,在写构造函数的时候,我一开始没注意,使用了系统默认的super,后来把代码基本写完之后,发现使用的自定义View没有任何效果,屏幕上一片空白,之后发现了问题所在,因为使用super调用的是父类的方法,要想调用本类的构造方法,必须改为this,切记。
完整的代码如下:
public class MyTextView extends View { private static final int DEFAULT_TITLE_SIZE = 10; private static final int DEFAULT_TITLE_COLOR = 0x000000; private static final int DEFAULT_BACKGROUND_COLOR = 0xffffff; private int titleSize = sp2px(DEFAULT_TITLE_SIZE); private int titleColor = DEFAULT_TITLE_COLOR; private int background_color = DEFAULT_BACKGROUND_COLOR; private String titleText = ""; public MyTextView(Context context) { this(context, null); } public MyTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); obtainStyledAttrs(context, attrs); } /** * 获取自定义属性 * * @param attrs */ private void obtainStyledAttrs(Context context, AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextView); titleSize = (int) array.getDimension(R.styleable.MyTextView_title_size, titleSize); titleColor = array.getColor(R.styleable.MyTextView_title_color, titleColor); titleText = array.getString(R.styleable.MyTextView_title_text); background_color = array.getColor(R.styleable.MyTextView_background_color, background_color); array.recycle(); } public int getTitleSize() { return titleSize; } public int getTitleColor() { return titleColor; } public String getTitleText() { return titleText; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(measureDimension(200, widthMeasureSpec), measureDimension(100, heightMeasureSpec)); } public int measureDimension(int defaultSize, int measureSpec) { int result; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { result = defaultSize; //UNSPECIFIED if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; } @Override protected void onDraw(Canvas canvas) { //save方法是将当前的结果推到栈里,相当于保存多个历史记录 canvas.save(); Paint paint = new Paint(); paint.setColor(background_color); canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint); canvas.save(); paint.setColor(titleColor); paint.setTextSize(titleSize); Rect rect = new Rect(); //getTextBounds方法一定要在setColor...之后 //此方法是将text文本放入已新建的Rect容器中,由此得到文本内容的宽高 paint.getTextBounds(titleText, 0, titleText.length(), rect); //此处不能用getMeasuredWidth()替换getWidth() canvas.drawText(titleText, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint); //restore方法是将最后一个结果弹出栈,相当于只留下最后一个修改的状态作为最终结果,而之前的状态全部被删除 canvas.restore(); } /** * dp转换为px * * @param dpVal * @return */ protected int dp2px(int dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics()); } /** * sp转换为px * * @param spVal * @return */ protected int sp2px(int spVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics()); }}
- Android初级自定义View
- 自定义View初级
- android 自定义view初级
- 自定义view的初级记录
- Android自定义view(初级篇)
- Android初级进阶之自定义View之SafeVeiw
- [Cocoa]_[初级]_[在自定义View中单击鼠标右键弹出菜单栏]
- 自定义View(三) switch开关按钮 ViewDragHelper的使用初级
- android初级学习之视图的绘制(自定义View的实现原理)
- 初级自定义cell
- 自定义view
- 自定义View
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义View
- 自定义view
- 动态内存分配
- ImageLoad中的配置属性--Configuration所有配置简介
- HTML基础
- Ruby语言学习系列--基本的ruby语法
- 第七届蓝桥杯省赛A组题解(1~5)
- 自定义View初级
- 结构体
- leetcode #92 in cpp
- JS作用域
- <<High-Speed Tracking with Kernelized Correlation Filters>> KCF(核化相关滤波)跟踪算法学习笔记
- 第13周项目1-分数类中的运算符重载
- Android引导界面的实现分析
- ReentrantLock 报 java.lang.IllegalMonitorStateException
- 如何更好的创建索引