StupidBeauty
Read times:1948Posted at: - no title specified

安卓开发文档翻译:画布和绘图对象,Canvas and Drawables

安卓框架的应用编程接口提供了一组二维绘图的应用编程接口,可用来:将妳的自定义图形渲染到一个画布上;或者,修改已有的视图(Views),以对其观感进行自定义。在进行二维绘图时,一般采用以下两种方法中的一种:

  1. a.将图形或动画绘制到妳的界面布局上的某个视图(View)对象中去。在这种用法中,妳的图形的绘制过程是由系统的普通View 层级绘图过程来管理的——妳只需要定义那些想要绘制到该View 中去的图形即可。

  2. b. 将妳自己的图形直接绘制到一个画布(Canvas)中去。在这种用法中,妳需要自行调用对应类的 onDraw() 方法(需传入妳的Canvas),或者调用Canvas 中的那些 draw...() 方法(例如 drawPicture() )。这样做的话,妳也能够控制任何动画。

选项"a",即,绘制到一个View中,最适合于以下情况:妳想要绘制简单的图形,它们不需要动态改变, 并且它们不是处于一个性能敏感的游戏中。例如,这种情况下,妳应当将图形绘制到某个View 中:妳想要在一个原本处于静态的应用程序中显示一个静态的图形或预设的动画。阅读 绘图对象 以了解更多信息。

选项"b",即,绘制到一个Canvas中,更适合于这种情况:妳需要在应用程序中频繁地重绘自身。类似视频游戏那样的应用程序,应当向自己的Canvas 中绘制。然而,具体的做法也不止一种:

  • •. 在妳的用户界面(UI)活动(Activity)所处的线程中做这件事,具体地:在布局中创建一个自定义的View组件,调用其 invalidate() 方法,然后在 onDraw() 回调函数中做出响应。

  • •.或者,在一个单独的线程中,管理一个 SurfaceView ,并且,以妳的线程能够达到的最快速度来向该Canvas 中绘图(妳无需请求 invalidate())

对一个Canvas进行绘图

如果妳的程序中需要进行特殊的绘图操作以及/或者控制图形的动画,那么,妳应当向一个 画布 进行绘图。Canvas充当着一个代理或接口的角色,代表着妳要向其上做绘制动作的实际表面(surface)——它储存着妳的所有的“绘图”调用。在对这个Canvas进行绘图的过程中,妳绘制的那些图形实际上是被放置到一个底层的 位图 上去了,该位图会被放置到窗口中。

onDraw() 回调方法对应的绘图事件中,会向妳传入对应的Canvas,妳只需将绘图调用放置于其上即可。在处理SurfaceView 对象时,可利用 SurfaceHolder.lockCanvas() 来获取到一个Canvas。(这两种情形都会在接下来的小节中说明。)然而,如果妳需要创建一个新的Canvas,那么,妳必须定义一个 Bitmap ,因为实际的绘图操作会被放置于其上。对于一个Canvas,是必须有一个对应的Bitmap。妳可以这样创建一个新的Canvas:

Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);

Canvas c = new Canvas(b);

现在,可以通过Canvas 向刚定义的这个Bitmap 来绘图了。在通过Canvas 向它做了绘图操作之后,可以使用那些 Canvas.drawBitmap(Bitmap,...) 方法来将该Bitmap 放置到另一个Canvas 中去。我们建议,妳最终使用通过 View.onDraw() SurfaceHolder.lockCanvas() 传递给妳的那个Canvas 来将最终图像绘制出来(参考后续小节)。

Canvas 类有着自己的绘图方法集合,例如: drawBitmap(...) drawRect(...) drawText(...) 以及其它的狠多。妳可能会用到的其它类,也会对应的 draw() 方法。例如,妳可能会有一些想要放置到该Canvas 中去的 Drawable 对象。Drawable有着自己的 draw() 方法,其中的一个参数便是妳的Canvas。

对一个View进行绘图

如果妳的应用程序不需要进行大量的处理,或者不需要达到较高的帧率(例如,一个象棋游戏、一个贪吃蛇游戏,或者别的什么只有慢速动画的应用程序),那么,妳应当考虑创建一个自定义的View组件,并且在 View.onDraw() 中使用一个Canvas 来进行绘图。这种做法中最方便之处就是,安卓框架会向妳提供一个预定义好的Canvas,妳只需向它绘图即可。

那么,具体的做法就是,继承 View 类(或者它的什么后代类),并且定义 onDraw() 这个回调方法。安卓框架会在适当的时机调用该方法,以请求妳的View 来绘制自身。这就是妳应当在 Canvas 上进行所有的绘图调用的地方,该 Canvas 会通过 onDraw() 回调方法传递给妳。

安卓框架只会在必要的时候调用 onDraw() 。每当妳的应用程序准备好绘制自身的时候,都需要通过调用 invalidate() 来请求妳的View 变成无效的。这会表明妳想要让妳的View被绘制,于是,安卓就会调用妳的 onDraw()方法(不过,并不会保证该回调函数会立即被调用)

在妳的View组件 onDraw()方法中,使用传递给妳的那个Canvas来进行所有的绘图操作,在这个过程中,可使用各种各样的 Canvas.draw...()方法,或者其它类中的那些以妳的Canvas 作为一个参数的 draw() 方法。当妳的 onDraw()执行完毕之后,安卓框架会使用妳的Canvas来绘制一个由系统控制的Bitmap

注意: 要想在除妳的主活动(Activity)线程之外的线程中请求进行无效化标识的话,则, 妳必须调用 postInvalidate()

欲了解该如何继承 View 类,则阅读 构建自定义的组件

如想观摩一下示例应用程序,则,参考Snake游戏,位于,SDK示例目录 <your-sdk-directory>/samples/Snake/

对一个SurfaceView进行绘图

SurfaceView 是 View 的一个特殊子类,它在View 层级结构中提供了独特的绘图表面。目的是,底土这种绘图表面提供给应用程序的辅助线程,这样,应用程序就不用等到系统的View 层级结构做好准备才能进行绘图操作了。这样的话,引用了该SurfaceView 的辅助线程,可以以自己能够达到的速度来向自己的Canvas 上绘图。

首先,妳需要创建一个继承了 SurfaceView 的新类。这个类还应当实现 SurfaceHolder.Callback 。这个子类是一个接口,它会向妳告知底层 Surface 的一些信息,例如:什么时候被创建了;什么时候发生改变了;什么时候被销毁了。这些事件是狠重要的,有了这些事件,妳才会知道:什么时候可以开始绘图了;是否应当根据新的表面属性来做一些调整;什么时候该停止绘图,以及杀死必要的任务。在SurfaceView类的内部,适合于定义妳的辅助线程(Thread)类,该辅助类会对妳的Canvas 进行所有的绘图操作。

妳不应当直接处理该Surface 对象,而应当通过一个 SurfaceHolder 来处理它。也就是说,当妳的SurfaceView 被初始化之后,调用 getHolder() 来获取到对应的SurfaceHolder。然后,妳应当向该SurfaceHolder 告知,妳希望收到SurfaceHolder 相关的回调(来自 SurfaceHolder.Callback ),具体做法就是,调用 addCallback() (传入this作为参数)。然后,在妳的SurfaceView 类中覆盖那些 SurfaceHolder.Callback 方法。

为了在妳的辅助线程中对该Surface Canvas 进行绘图,妳必须向妳的线程中传入该SurfaceHandler,并且通过 lockCanvas() 来获取到该Canvas。然后,妳可以获取到由SurfaceHolder 提供的Canvas,并且对它进行绘图。当妳完成Canvas 上的绘图之后,调用 unlockCanvasAndPost() ,并且传入妳的Canvas 作为参数。然后,该Surface就会绘制该Canvas 了。每当妳想要重绘的时候,就按照这个流程来做,锁定画布、解锁画布,如此循环。

注意:每次妳从SurfaceHolder 获取到该Canvas 的时候,该Canvas 之前的状态会被保留。为了正确地呈现出动画效果,妳必须重绘整个表面。例如,妳可以如此清除该Canvas 在之前的状态:调用 drawColor() 来填充某个颜色;或者调用 drawBitmap() 来设置一个背景图片。否则,妳会看到之前绘制的东西的残影。

