本文为翻译官网的介绍,官网地址:http://developer.android.com/guide/topics/ui/custom-components.html
简介
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
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;
/**
* Example of how to write a custom subclass of View. LabelView
* is used to draw simple text views. Note that it does not handle
* styled text or right-to-left writing systems.
*
*/
public class LabelView extends View {
/**
* Constructor. This version is only needed if you will be instantiating
* the object manually (not from a layout XML file).
* @param context the application environment
*/
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();
Resources.StyledAttributes a = context.obtainStyledAttributes(attrs,
R.styleable.LabelView);
CharSequence s = a.getString(R.styleable.LabelView_text);
if (s != null) {
setText(s.toString());
}
ColorStateList textColor = a.getColorList(R.styleable.
LabelView_textColor);
if (textColor != null) {
setTextColor(textColor.getDefaultColor(0));
}
int textSize = a.getInt(R.styleable.LabelView_textSize, 0);
if (textSize > 0) {
setTextSize(textSize);
}
a.recycle();
}
*/
private void initLabelView() {
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(16);
mTextPaint.setColor(0xFF000000);
mPaddingLeft = 3;
mPaddingTop = 3;
mPaddingRight = 3;
mPaddingBottom = 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) {
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) {
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;
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) + mPaddingLeft
+ mPaddingRight;
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;
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)
result = (int) (-mAscent + mTextPaint.descent()) + mPaddingTop
+ mPaddingBottom;
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);
canvas.drawText(mText, mPaddingLeft, mPaddingTop - mAscent, mTextPaint);
}
private Paint mTextPaint;
private String mText;
private int mAscent;
}
应用该自定义组件的layout xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.android.apis.view.LabelView
android:background="@drawable/red"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:text="Red"/>
<com.example.android.apis.view.LabelView
android:background="@drawable/blue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:text="Blue" app:textSize="20dp"/>
<com.example.android.apis.view.LabelView
android:background="@drawable/green"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:text="Green" app:textColor="#ffffffff" />
</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上
复合组件
复合组件示例
private class SpeechView extends LinearLayout {
public SpeechView(Context context, String title, String dialogue, boolean expanded) {
super(context);
this.setOrientation(VERTICAL);
// Here we build the child views in code. They could also have
// been specified in an XML file.
mTitle = new TextView(context);
mTitle.setText(title);
addView(mTitle, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mDialogue = new TextView(context);
mDialogue.setText(dialogue);
addView(mDialogue, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mDialogue.setVisibility(expanded ? VISIBLE : GONE);
}
/**
* Convenience method to set the title of a SpeechView
*/
public void setTitle(String title) {
mTitle.setText(title);
}
/**
* Convenience method to set the dialogue of a SpeechView
*/
public void setDialogue(String words) {
mDialogue.setText(words);
}
/**
* Convenience method to expand or hide the dialogue
*/
public void setExpanded(boolean expanded) {//该方法在List4中没有
mDialogue.setVisibility(expanded ? VISIBLE : GONE);
}
private TextView mTitle;
private TextView mDialogue;
}
SpeachView,继承了LinearLayout,纵向布局。内部有一个TextView的title,一个TextView的dialogue。List4完全展开两个TextView;List6点击title可以收缩/展开dialogue。
修改现有View类型
public static class LinedEditText extends EditText {
private Rect mRect;
private Paint mPaint;
// This constructor is used by LayoutInflater
public LinedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
// Creates a Rect and a Paint object, and sets the style and color of the Paint object.
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0x800000FF);
}
/**
* This is called to draw the LinedEditText object
* @param canvas The canvas on which the background is drawn.
*/
@Override
protected void onDraw(Canvas canvas) {
// Gets the number of lines of text in the View.
int count = getLineCount(); //edittext中有几行, edittext继承textview
// Gets the global Rect and Paint objects
Rect r = mRect;
Paint paint = mPaint;
/*
* Draws one line in the rectangle for every line of text in the EditText
*/
for (int i = 0; i < count; i++) {
// Gets the baseline coordinates for the current line of text
int baseline = getLineBounds(i, r);//将一行的范围坐标赋给矩形r;返回一行y方向上的基准线坐标
/*
* Draws a line in the background from the left of the rectangle to the right,
* at a vertical position one dip below the baseline, using the "paint" object
* for details.
*/
canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);//绘制一条线,宽度为原行的宽度,高度为从基线开始+1个像素
}
// Finishes up by calling the parent method
super.onDraw(canvas);
}
}
定义
类的初始化
重写的方法
使用自定义组件
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="com.example.android.notepad.NoteEditor$LinedEditText"
android:id="@+id/note"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:padding="5dp"
android:scrollbars="vertical"
android:fadingEdge="vertical"
android:gravity="top"
android:textSize="22sp"
android:capitalize="sentences"
/>
使用完全限定类名,引入自定义组件。使用$引用内部类。