动画和图形:画布和可绘制对象

Android框架API提供了一组2D绘图API,允许您将自己的自定义图形呈现到画布上或修改现有视图以自定义其外观和感觉。 绘制2D图形时,通常可以通过以下两种方法之一进行:

1、将您的图形或动画绘制到布局中的View对象中。 以这种方式,您的图形的绘制由系统的常规视图层次结构绘图过程处理 - 您只需简单的定义图形进入视图。


2、将您的图形直接绘制到画布。 这样,您亲自调用相应类的onDraw()方法(传递您的Canvas)或Canvas draw ...()方法之一(如drawPicture())。 在这样做的时候,你也可以控制任何动画。


选项“1”绘制为视图,是您想要绘制不需要动态更改而不是性能密集型游戏的一部分的简单图形时的最佳选择。 例如,当您要在静态应用程序中显示静态图形或预定义的动画时,应将绘图绘制到视图中。 阅读Drawables以获取更多信息。


当您的应用程序需要定期重新绘制时,选项“2”绘制到画布是更好的选择。 应用程序如视频游戏应该自己绘制到画布。 但是,有多种方式可以做到这一点:

1、在与您的UI Activity相同的线程中,您可以在布局中创建自定义View组件,调用invalidate(),然后处理onDraw()回调。


2、或者,在一个单独的线程中,您可以管理一个SurfaceView,并且像线程一样快速地执行绘图(您不需要请求invalidate())。




一、使用画布绘画

当您编写一个应用程序时,您需要执行专门的绘图和/或控制图形的动画,您应该通过绘制画布来实现。 画布可以作为一个伪装或界面,到您绘制图形的实际表面 - 它可以保存所有的“绘制”调用。 通过画布,您的绘图实际上是在底层位图上进行的,这个位图放在窗口中。


如果您正在绘制onDraw()回调方法,Canvas将为您提供,您只需要对其进行绘图调用即可。 在处理SurfaceView对象时,您也可以从SurfaceHolder.lockCanvas()获取Canvas。 (以下两个方面都将讨论这两种情况。)但是,如果需要创建一个新的Canvas,则必须定义实际执行绘图的位图。 画布总是需要位图。 您可以像这样设置一个新的画布:

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

现在您的画布将绘制到定义的位图上。 在使用画布绘制后,您可以使用Canvas.drawBitmap(位图,...)方法之一将其位图带到另一个画布。 建议您最终通过View.onDraw()或SurfaceHolder.lockCanvas()提供给您的画布来绘制最终的图形(请参阅以下部分)。


Canvas类有自己的一组可以使用的绘图方法,如drawBitmap(...),drawRect(...),drawText(...)等等。 您可能使用的其他类也有draw()方法。 例如,你可能会有一些Drawable对象,你想放在画布上。 Drawable具有自己的draw()方法,它将Canvas作为参数。


一)、在View上

如果您的应用程序不需要大量的处理速度或帧速率(也许是象棋游戏,蛇游戏或其他慢动画应用程序),那么您应该考虑使用Canvas创建一个自定义的View组件和绘图在View.onDraw()中。 最方便的是,Android框架将为您提供一个预定义的画布,您可以在其中放置绘图调用。


开始,扩展View类(或其后代)并定义onDraw()回调方法。 这个方法将被Android框架调用,要求你的View自己绘制。 这是您将执行所有调用来绘制通过onDraw()回调传递给您的画布。


Android框架只会在必要时调用onDraw()。 每当您的应用程序准备绘制时,您必须通过调用invalidate()来请求您的视图无效。 这表示您希望绘制视图,Android会调用您的onDraw()方法(尽管不能保证回调将是即时的)。


在View组件的onDraw()中,使用Canvas.draw ...()方法或Canvas作为参数的其他类draw()方法,为您的所有绘图使用提供给您的画布。 一旦您的onDraw()完成,Android框架将使用您的Canvas绘制系统处理的位图。


注意:为了从除主要活动线程之外的线程请求无效,您必须调用postInvalidate()。


有关扩展View类的信息,请参阅构建自定义组件。


有关示例应用程序,请参阅SDK样本文件夹中的Snake游戏:<your-sdk-directory> / samples / Snake /。


二)、在SurfaceView上

SurfaceView是View的一个特殊子类,它在View层次结构中提供了一个专用的绘图面。 目的是向应用程序的辅助线程提供这个绘图面,这样应用程序不需要等到系统的View层次结构准备绘制。 相反,引用SurfaceView的辅助线程可以按照自己的速度绘制到自己的Canvas。


首先,您需要创建一个扩展SurfaceView的新类。 该类也应该实现SurfaceHolder.Callback。 这个子类是一个接口,它将通知您有关底层Surface的信息,例如创建,更改或销毁它们的信息。 这些事件很重要,因此您可以知道何时可以开始绘制,无论您是否需要根据新的表面属性进行调整,以及何时停止绘制并可能杀死某些任务。 您的SurfaceView类也是定义您的Secondary Thread类的好地方,它将执行Canvas的所有绘图过程。


