前言
作为一名有创新意思的开发人员,你迟早会发现内置的控件会满足不了你的想象力。
拥有扩展已存在的视图、组建复合的控件以及创建独特的新视图能力,可以创建出最适合自己应用程序工作流的有优美用户界面,让用户得到最优的体验。
创建新视图的最佳方法和希望达到的目标有关:
1.如果现有控件已经可以满足希望实现的基本功能,那么只需对现有控件的外观或行为进行修改或扩展即可。通过重写事件处理程序和onDraw()方法。
2.可以通过组合多个视图来创建不可分割的、可重用的控件,从而使它可以综合使用过个相关联的视图功能,比如一键清空TextView组合控件。
3.创建一个全新的控件。
下面我们通过一个小实例,创建一个罗盘界面来体验一下如何自定义控件。
一.创建自定义控件类Compass,继承View:
- package com.example.compass;
- import android.content.Context;
- import android.content.res.Resources;
- import android.graphics.Canvas;
- import android.graphics.Paint;
- import android.util.AttributeSet;
- import android.view.View;
- import android.view.accessibility.AccessibilityEvent;
- public class Compass extends View {
- private Paint makerPaint;
- private Paint textPaint;
- private Paint circlePaint;
- private String north, south, east, west;
- private int textHeight;
- public Compass(Context context) {
- super(context);
- initCompassView();
- }
- public Compass(Context context, AttributeSet attrs) {
- super(context, attrs);
- initCompassView();
- }
- public Compass(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initCompassView();
- }
- private void initCompassView() {
- setFocusable(true);
- Resources r = this.getResources();
- // 画圆
- circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- circlePaint.setColor(r.getColor(R.color.background_color));
- circlePaint.setStrokeWidth(1);
- circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
- north = r.getString(R.string.cardinal_north);
- south = r.getString(R.string.cardinal_south);
- east = r.getString(R.string.cardinal_east);
- west = r.getString(R.string.cardinal_west);
- textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- textPaint.setColor(r.getColor(R.color.text_color));
- textHeight = (int) textPaint.measureText("yY");
- makerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//抗锯齿
- makerPaint.setColor(r.getColor(R.color.maker_color));
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int measureWidth = measure(widthMeasureSpec);
- int measureHeight = measure(heightMeasureSpec);
- int d = Math.min(measureHeight, measureWidth);
- setMeasuredDimension(d, d);
- }
- private int measure(int measureSpec) {
- int result = 0;
- // 对测量说明进行解码
- int speMode = MeasureSpec.getMode(measureSpec);
- int speSize = MeasureSpec.getSize(measureSpec);
- if (speMode == MeasureSpec.UNSPECIFIED) {
- // 如果没有指定界限,则默认返回大小200
- result = 200;
- } else {
- // 由于你希望填充可以的空间,所有总是返回整个可用的的边界
- result = speSize;
- }
- return result;
- }
- //添加属性
- private float bearing;
- public float getBearing() {
- return bearing;
- }
- public void setBearing(float _bearing) {
- bearing = _bearing;
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);//添加可访问性支持,罗盘显示方向
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- int mMeasureWidth = getMeasuredWidth();
- int mMeasureHeight = getMeasuredHeight();
- int px = mMeasureWidth / 2;
- int py = mMeasureHeight / 2;
- int radius = Math.min(px, py);//去最小值作为半径;
- // 绘制背景
- canvas.drawCircle(px, py, radius, circlePaint);
- canvas.save();
- canvas.rotate(-bearing, px, py);// 旋转-bearing度角度;
- // 绘制标记
- int textWidth = (int) textPaint.measureText("W");
- int cardinalX = px - textWidth / 2;
- int cardinalY = py - radius + textHeight;
- // 每15度绘制一个标记,每45度绘制一个文本
- for (int i = 0; i < 24; i++) {
- canvas.drawLine(px, py - radius, px, py - radius + 10, makerPaint);
- canvas.save();
- canvas.translate(0, textHeight);
- // 绘制基本方位
- if (i % 6 == 0) {
- String dirString = "";
- switch (i) {
- case 0:
- dirString = north;
- int arrowY = 2 * textHeight;
- canvas.drawLine(px, arrowY, px - 5, 3 * textHeight, makerPaint);
- canvas.drawLine(px, arrowY, px + 5, 3 * textHeight, makerPaint);
- break;
- case 6:
- dirString = east;
- break;
- case 12:
- dirString = south;
- break;
- case 18:
- dirString = west;
- break;
- default:
- break;
- }
- canvas.drawText(dirString, cardinalX, cardinalY, textPaint);
- // 每45度绘制文本
- } else if (i % 3 == 0) {
- String angle = String.valueOf(i * 15);
- float angleTextWidth = textPaint.measureText(angle);
- int angleTextX = (int) (px - angleTextWidth / 2);
- int angleTextY = py - radius + textHeight;
- canvas.drawText(angle, angleTextX, angleTextY, textPaint);
- }
- canvas.restore();
- canvas.rotate(15, px, py);
- }
- canvas.restore();
- }
- // 将当前方向用作可访问性事件使用的内容
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.dispatchPopulateAccessibilityEvent(event);
- if (isShown()) {
- String bearingStr = String.valueOf(bearing);
- if (bearingStr.length() > AccessibilityEvent.MAX_TEXT_LENGTH)
- bearingStr = bearingStr.substring(0, AccessibilityEvent.MAX_TEXT_LENGTH);
- event.getText().add(bearingStr);
- return true;
- } else {
- return false;
- }
- }
- }
二、配置属性
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <string name="app_name">Compass</string>
- <string name="hello_world">Hello world!</string>
- <string name="action_settings">Settings</string>
- <string name="cardinal_north" >N</string>
- <string name="cardinal_east" >E</string>
- <string name="cardinal_south" >S</string>
- <string name="cardinal_west" >W</string>
- </resources>
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <color name="background_color">#F555</color>
- <color name="maker_color">#AFFF</color>
- <color name="text_color">#AFFF</color>
- </resources>
三、引入自定义控件
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <com.example.compass.Compass
- android:id="@+id/compass"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- </RelativeLayout>
- package com.example.compass;
- import android.app.Activity;
- import android.os.Bundle;
- public class MainActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Compass compass=(Compass) this.findViewById(R.id.compass);
- compass.setBearing(0);
- }
- }
运行实例: