Android 自定义View

声明,翻译自谷歌官方文档:http://developer.android.com/training/custom-views/create-view.html


一、创建View类

一个好的自定义view应该遵循以下规则:

  • 符合android标准
  • 提供自定义的 styleable attributes 以便在XML layouts中试用。
  • 设计辅助功能。(这个是用来支持无障碍阅读的)
  • 兼容android的多平台。
android 的 framework 提供了一系列的 类和 XML 的标签来帮助创建 符合上述规则的view。本课程将讨论怎么使用 Android的framework 创建 view的核心功能。

1.继承View类
Android framework中的所有view都继承自View类,所以 你自定义的view也应该继承自 View类,或者也可以继承自 framework中的其它已经存在的view 类(例如:Button类)。

为了允许  Android Developer Tools 关联到你的 view(在XML 布局中预览你的view),你必须声明一个包含  Context 和AttributeSet 参数的构造方法。 这个构造函数允许 布局编辑器 来创建和编辑 你的view。
class PieChart extends View {
    public PieChart(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
2.声明自定义属性(Attributes)

要在XML布局中添加你的view,并通过XML 来控制view的外观和行为,你必须实现以下功能:
  •  res/values/attrs.xml 资源文件中添加 <declare-styleable>声明自定属性。
  • 在XML布局中指定 属性的值。
  • 在运行时解析 指定的属性值。
  • 根据解析的数据值来初始化view。
下面讲解如何自定义属性:
需要在 res/values/attrs.xml  资源文件中声明<declare-styleable> 标签,直接看例子:

<resources>
   <declare-styleable name="PieChart">
       <attr name="showText" format="boolean" />
       <attr name="labelPosition" format="enum">
           <enum name="left" value="0"/>
           <enum name="right" value="1"/>
       </attr>
   </declare-styleable>
</resources>
name属性必须指定,在运行时需要根据这个名称来取得 自定义属性列表中的所有值。具体attr 属性的定义规则,这里不做讲解,可以自行百度。需要注意的一点是自定义属性的 name 值应该与你的自定view的 类名相同,这样方便IDE来自动补全代码。

定义好属性后,可以在XML布局中调用,调用之前需要声明一个自定义命名空间,这个是用来区分 属性归属的,下面看例子:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">
 <com.example.customviews.charting.PieChart
     custom:showText="true"
     custom:labelPosition="left" />
</LinearLayout>

每个布局中都会有
 xmlns:android="http://schemas.android.com/apk/res/android"
     这个命名空间,这个是声明 android framework中的view需要的属性的命名空间,而自定义的属性命名空间需要 http://schemas.android.com/apk/res/[your package name] 这样申请。申请完后就可以通过 命名空间  名称 来调用了 。  


3.应用在XML布局中设置的自定义属性
当view 从XML 布局中创建时,所有在XML标签中声明的属性都会通过AttributeSet  传递给view的构造方法。这样就能够直接从传递过来的AttributeSet 中读取到自定义属性的值了,这样做有以下几个缺点;
  •      在AttributeSet  的属性值不会自动解析。
  •      主题Styles 不会自动申请。
而是会传递 AttributeSet     给 obtainStyledAttributes()   。这个方法会返回一个用来提取已经被样式化的属性值集合 TypedArray  。
 Android的资源编辑器在你调用obtainStyledAttributes() 时会做很多工作,会把所有的<declare-styleable>   值封装到  TypedArray 中,而你需要从TypedArray  中提取需要的属性值,看例子:
public PieChart(Context context, AttributeSet attrs) {
   super(context, attrs);
   TypedArray a = context.getTheme().obtainStyledAttributes(
        attrs,
        R.styleable.PieChart,
        0, 0);

   try {
       mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
       mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
   } finally {
       a.recycle();
   }
}
注意: TypedArray   是系统共享资源,所以必须在用完后 回收掉!

4.动态添加属性和事件
在XML中添加属性是非常强大的方法来控制view的显示和行为,但是只能在初始化时控制view。如果需要动态的控制view的属性就需要公开每个 属性的 getter和 setter 方法,看例子:
public boolean isShowText() {
   return mShowText;
}

public void setShowText(boolean showText) {
   mShowText = showText;
   invalidate();
   requestLayout();
}
注意: setShowText  调用了   invalidate() 和 requestLayout() 方法。这两个方法的调用是非常重要的能够确保view的可靠性。 invalidate() 让系统重新绘制view 、 requestLayout() 通知系统重新布局view(例如view的尺寸,形状改变等)。     
自定义view 应该支持监听view的重要事件。例如 PieChart 声明一个自定义的事件回调OnCurrentItemChanged  来通知监听者 用户旋转了饼图。

5.设计辅助功能
自定义view应该支持更广泛的使用者,包括聋哑人和盲人等,为了支持辅助功能,你应该:
  • 使用Android的标签输入字段android:contentDescription 。
  •  在适当的时候通过调用  sendAccessibilityEvent() 发送辅助事件。
  • 支持设备控制器,例如 轨迹球等。
更多关于辅助功能请访问   Making Applications Accessible 。
  
二、Custom Drawing
自定义view的最重要的就是其外观。自定义绘制 能够非常容易或者复杂的实现你的需求。本课程介绍一些最常见的操作。

1.重写 onDraw()方法
自定义view的最重要的步骤是重写 onDraw()  方法。该方法的参数是Canvas 类,让view可以来绘制自己。Canvas 类定义了绘制文本、线条、位图和许多其它基本图形的方法。你可以在onDraw()   方法中创建自定义的 UI。

在可以调用 绘制方法的前,需要创建 Paint  类。

2.Create Drawing Objects (创建绘图对象)
android.graphics framework 把绘制分成两个部分:
  • 绘制什么,通过Canvas 实现。
  • 怎么绘制,通过Paint 实现。
例如:Canvas 提供方法来绘制线条,而Paint 提供方法来确定线条的颜色。Canvas 提供方法来绘制矩形,而Paint 提供方法来确定怎样填充矩形。总的来说,Canvas 提供方法绘制形状,而Paint 提供方法来确定形状的颜色、样式、字体等。

     所以在你绘制任何东西之前,你需要创建一个或者多个Paint 对象。 PieChart 例子在init 方法(在构造函数中调用)中声明:
private void init() {
   mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mTextPaint.setColor(mTextColor);
   if (mTextHeight == 0) {
       mTextHeight = mTextPaint.getTextSize();
   } else {
       mTextPaint.setTextSize(mTextHeight);
   }

   mPiePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   mPiePaint.setStyle(Paint.Style.FILL);
   mPiePaint.setTextSize(mTextHeight);

   mShadowPaint = new Paint(0);
   mShadowPaint.setColor(0xff101010);
   mShadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL));

   ...
提前创建对象是一个重要的优化。Views 会非常频繁的重绘,并且许多对象都需要昂贵的初始化。在 onDraw()  方法中创建绘制对象会显著的降低性能,造成UI卡顿。

3.Handle Layout Events
为了正确的绘制出自定义的view,需要确定view的尺寸。复杂的自定义view需要根据多个布局在屏幕上的大小和形状来计算。哪怕只有一个app使用你的view,你都不能假设自定义view在屏幕上的尺寸。你应该处理不同屏幕尺寸、多屏幕密度以及各种宽高纵横比和屏幕方向。

虽然View 有很多方法来获取尺寸,但是大多数的这些方法不需要重写。如果自定义view不需要特别控制它的尺寸,只需要重写一个方法:onSizeChanged() 。

 onSizeChanged() 方法会在view第一次分配尺寸大小 或者 由于其它原因view的尺寸改变了 的时候调用。在onSizeChanged() 方法中计算view的位置、尺寸和其它与view尺寸有关的值,而不是每次绘制view的时候计算。 在 PieChart  例子中,在onSizeChanged() 方法中计算了 饼图的边框、文本标签 和 其它视觉元素的 相对位置。

当view被分配给一个尺寸,布局管理器会设定这个大小包括视图的所有padding。当你计算view的尺寸时,你必须自己取得padding值。下面是PieChart.onSizeChanged() :
 // Account for padding
       float xpad = (float)(getPaddingLeft() + getPaddingRight());
       float ypad = (float)(getPaddingTop() + getPaddingBottom());

       // Account for the label
       if (mShowText) xpad += mTextWidth;

       float ww = (float)w - xpad;
       float hh = (float)h - ypad;

       // Figure out how big we can make the pie.
       float diameter = Math.min(ww, hh);
如果需要更好的控制view的布局参数,重写 onMeasure() 方法。这个方法 的参数是 View.MeasureSpec 它会描述view的 父容器分配给view的尺寸,和分配的尺寸是硬性的最大值还是只是一个建议。为了优化,这些值都会压缩成整数,可以使用 View.MeasureSpec 的静态方法来解压出存储的每个整数信息。

下面是一个重写 onMeasure() 方法的例子。在实现过程中,PieChart  视图使它的区域足够大来使饼图能够放的下它的标签。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   // Try for a width based on our minimum
   int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
   int w = resolveSizeAndState(minw, widthMeasureSpec, 1);

   // Whatever the width ends up being, ask for a height that would let the pie
   // get as big as it can
   int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
   int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);

   setMeasuredDimension(w, h);
}
在代码中有三个地方需要注意:
  
4.Draw !
  完成上述的初始化和测量代码后,就可以重写 onDraw() 方法了。每个view重写的 onDraw()  方法都是不同的,但是也有一些相同的常用操作: 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值