而不是直接处理Surface对象,您应该通过SurfaceHolder处理它。 所以当你的SurfaceView被初始化时,通过调用getHolder()获取SurfaceHolder。 然后,您应该通过调用addCallback()通过SurfaceHolder来接收SurfaceHolder回调(通过SurfaceHolder.Callback)(传递给它)。 然后覆盖您的SurfaceView类中的每个SurfaceHolder.Callback方法。


为了从第二个线程中绘制到Surface Canvas,您必须通过ThreadHolder的线程传递并使用lockCanvas()检索Canvas。 您现在可以使用SurfaceHolder给您的画布,并对其进行必要的绘制。 绘制画布后,调用unlockCanvasAndPost(),传递Canvas对象。 当您离开时,Surface将会绘制Canvas。 每次要重绘时,执行此序列的锁定和解锁画布。


注意:在每次通过时,从SurfaceHolder检索Canvas,Canvas的先前状态将被保留。 为了正确地动画您的图形,您必须重新绘制整个表面。 例如,您可以通过使用drawColor()填充颜色或使用drawBitmap()设置背景图像来清除画布的上一个状态。 否则,您将看到您以前执行的图纸的痕迹。



二、可绘制对象

本文档讨论了使用Drawable对象绘制图形以及如何使用Drawable类的几个子类的基础知识。 有关使用Drawable进行逐帧动画的信息,请参阅可绘制动画。


Drawable是“可绘制的东西”的一般抽象。 你会发现Drawable类可以定义各种可绘制的图形,包括BitmapDrawable,ShapeDrawable,PictureDrawable,LayerDrawable等等。 当然,您也可以扩展它们以定义您自己的以可以独特的方式运行的自定义Drawable对象。


有三种方法来定义和实例化一个Drawable:使用保存在项目资源中的图像; 使用定义Drawable属性的XML文件; 或使用普通类构造函数。 下面我们将讨论前两种技术(使用构造函数对于有经验的开发人员来说并不新鲜)。


一)、从资源图像创建

向应用程序添加图形的一种简单方法是通过引用项目资源中的图像文件。 支持的文件类型是PNG(首选),JPG(可接受)和GIF(不鼓励)。 这种技术显然是应用程序图标,徽标或其他图形(比如游戏中使用的图形)的首选。


要使用图像资源,只需将文件添加到项目的res / drawable /目录中即可。 从那里,您可以从代码或XML布局中引用它。 无论哪种方式,都使用资源ID来引用,该资源ID是没有文件类型扩展名的文件名(例如,my_image.png被引用为my_image)。


注意:在构建过程中,通过aapt工具的无损图像压缩可以自动优化放置在res / drawable /中的图像资源。 例如,不需要256色以上的真彩色PNG可以使用调色板转换为8位PNG。 这将导致质量相同的图像,但需要更少的内存。 因此,请注意,放置在此目录中的图像二进制文件可能在构建期间发生更改。 如果您打算将图像作为位流读取,以便将其转换为位图,请将您的图像放在res / raw /文件夹中,而不是进行优化。


1、示例代码

以下代码片段演示了如何构建使用可绘制资源的图像并将其添加到布局的ImageView。

LinearLayout mLinearLayout;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  // Create a LinearLayout in which to add the ImageView
  mLinearLayout = new LinearLayout(this);

  // Instantiate an ImageView and define its properties
  ImageView i = new ImageView(this);
  i.setImageResource(R.drawable.my_image);
  i.setAdjustViewBounds(true); // set the ImageView bounds to match the Drawable's dimensions
  i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT,
      LayoutParams.WRAP_CONTENT));

  // Add the ImageView to the layout and set the layout as the content view
  mLinearLayout.addView(i);
  setContentView(mLinearLayout);
}

在其他情况下,您可能希望将图像资源作为Drawable对象处理。 为此,从资源创建一个Drawable,如下所示:

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

注意:您的项目中的每个唯一资源只能维护一个状态,无论您可以为其实例化多少个不同的对象。 例如,如果您从同一个图像资源中实例化两个Drawable对象,那么更改其中一个Drawable的属性(例如alpha),那么它也会影响另一个。 所以当处理图像资源的多个实例时,而不是直接转换Drawable,您应该执行补间动画。


2、示例xml

下面的XML代码片段显示了如何在XML布局中添加一个可以绘制到ImageView的资源(使用一些红色色调才是有趣的)。

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tint="#55ff0000"
        android:src="@drawable/my_image"/>

有关使用项目资源的更多信息,请阅读有关资源和资产。


二)、从资源XML创建

现在,您应该熟悉Android开发用户界面的原则。 因此,您了解在XML中定义对象时固有的功能和灵活性。 这个哲学从“意见”到“可绘制”都是讽刺的。 如果您想要创建一个Drawable对象,最初不依赖于应用程序代码或用户交互定义的变量,那么在XML中定义Drawable是一个很好的选择。 即使您希望您的Drawable在您的应用程序的用户体验期间更改其属性,您应该考虑以XML定义对象,因为您始终可以在实例化之后修改属性。


一旦你用XML定义了Drawable,将文件保存在项目的res / drawable /目录中。 然后,通过调用Resources.getDrawable()来检索和实例化对象,传递它的XML文件的资源ID。 (见下面的例子)


任何支持inflate()方法的Drawable子类可以在XML中定义,并由应用程序实例化。 支持XML通过的每个Drawable都可以使用特定的XML属性来帮助定义对象属性(请参阅类参考以查看这些属性)。 有关如何在XML中定义它的信息,请参阅每个Drawable子类的类文档。


1、示例

以下是定义TransitionDrawable的一些XML:

<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);

有关每个支持的XML属性的更多信息,请参阅上面列出的Drawable类。



三、可绘制形状

当您想要动态绘制一些二维图形时,ShapeDrawable对象可能会适合您的需要。 使用ShapeDrawable,您可以以编程方式绘制原始形状并以任何可想象的方式对其进行风格化。


一个ShapeDrawable是Drawable的扩展,所以你可以使用一个Drawable在预期的任何地方 - 也许是一个View的背景,用setBackgroundDrawable()设置。 当然,您也可以将自己的形状绘制为自己的自定义视图,然后再添加到您的布局中。 因为ShapeDrawable有它自己的draw()方法,你可以创建一个View的子类,在View.onDraw()方法中绘制ShapeDrawable。 这是View类的一个基本扩展,只是为了绘制一个ShapeDrawable作为视图:

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);
  }
}

在构造函数中,将ShapeDrawable定义为OvalShape。 然后给出一种颜色,并设置形状的界限。 如果没有设置边界,则不会绘制形状,而如果不设置颜色,则默认为黑色。


使用自定义视图定义,它可以任何你喜欢的方式绘制。 使用上面的示例,我们可以在Activity中以编程方式绘制形状:

CustomDrawableView mCustomDrawableView;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  mCustomDrawableView = new CustomDrawableView(this);

  setContentView(mCustomDrawableView);
}

如果要从XML布局中绘制此自定义drawable,而不是从Activity中绘制,那么CustomDrawable类必须覆盖View(Context,AttributeSet)构造函数,该函数通过XML通过实例化View来调用。 然后添加一个CustomDrawable元素到XML,像这样:

<com.example.shapedrawable.CustomDrawableView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />

ShapeDrawable类(像android.graphics.drawable包中的许多其他Drawable类型)允许您使用公共方法定义drawable的各种属性。 您可能需要调整的某些属性包括Alpha透明度,颜色过滤器,抖动,不透明度和颜色。


您还可以使用XML定义原始可绘制形状。 有关详细信息,请参阅“可绘制资源”文档中有关“形状绘图”的部分。



九、九宫格

NinePatchDrawable图形是一个可伸缩的位图图像,Android将自动调整大小以适应您将其作为背景放置的View的内容。 NinePatch的一个示例使用是标准Android按钮使用的背景 - 按钮必须伸展以适应各种长度的字符串。 NinePatch drawable是一个标准的PNG图像,包含一个额外的1像素宽的边框。 必须使用扩展名.9.png保存,并保存到项目的res / drawable /目录中。


边框用于定义图像的可伸缩和静态区域。 您可以通过在边框的左侧和顶部绘制一个(或多个)1像素宽的黑色线条(其他边框像素应完全透明或白色)来指示可伸缩部分。 您可以拥有所需的可拉伸部分:它们的相对尺寸保持不变,所以最大的部分始终保持最大。


您还可以通过绘制右侧和底部的一行来定义图像的可选绘制部分(有效地,填充线)。 如果一个View对象将NinePatch设置为其背景,然后指定View的文本,则它将自动伸展,以便所有文本仅适用于右侧和底部指定的区域(如果包含)。 如果不包括填充行,Android将使用左侧和上侧的行来定义此可绘制区域。


为了澄清不同行之间的区别,左和右行定义允许复制图像的哪些像素以拉伸图像。 底部和右侧的行定义图像内的相对区域,视图的内容被允许位于其内。


以下是用于定义按钮的NinePatch文件示例:



这个NinePatch定义了一个可拉伸区域,左边和顶部线条以及带有底部和右边线条的可绘制区域。 在顶部图像中,虚线的灰色线标识将被复制的图像的区域,以便拉伸图像。 底部图像中的粉色矩形标识允许视图内容的区域。 如果内容不适合该区域,则图像将被拉伸,以便它们可以进行。


Draw 9补丁工具使用WYSIWYG图形编辑器提供了一种非常方便的创建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"/>

请注意,宽度和高度设置为“wrap_content”以使按钮整齐地贴合在文本上。


以下是从上面显示的XML和NinePatch图片呈现的两个按钮。 请注意按钮的宽度和高度如何随着文字而变化,背景图像会延伸以适应它。





十、矢量绘图

VectorDrawable是一个在XML文件中定义的向量图形,它是一组点,线和曲线以及相关联的颜色信息。 从Android 5.0(API级别21)开始,有两个类支持矢量图形作为可绘制资源:VectorDrawable和AnimatedVectorDrawable。 在Android 5.0(API级别21)之前,支持库23.2或更高版本可以完全支持Vector Drawables和Animated Vector Drawable。


有关使用向量可绘制框架API或向量可绘制支持库的更多信息,请转到Vector Drawable。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值