简介
Android提供了用于构建UI的强大的组件模型。两个基类:View和ViewGroup。
可用Widget的部分名单包括Button, TextView, EditText, ListView, CheckBox,RadioButton, Gallery, Spinner,以及一些有特别作用的组件: AutoCompleteTextView, ImageSwitcher和 TextSwitcher。
可用的布局有:LinearLayout,FrameLayout,RelativeLayout,AbsoluteLayout,GridLayout (later on api level 14 or v7-support)
基本做法
1. 继承自View或View的子类
2. 重写父类的一些方法,如:onDraw(),onMeasure(),onLayout()等
3. 使用自定义的组件类。
完全自定义组件
1. 最普通的作法是,继承自View,实现你的自定义组件
2. 提供一个构造函数,采用有属性参数的,也可以使用自定义属性
3. 你可能想在组件中创建自己的事件监听器,属性访问器和修改器,或其他行为
4. 几乎肯定要重写onDraw(),onMeasure()。默认onDraw()什么也没作,onMeasure()则设置一个100x100的尺寸。
5. 根据需要重写其他方法 ...
onDraw()和onMeasure()
onDraw(),提供一个Canvas,可以绘制2D图形。
若要绘制3D图形,请继承GLSurfaceView,参见,api-demo下的 GLSurfaceViewActivity
onMeasure() 测量组件
1. 宽度和高度在需要测量时调用该方法
2. 应该进行测量计算组件将需要呈现的宽度和高度。它应该尽量保持传入的规格范围内,尽管它可以选择超过它们(在这种情况下,父视图可以选择做什么,包括裁剪,滚动,抛出一个异常,或者要求onMeasure()再次尝试,或使用不同的测量规格)
3. 宽高计算完毕后,必须调用用setMeasuredDimession(int width, int height),进行设置。否则将抛出一个异常
下面是一些View中可被调用的方法总结(未全部包含,可自行查看类似onXxx的方法):
Category | Methods | Description |
Creation | Constructors | There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file. |
Called after a view and all of its children has been inflated from XML. | ||
Layout | Called to determine the size requirements for this view and all of its children. | |
Called when this view should assign a size and position to all of its children. | ||
Called when the size of this view has changed. | ||
Drawing | | Called when the view should render its content. |
Event processing | Called when a new key event occurs. | |
Called when a key up event occurs. | ||
Called when a trackball motion event occurs. | ||
Called when a touch screen motion event occurs. | ||
Focus | Called when the view gains or loses focus. | |
Called when the window containing the view gains or loses focus. | ||
Attaching | Called when the view is attached to a window. | |
Called when the view is detached from its window. | ||
Called when the visibility of the window containing the view has changed. |
自定义View示例
adi-demo下的示例:LabelView
1. /*
2. * Copyright (C) 2007 The Android Open Source Project
3. *
4. * Licensed under the Apache License, Version 2.0 (the "License");
5. * you may not use this file except in compliance with the License.
6. * You may obtain a copy of the License at
7. *
8. * http://www.apache.org/licenses/LICENSE-2.0
9. *
10. * Unless required by applicable law or agreed to in writing, software
11. * distributed under the License is distributed on an "AS IS" BASIS,
12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13. * See the License for the specific language governing permissions and
14. * limitations under the License.
15. */
16.
17. package android.widget;
18.
19. import android.content.Context;
20. import android.graphics.Canvas;
21. import android.graphics.Paint;
22. import android.view.View;
23.
24. /**
25. * Example of how to write a custom subclass of View. LabelView
26. * is used to draw simple text views. Note that it does not handle
27. * styled text or right-to-left writing systems.
28. *
29. */
30. public class LabelView extends View {
31. /**
32. * Constructor. This version is only needed if you will be instantiating
33. * the object manually (not from a layout XML file).
34. * @param context the application environment
35. */
36. public LabelView(Context context) {
37. super(context);
38. initLabelView();
39. }
40.
41. /**
42. * Construct object, initializing with any attributes we understand from a
43. * layout file. These attributes are defined in
44. * SDK/assets/res/any/classes.xml.
45. *
46. * @see android.view.View#View(android.content.Context, android.util.AttributeSet)
47. public LabelView(Context context, AttributeSet attrs) {
48. super(context, attrs);
49. initLabelView();
50.
51. Resources.StyledAttributes a = context.obtainStyledAttributes(attrs,
52. R.styleable.LabelView);
53.
54. CharSequence s = a.getString(R.styleable.LabelView_text);
55. if (s != null) {
56. setText(s.toString());
57. }
58.
59. ColorStateList textColor = a.getColorList(R.styleable.
60. LabelView_textColor);
61. if (textColor != null) {
62. setTextColor(textColor.getDefaultColor(0));
63. }
64.
65. int textSize = a.getInt(R.styleable.LabelView_textSize, 0);
66. if (textSize > 0) {
67. setTextSize(textSize);
68. }
69.
70. a.recycle();
71. }
72.
73. */
74. private void initLabelView() {
75. new Paint();
76. true);
77. 16);
78. 0xFF000000);
79.
80. 3;
81. 3;
82. 3;
83. 3;
84. }
85.
86. /**
87. * Sets the text to display in this label
88. * @param text The text to display. This will be drawn as one line.
89. */
90. public void setText(String text) {
91. mText = text;
92. requestLayout();
93. invalidate();
94. }
95.
96. /**
97. * Sets the text size for this label
98. * @param size Font size
99. */
100. public void setTextSize(int size) {
101. mTextPaint.setTextSize(size);
102. requestLayout();
103. invalidate();
104. }
105.
106. /**
107. * Sets the text color for this label
108. * @param color ARGB value for the text
109. */
110. public void setTextColor(int color) {
111. mTextPaint.setColor(color);
112. invalidate();
113. }
114.
115.
116. /**
117. * @see android.view.View#measure(int, int)
118. */
119. @Override
120. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
121. setMeasuredDimension(measureWidth(widthMeasureSpec),
122. measureHeight(heightMeasureSpec));
123. }
124.
125. /**
126. * Determines the width of this view
127. * @param measureSpec A measureSpec packed into an int
128. * @return The width of the view, honoring constraints from measureSpec
129. */
130. private int measureWidth(int measureSpec) {
131. int result;
132. int specMode = MeasureSpec.getMode(measureSpec);
133. int specSize = MeasureSpec.getSize(measureSpec);
134.
135. if (specMode == MeasureSpec.EXACTLY) {
136. // We were told how big to be
137. result = specSize;
138. else {
139. // Measure the text
140. int) mTextPaint.measureText(mText) + mPaddingLeft
141. + mPaddingRight;
142. if (specMode == MeasureSpec.AT_MOST) {
143. // Respect AT_MOST value if that was what is called for by measureSpec
144. result = Math.min(result, specSize);
145. }
146. }
147.
148. return result;
149. }
150.
151. /**
152. * Determines the height of this view
153. * @param measureSpec A measureSpec packed into an int
154. * @return The height of the view, honoring constraints from measureSpec
155. */
156. private int measureHeight(int measureSpec) {
157. int result;
158. int specMode = MeasureSpec.getMode(measureSpec);
159. int specSize = MeasureSpec.getSize(measureSpec);
160.
161. int) mTextPaint.ascent();
162. if (specMode == MeasureSpec.EXACTLY) {
163. // We were told how big to be
164. result = specSize;
165. else {
166. // Measure the text (beware: ascent is a negative number)
167. int) (-mAscent + mTextPaint.descent()) + mPaddingTop
168. + mPaddingBottom;
169. if (specMode == MeasureSpec.AT_MOST) {
170. // Respect AT_MOST value if that was what is called for by measureSpec
171. result = Math.min(result, specSize);
172. }
173. }
174. return result;
175. }
176.
177. /**
178. * Render the text
179. *
180. * @see android.view.View#onDraw(android.graphics.Canvas)
181. */
182. @Override
183. protected void onDraw(Canvas canvas) {
184. super.onDraw(canvas);
185. canvas.drawText(mText, mPaddingLeft, mPaddingTop - mAscent, mTextPaint);
186. }
187.
188. private Paint mTextPaint;
189. private String mText;
190. private int mAscent;
191. }
应用该自定义组件的layout xml:
1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2. xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"
3. android:orientation="vertical"
4. android:layout_width="match_parent"
5. android:layout_height="wrap_content">
6.
7. <com.example.android.apis.view.LabelView
8. android:background="@drawable/red"
9. android:layout_width="match_parent"
10. android:layout_height="wrap_content"
11. app:text="Red"/>
12.
13. <com.example.android.apis.view.LabelView
14. android:background="@drawable/blue"
15. android:layout_width="match_parent"
16. android:layout_height="wrap_content"
17. app:text="Blue" app:textSize="20dp"/>
18.
19. <com.example.android.apis.view.LabelView
20. android:background="@drawable/green"
21. android:layout_width="match_parent"
22. android:layout_height="wrap_content"
23. app:text="Green" app:textColor="#ffffffff" />
24.
25. </LinearLayout>
该示例演示了:
1. 继承自View的完全自定义组件
2. 带参数的构造函数(一些属性参数在xml中设置)。还使用了自定义属性 R.styleable.LabelView
3. 一些标准的public 方法,如setText()、setTextSize()、setTextColor()
4. onMeasure()测量组件尺寸,内部由measureWidth(int measureSpec) 和 measureHeight(int measureSpec)来测量。
5. onDraw()将Label绘制到画面Canvas上
复合组件
由一些现有组件,复合成一个新的组件。
要创建一个复合组件:
1. 通常需要创建一个类,继承自一个Layout,或者ViewGroup。
2. 在构造函数中,需要先调用父类相应的构造函数。然后设置一些需要的组件用于复合。可以使用自定义属性
3. 可以创建监听器,监听处理一些可能的动作
4. 可能有一些 属性的 get / set 方法
5. 如果继承自某一Layout类时,不需要重写onDraw()和onMeasure(),因为Layout类中有默认的行为。如有必要,当然也可以重写
6. 可能重写其他一些onXxx(),以达到你想要的效果
复合组件示例
api-demo下的List4和List6里的内部类SpeachView,以下为List6中的源码
1. private class SpeechView extends LinearLayout {
2. public SpeechView(Context context, String title, String dialogue, boolean expanded) {
3. super(context);
4.
5. this.setOrientation(VERTICAL);
6.
7. // Here we build the child views in code. They could also have
8. // been specified in an XML file.
9.
10. new TextView(context);
11. mTitle.setText(title);
12. new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
13.
14. new TextView(context);
15. mDialogue.setText(dialogue);
16. new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
17.
18. mDialogue.setVisibility(expanded ? VISIBLE : GONE);
19. }
20.
21. /**
22. * Convenience method to set the title of a SpeechView
23. */
24. public void setTitle(String title) {
25. mTitle.setText(title);
26. }
27.
28. /**
29. * Convenience method to set the dialogue of a SpeechView
30. */
31. public void setDialogue(String words) {
32. mDialogue.setText(words);
33. }
34.
35. /**
36. * Convenience method to expand or hide the dialogue
37. */
38. public void setExpanded(boolean expanded) {//该方法在List4中没有
39. mDialogue.setVisibility(expanded ? VISIBLE : GONE);
40. }
41.
42. private TextView mTitle;
43. private TextView mDialogue;
44. }
SpeachView,继承了LinearLayout,纵向布局。内部有一个TextView的title,一个TextView的dialogue。List4完全展开两个TextView;List6点击title可以收缩/展开dialogue。
修改现有View类型
继承自一个现有的View,以增强其功能,满足需要。
sdk中有个记事本NotePad的示例工程。其中有一个类就是扩展了EditText。
在NoteEditor类中:
1. public static class LinedEditText extends EditText {
2. private Rect mRect;
3. private Paint mPaint;
4.
5. // This constructor is used by LayoutInflater
6. public LinedEditText(Context context, AttributeSet attrs) {
7. super(context, attrs);
8.
9. // Creates a Rect and a Paint object, and sets the style and color of the Paint object.
10. new Rect();
11. new Paint();
12. mPaint.setStyle(Paint.Style.STROKE);
13. 0x800000FF);
14. }
15.
16. /**
17. * This is called to draw the LinedEditText object
18. * @param canvas The canvas on which the background is drawn.
19. */
20. @Override
21. protected void onDraw(Canvas canvas) {
22.
23. // Gets the number of lines of text in the View.
24. int count = getLineCount(); //edittext中有几行, edittext继承textview
25.
26. // Gets the global Rect and Paint objects
27. Rect r = mRect;
28. Paint paint = mPaint;
29.
30. /*
31. * Draws one line in the rectangle for every line of text in the EditText
32. */
33. for (int i = 0; i < count; i++) {
34.
35. // Gets the baseline coordinates for the current line of text
36. int baseline = getLineBounds(i, r);//将一行的范围坐标赋给矩形r;返回一行y方向上的基准线坐标
37.
38. /*
39. * Draws a line in the background from the left of the rectangle to the right,
40. * at a vertical position one dip below the baseline, using the "paint" object
41. * for details.
42. */
43. 1, r.right, baseline + 1, paint);//绘制一条线,宽度为原行的宽度,高度为从基线开始+1个像素
44. }
45.
46. // Finishes up by calling the parent method
47. super.onDraw(canvas);
48. }
49. }
定义
一个public的静态内部类,以便它可以被访问:NoteEditor.MyEditText
它是静态内部类,意味着,它不依靠外部类的成员,不会产生一些“组合的方法”。
继承自EditText
类的初始化
构造函数中,先调用父类的构造方法,并且它是带属性参数的构造函数。在使用时,从一个xml布局文件inflate
重写的方法
只有onDraw()被重写。在onDraw()中绘制了一条蓝色的线,该线从每行文本的的基线开始向下1像素,宽度为行宽。
方法结束前,调用super.onDraw()
使用自定义组件
1. <view xmlns:android="http://schemas.android.com/apk/res/android"
2. class="com.example.android.notepad.NoteEditor$LinedEditText"
3. android:id="@+id/note"
4. android:layout_width="match_parent"
5. android:layout_height="match_parent"
6. android:background="@android:color/transparent"
7. android:padding="5dp"
8. android:scrollbars="vertical"
9. android:fadingEdge="vertical"
10. android:gravity="top"
11. android:textSize="22sp"
12. android:capitalize="sentences"
13. />
使用完全限定类名,引入自定义组件。使用$引用内部类。