如想观摩一下示例应用程序,则,参考Lunar Lander游戏,位于SDK示例目录中: <your-sdk-directory>/samples/LunarLander/ 。或者,在 示例代码 小节中浏览源代码。

绘图对象Drawables

安卓提供了一个自定义的二维图形库,可用于绘制形状和图片。在 android.graphics.drawable 包中,可找到那些用来进行二维绘图的常用类。

这篇文档,说明了,使用Drawable 对象来绘制图像的基本知识,以及,如何使用Drawable 类的某些子类。欲知该如何利用Drawables 来产生逐帧动作,则,阅读 绘图对象动画

Drawable ,是这样一种通用的抽象:"某种能够绘制的东西"。妳会发现,Drawable类被扩展了,产生出了各种不同类型的绘图对象图形,包括: BitmapDrawable ShapeDrawable PictureDrawable LayerDrawable 还有些别的。当然,妳还可以继承这些类,以定义出妳自己的自定义Drawable对象,它们会有自己的独特行为。

有三种方式可用来定义及实例化一个Drawable:使用妳的项目的资源中存储的某张图片;使用某个定义了该Drawable 的各个属性的XML 文件;或者,使用普通的类构造函数。下面,我们会说明前面两种方式(对于一个经验充足的开发者来说,使用构造函数狠简单)

利用资源图片来创建

向妳的应用程序中加入图像的一种简单方式,就是,引用妳的项目资源中的某个图片文件。支持以下图片格式:PNG (优先考虑)JPG (可接受)GIF (不建议使用)。这种方式,适用于以下事物:应用程序的图标;标志;或者,其它的图像,例如,游戏中的图片。

要想使用一个图片资源的话,只需将它加入到妳的项目中的 res/drawable/ 目录。然后,妳可以利用代码或XML 布局来引用它。无论采用哪种方式,在引用它的时候,都需要给出一个资源编号(ID),即,去掉文件类型扩展名之后的文件名(例如,my_image.png ,则其资源编号为 my_image)

注意: res/drawable/ 中放置的那些图片资源,可能会在构建过程中被 aapt 工具自动利用无损图片压缩算法进行优化。例如,一张真彩色的PNG图片,如果其内容的颜色数实际上不超过256,那么,它可能会被转换为一张带调色板的8PNG图片。这样,将会产生一张具有同样质量而体积较小的图片。因此,请注意,这个目录中放置的图片,可能会在构建过程中发生变化。如果妳需要将图片作为位流读入,然后转换成一张位图的话,则,应当将它放置在 res/raw/ 目录中,那里的图片不会被优化。

示例代码

以下代码片断,展示的是,如何使用绘图对象资源中的某张图片来构建一个 ImageView ,并且将它加入到布局中。

LinearLayout mLinearLayout;

protected void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  // 创建一个LinearLayout,日后将向它加入ImageView

  mLinearLayout = new LinearLayout(this);

  // 实例化一个ImageView,并且定义它的属性

  ImageView i = new ImageView(this);

  i.setImageResource(R.drawable.my_image);

  i.setAdjustViewBounds(true); // 设置属性,使得该ImageView的边界与该Drawable 的尺寸相匹配

  i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT,

      LayoutParams.WRAP_CONTENT));

  // 将该ImageView加入到布局中,然后,将该布局设置为内容视图

  mLinearLayout.addView(i);

  setContentView(mLinearLayout);

}

在其它情况下,妳可能想要将妳的图片资源当作一个 Drawable 对象来对待。要想做到这一点的话,则,像这样来从资源中创建一个Drawable

Resources res = mContext.getResources();

Drawable myImage = res.getDrawable(R.drawable.my_image);

注意: 妳的项目中的每个唯一的资源,都只能维持在单个状态,无论妳用它实例化了多少个对象都是如此。例如,妳使用同一个图片资源实例化了两个Drawable对象,然后,改变了两个Drawables 中的一个对象的某个属性(例如透明度(alpha)),则,另一个也会受到影响。所以,当妳在处理单个图片资源的多个实例的时候,不要直接对该Drawable进行变换,而是,应当启动一个 补间动画

示例XML

下面XML片断,展示了,如何将一个资源Drawable添加到XML 布局中的某个 ImageView 中去(加入了一些红色的点缀,以增加乐趣)。

<ImageView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:tint="#55ff0000"

        android:src="@drawable/my_image"/>

欲知更多关于使用项目资源的信息,则阅读 资源及资产

使用资源XML来创建

到目前为止,妳应当已经熟悉了在安卓中设计 User Interface 的原则了。即,妳理解了用 XML 来定义对象的这种做法的强大力量及固有的灵活性。这种原则贯穿于Views 和Drawables 的设计中。如果,妳想要创建一个Drawable 对象,它最初不依赖于由妳的应用程序代码或用户的交互而产生的变量,那么,将该Drawable 定义到XML 中是一个好主意。甚至,即使妳的Drawable可能会在用户的使用过程中发生属性改变,妳也应当考虑将该对象定义到XML 中,因为,当它被初始化之后,妳仍然可以修改它的属性。

当妳在XML 中定义好了该Drawable 之后,就将那个文件保存到妳的项目的 res/drawable/ 目录中。然后,通过调用 Resources.getDrawable() 来获取并且实例化该对象,这个过程中要传入妳的XML 文件的资源编号(ID)。(参考下面 的示例 )

任何一个支持 inflate() 方法的Drawable 子类,都可以定义到XML 中,并且被妳的应用程序所实例化。每个支持XML 实例化(inflation)的Drawable,都会利用特定的用来辅助定义该对象的属性的XML 属性(阅读对应的类参考文档,以了解那都是些什么属性)。阅读每个Drawable 子类的类文档,以了解,如何在XML 中定义它们。

示例

以下XML 示例,定义了一个TransitionDrawable:

<transition xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/image_expand">

    <item android:drawable="@drawable/image_collapse">

</transition>

将这段XML保存 res/drawable/expand_collapse.xml ,然后,下面的代码会实例化该TransitionDrawable,并且将它设置为某个ImageView 的内容:

Resources res = mContext.getResources();

TransitionDrawable transition = (TransitionDrawable)

    res.getDrawable(R.drawable.expand_collapse);

ImageView image = (ImageView) findViewById(R.id.toggle_image);

image.setImageDrawable(transition);

然后,可使用以下代码来让该迁移运行(持续1)

transition.startTransition(1000);

阅读上面列出的那些Drawable 类的文档,以了解,它们都支持哪些XML 属性。

形状绘图对象Drawable

如果妳想要动态地绘制某些二维的图形,那么, ShapeDrawable 对象狠适合妳。利用ShapeDrawable,妳可以在代码中绘制基本图元形状,并且,以任何能够想像得到的方式来改变它们的样式。

ShapeDrawable继承 Drawable ,所以妳可以像使用Drawable 一样地使用它——例如,使用 setBackgroundDrawable() 来设置View 的背景。当然,妳也可以将各种形状绘制到它自己的 View 中去,以便日后将它加入到布局中。因为ShapeDrawable有它自己的 draw() 方法,所以,妳可以创建一个View 子类,让它在自己的 View.onDraw() 方法中绘制出该ShapeDrawable。以下代码展示了一个基本的View子类,它会将一个ShapeDrawable 作为一个View 来绘制:

public class CustomDrawableView extends View {

  private ShapeDrawable mDrawable;

  public CustomDrawableView(Context context) {

    super(context);

    int x = 10;

    int y = 10;

    int width = 300;

    int height = 50;

    mDrawable = new ShapeDrawable(new OvalShape());

    mDrawable.getPaint().setColor(0xff74AC23);

    mDrawable.setBounds(x, y, x + width, y + height);

  }

  protected void onDraw(Canvas canvas) {

    mDrawable.draw(canvas);

  }

}

在构造函数中,利用一个 OvalShape 来定义了一个ShapeDrawable。然后,为它设置了一个颜色, 并且设置了形状的边界范围。如果妳不设置边界的话,该形状不会被绘制出来,而如果妳不设置颜色的话,它会是默认的黑色。

定义了该自定义View之后,妳就可以以自己喜欢的任何方式来绘制它了。对于上面的示例代码,我们可以在一个活动(Activity)的代码中如此绘制该形状:

CustomDrawableView mCustomDrawableView;

protected void onCreate(Bundle savedInstanceState) {

  super.onCreate(savedInstanceState);

  mCustomDrawableView = new CustomDrawableView(this);

  setContentView(mCustomDrawableView);

}

如果妳想要在XML 布局中绘制这个自定义的绘图对象,而不是在活动(Activity)代码中做这件事的话,那么,这个CustomDrawable类必须覆盖 View(Context, AttributeSet) 这个构造函数,它会在通过XML 实例化一个View 的时候被调用。然后,向XML 中加入一个CustomDrawable 元素,就像这样:

<com.example.shapedrawable.CustomDrawableView

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        />

ShapeDrawable( android.graphics.drawable 包中的狠多其它Drawable 类型一样)允许妳利用公有方法来定义该绘图对象的多种属性。其中包括:透明度(alpha transparency);颜色过滤器;抖动(dither);不透明度(opacity);和颜色。

妳也可以使用XML 来定义基本的图元绘图形状。欲知更多信息,则,阅读 绘图对象资源 文档中关于Shape Drawables 的小节。

点9

NinePatchDrawable 图像,是一种可缩放的位图图片,安卓会自动对以它为背景的View 进行缩放,以适应妳放入其中内容。NinePatch 的一个典型例子是,标准安卓按钮中的背景——按钮被能够被拉伸以适应不同长度的字符串。NinePatch绘图对象,是在标准PNG图片中加上额外的1像素宽的边框。它必须以 .9.png 扩展名来保存,并且必须被放置于妳的项目的 res/drawable/ 目录中。

边框被用来定义该图片中可拉伸的区域及固定区域。这样来标记可拉伸区域:在边框的左侧和顶部绘制一条(或多条)1像素宽的黑色线(边线中其它的像素应当保持完全透明或白色)。妳可以定义出任意数量的可拉伸区域:它们的相对尺寸会保持不变,因此,最大的区域永远是最大的。

妳还可以在右侧和底部连线上画线来定义一个可选的绘图对象区域(或者说,填充线)。如果某个View对象将该NinePatch设置为它的背景,然后指定了该View的文字内容,则,它会拉伸自身,使得,所有的文字内容都被右侧和底部线(如果有的话)所标示的区域容纳进去。如果未包含填充线,则,安卓会使用左侧和顶部线来定义绘图对象区域。

进一步将不同线之间的区别讲清楚,左侧和顶部线的作用是,在拉伸图片的过程中,图片里的哪些像素是允许被复制的。底部和右侧线的作用是,定义中图片里的一块相对区域,这个区域就是该View 中允许放置内容的区域。

以下是一个用来定义按钮的示例NinePatch 文件:

这个NinePatch文件,使用左侧和顶部线定义了一个可拉伸区域,使用底部和右侧线定义了绘图对象区域。在上面一张图片中,灰色的虚线,标识出了图片中可被复制以便实现图片拉伸的区域。下面一张图片中的粉红色矩形,标识出了允许放置该View 的内容的区域。如果内容无法在该区域中放下,则,图片会被拉伸,直到能放下为止。

9绘制 工具,以一个所见即所得的图形界面,提供了一个超级好用的NinePatch图片创建工具。甚至,如果妳所定义的拉伸区域可能会因为像素复制而产生毛刺的话,它还会发出警告。

示例XML

下面是一段示例布局XML代码,展示的是,如何将一个NinePatch图片应用到多个按钮上去。(该NinePatch图片被保存为 res/drawable/my_button_background.9.png

<Button id="@+id/tiny"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_alignParentTop="true"

        android:layout_centerInParent="true"

        android:text="Tiny"

        android:textSize="8sp"

        android:background="@drawable/my_button_background"/>

<Button id="@+id/big"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_alignParentBottom="true"

        android:layout_centerInParent="true"

        android:text="Biiiiiiig text!"

        android:textSize="30sp"

        android:background="@drawable/my_button_background"/>

注意,宽度(width)和高度(height)都被设置为"wrap_content",使得按钮自动适应文字内容。

以下就是使用上面的XML 和NinePatch 图片渲染出的按钮的样子。注意看,因为文字内容的不同,两个按钮的宽度和高度都不同,而背景图片发生了拉伸,以适应文字内容。

龙脉

Your opinions
Your name:Email:Website url:Opinion content: