API DEMO LabelView分析
来源:互联网 发布:轻淘客和淘宝联盟 编辑:程序博客网 时间:2024/05/04 12:51
LabelView是Android API DEMO中的一个例子,演示了如何写一个简单的自定义View。
先看一下效果
布局文件是这样的:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.example.labelview" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.example.labelview.LabelView android:background="@android:color/holo_blue_light" android:layout_width="match_parent" android:layout_height="match_parent" app:text="Red"/> <com.example.labelview.LabelView android:background="@android:color/holo_red_light" android:layout_width="match_parent" android:layout_height="wrap_content" app:text="Blue" app:textSize="20dp"/> <com.example.labelview.LabelView android:background="@android:color/holo_purple" android:layout_width="match_parent" android:layout_height="wrap_content" app:text="Green" app:textColor="#ffffffff" /></LinearLayout>xml元素名com.example.labelview.LabelView,对于自定义的控件,要写上它完整的包路径
LabelVIew的路径如图所示:
注意到标签中的app:text,app:textColor,app:textSize属性都是控件的自定义属性,app:前缀是labelview专属的,定义它的步骤如下:
1.在布局文件中声明命名空间
LinearLayout元素下的xmlns:app="http://schemas.android.com/apk/res/com.example.labelview" 属性,声明了app:这个自定义View属性,格式为:
xmlns:命名空间 /res/应用程序包名。
2,在attrs.xml中定义属性类型
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="LabelView"> <attr name="text" format="string" /> <attr name="textColor" format="color" /> <attr name="textSize" format="dimension" /> </declare-styleable> </resources>
3.在布局文件xml中使用这个属性
app:text="Red"
app:textSize="20dp"
app:textColor="#ffffffff"
4.在代码中实现初始化。
LabelView的代码:
public class LabelView extends View { private Paint mTextPaint; private String mText; private int mAscent; /** * Constructor. This version is only needed if you will be instantiating * the object manually (not from a layout XML file). * @param context */ public LabelView(Context context) { super(context); initLabelView(); } /** * Construct object, initializing with any attributes we understand from a * layout file. These attributes are defined in * SDK/assets/res/any/classes.xml. * * @see android.view.View#View(android.content.Context, android.util.AttributeSet) */ public LabelView(Context context, AttributeSet attrs) { super(context, attrs); initLabelView(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelView); CharSequence s = a.getString(R.styleable.LabelView_text); if (s != null) { setText(s.toString()); } // Retrieve the color(s) to be used for this view and apply them. // Note, if you only care about supporting a single color, that you // can instead call a.getColor() and pass that to setTextColor(). setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000)); int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0); if (textSize > 0) { setTextSize(textSize); } a.recycle(); } private final void initLabelView() { mTextPaint = new Paint(); mTextPaint.setAntiAlias(true); // Must manually scale the desired text size to match screen density mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density); mTextPaint.setColor(0xFF000000); setPadding(20, 3, 3, 3); } /** * Sets the text to display in this label * @param text The text to display. This will be drawn as one line. */ public void setText(String text) { mText = text; requestLayout(); invalidate(); } /** * Sets the text size for this label * @param size Font size */ public void setTextSize(int size) { // This text size has been pre-scaled by the getDimensionPixelOffset method mTextPaint.setTextSize(size); requestLayout(); invalidate(); } /** * Sets the text color for this label. * @param color ARGB value for the text */ public void setTextColor(int color) { mTextPaint.setColor(color); invalidate(); } /** * @see android.view.View#measure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.v("test", "onMeasure"); setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } /** * Determines the width of this view * @param measureSpec A measureSpec packed into an int * @return The width of the view, honoring constraints from measureSpec */ private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } /** * Determines the height of this view * @param measureSpec A measureSpec packed into an int * @return The height of the view, honoring constraints from measureSpec */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); mAscent = (int) mTextPaint.ascent(); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text (beware: ascent is a negative number) //-asent+desent = 字符的高度,再加上山下的padding等于这个view的总高度 result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } /** * Render the text * * @see android.view.View#onDraw(android.graphics.Canvas) */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.v("test", "onDraw"); //y是指定这个字符baseline在屏幕上的位置 canvas.drawText(mText, getPaddingLeft(), ((int) (-mAscent + getPaddingTop())), mTextPaint); } }
先看构造方法
public LabelView(Context context, AttributeSet attrs) { super(context, attrs); initLabelView(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelView); CharSequence s = a.getString(R.styleable.LabelView_text); if (s != null) { setText(s.toString()); } // Retrieve the color(s) to be used for this view and apply them. // Note, if you only care about supporting a single color, that you // can instead call a.getColor() and pass that to setTextColor(). setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000)); int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0); if (textSize > 0) { setTextSize(textSize); } a.recycle(); }intiLavbelVIew方法设置了Paint对象绘制时的一些初始值。
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelView);
获得了attrs.xml中定义的属性类型,然后用TypedArray对象中的方法获得具体的值,因为attr文件中,text是string类型的,所以用getString,textSize和textColor分别对应getColor和getDimensionPixelOffset。最后要调用a.recycle();回收TypedArray对象。
注意到 setTextSize方法:
public void setTextSize(int size) { // This text size has been pre-scaled by the getDimensionPixelOffset method mTextPaint.setTextSize(size); requestLayout(); invalidate(); }
requestLayout的调用会触发View的onMeasure过程,invalidate的调用会触发View的onDraw过程。
View的绘制流程中,onMeasure用来测量控件的尺寸,onDraw用来绘制图形
其他2个设置text的方法,
public void setText(String text) { mText = text; requestLayout(); invalidate(); }
public void setTextColor(int color) { mTextPaint.setColor(color); invalidate(); }可以看到setTextColor并没有调用requestLayout方法,因为设置字体的颜色并不需要变化字体的大小形状,所以不用测量,就不需要走onMeasure的流程,自然不需调用requestLayout方法。
LabelView重写了onMeasure方法
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));用来设置测量完毕的尺寸,宽度和高的测量是通过measureWidth和measureHeight进行的。
measureWidth:
private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; }
onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值,需要用静态方法getMode和getSize分别取出
MeasureSpec.getMode获取MeasureSpec的Mode,MeasureSpec的Mode有三种类型:MeasureSpec.AT_MOST
MeasureSpec.EXACTLY
MeasureSpec.UNSPECIFIED
MeasureSpec.UNSPECIFIED
调试代码时,发现MeasureSpec.getSize会返回屏幕的真实宽度,mode和布局xml中宽度属性值的对应关系为:
xx dp:specMode == MeasureSpec.EXACTLY
wrap_content:specMode == MeasureSpec.AT_MOST
match_parent:specMode ==MeasureSpec.EXACTLY
所以,如果布局中是具体的dp的话,result返回dp换算后的实际px。而如果是match_parent的话,会返回MeasureSpec.getSize的值,即屏幕的实际宽度。如果是wrap_content的话,会先调用Paint的measureText方法算出text的具体宽度然后返回。
再来看measureHeight方法:
private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); mAscent = (int) mTextPaint.ascent(); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text (beware: ascent is a negative number) //-asent+desent = 字符的高度,再加上山下的padding等于这个view的总高度 result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; }大体上和measureWidth差不多。计算字体高度的方式很有意思,涉及到baseLine的问题,
以下图片来自网络,说明baseLine和各个属性的关系
ascent:是baseline之上至字符最高处的距离
descent:是baseline之下至字符最低处的距离
descent:是baseline之下至字符最低处的距离
所以用如下代码计算字体高度
result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() + getPaddingBottom();
最后来看onDraw方法,它负责具体的绘制
protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawText(mText, getPaddingLeft(), ((int) (-mAscent + getPaddingTop())), mTextPaint); }使用画布对象canvas绘制字体,参数是:显示的字体内容,x坐标,y坐标,paint对象
- API DEMO LabelView分析
- 剖析API Demos中的LabelView
- API Demo GestureBuilderActivity分析一
- Api-Demo Advanced preference分析
- API Demo SearchableDictionary代码分析一
- API Demo SearchableDictionary代码分析二
- API Demo Snake代码分析二 onSaveInstanceState和onRestoreInstanceState分析
- API Demo Snake代码分析三 程序架构的分析
- API Demo Snake代码分析一 FrameLayout新的认识
- ASP调用LABELVIEW
- Android 标签云 LabelView
- android标签云:LabelView
- Android控件之LabelView
- lehpone api demo学习
- API DEMO APPWIGHT 学习
- Google 翻译API Demo
- zookeeper api demo
- API Demo MulitiRes 学习
- 数字签名和数字证书
- uva 10160 Servicing Stations
- 庞国网英雄会之杨辉三角的变形
- OwnCloud目录说明
- Java 异常类层次结构
- API DEMO LabelView分析
- Javascript 面向对象编程(一):封装
- opencv学习-imgprocess-创建自己的滤波器
- 整数因子的分解问题--java
- Javascript面向对象编程(二):构造函数的继承
- C++ 数据结构 堆
- Javascript面向对象编程(三):非构造函数的继承
- Java程序优化的一些最佳实践
- 进程,服务,端口