Android API Guide for Animation and Graphics(五)—— 动画与图形(画布和可绘制对象)

本文详细介绍Android平台上的2D绘图技术,包括画布(Canvas)、可绘制对象(Drawables)及向量图(Vector Drawables)的使用方法。涵盖在View和SurfaceView上绘图的区别,以及如何使用ShapeDrawable和VectorDrawable等工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

画布和可绘制对象(Canvas and Drawables)

Android框架接口提供了一组允许你自定义图形渲染到画布上或者修改已存在View对象外观的2D绘图接口。所以绘制2D图时,通常采用以下两种方式之一:

  • a.将图形或者动画绘制到你布局中的一个视图对象上。这种方式,绘制的图形由系统的视图层次结构进程处理,你只需简单的将图形声明到视图对象中。

  • b.直接在画布上绘制你的图形。这种方式,你需要自己适当的调用类的onDraw方法(传递你绘图的画布对象),或者调用画布的draw…()方法(如drawPicture())。这样做你也可以控制任何动画。

当绘制不需要动态改变或没有交互性游戏部分的简单图形时,选择a是最好的方式。例如,当你想在其他静态应用程序中显示静态图形或预定义动画时,你应该将图形绘制到View上。阅读Drawables更多信息。

当应用程序需要频繁重绘自身时,b是更好的选择。例如像视频游戏就需要绘制到画布上去。当然,有多种方法可以做到这一点:

  • 在布局创建自定义视图控件的 UI线程中,调用invalidate()然后处理onDraw()回调。

  • 或者,在单独管理SurfaceView的线程中尽可能快的实现画布上的绘制(这样就不需要调用invalidate())。

在画布上绘制(Draw with a Canvas)

当应用需要实现实现特殊绘制和控制动画的图形时,就应该通过画布来实现。画布作为一个虚拟的界面作用于真正的图形绘制界面上,它持有你所有绘制方法的调用。通过画布,实际上是在一个放入window的位图中实现绘制的。

在绘制过程回调onDraw()方法事件中传给你一个Canvas对象,你只需要在它上面完成你的绘制工作就行了。当处理SurfaceView对象时,你也可以通过SurfaceHolder.lockCanvas()获取Canvas对象(所有这些使用场景都会在下文中讨论)。但是,如果你需要创建一个新的Canvas对象,你得定义在实际完成绘制工作的Bitmap对象上,这种方式,Bitmap对于画布是必要条件。你可以如下创建一个画布对象:

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

现在,画布将绘制到定义的位图上。通过画布在上bitmap上绘图后,可以使用画布其中一个方法,Canvas.drawBitmap(Bitmap,…)将Bitmap对象传给另Canvas对象上。建议通过传给你的Canvas对象,并调用View.onDraw()或者SurfaceHolder.lockCanvas()来绘制你最终的图形。(详情阅读下文)

画布有自己的一套绘制方法可以给你使用,如drawBitmap(…),drawRect(),drawText()等等。其它你可能使用的类也有draw()方法。比如,你可能想把一个Drawable对象绘制到画布上,Drawable对象就有自己的draw()方法,它将Canvas作为参数。

在View上绘制(On a View)

如果应用程序不需要大量的处理或高的帧速率要求(比如是棋牌游戏、贪吃蛇游戏或者慢动画的应用程序),那你应该考虑创建自定义的视图组件并使用画布在View.onDraw()方法中进行绘制。这么做的好处就是Android框架预先提供给你一个在绘制时需要使用到的Canvas对象,这样你就不需要自己创建了。

首先,继承View类(或View的子类)然后声明onDraw回调方法。Android框架调用onDraw方法去请求你的View绘制自身。这就是通过onDraw()回调方法传给你Canvas对象后实现所有绘制调用的地方。

Android框架只会在需要时调用onDraw()。每次应用程序准备绘制时,你得调用invalidate()来让你的View无效。这暗示你的视图将被重新绘制,然后Android框架再调用onDraw方法(尽管不能保证这个回调会被立刻执行)。

在View组件的onDraw()方法中使用传给你的Canvas对象,或者在其它类携带Canvas对象的draw()方法中,完成所有的绘制工作。通过Canvas作为参数传递给你,然后通过它来调用不同的画布绘制方法。一旦onDraw()的工作完成,Android框架就使用Canvas绘制一个给系统处理的bitmap位图对象。

Note:为了在子线程中请求invalidate方法,而不是在UI线程中,你得调用postInvalidate().

在SurfaceView上绘制(On a SurfaceView)

SurfaceView是View的特殊子类,在视图层次结构中提供了专门的界面绘图功能。SurfaceView的目的就是将绘制的界面提供给应用程序的辅助线程,以至于应用程序不需要等到系统视图层次结构准备绘制的阶段。取而代之的是,辅助线程持有SurfaceView的引用,所以可以先绘制到它的画布上。

首先,需要创建一个继承SurfaceView的子类,这个子类需要实现SurfaceHolder.Callback.子类Callback是告诉你一些关于Surface底层信息的接口,比如Surface的创建,改变或销毁。这些事件对于你准备开始绘制工作,不管是否为新Surface的属性做调整,还是停止绘制工作以及杀掉一些潜在的任务是非常重要的。在SurfaceView类中定义辅助线程实现所有在画布上的绘制工作也是一个不错的选择。

你应该通过SurfaceHolder处理Surface对象,而不是直接处理它。所以,当你的SurfaceView初始化之后,通过调用getHolder()获取SurfaceHolder对象。然后你应该通过调用addCallback()方法通知SurfaceHolder你想接收SurfaceHolder的回调(SurfaceHolder.Callback的回调)。然后在SurfaceView的子类中重写SurfaceHolder.Callback的回调函数。

为了在辅助线程的画布上进行绘制,你得传递SurfaceHandler线程并通过lockCanvas()检索Canvas对象。现在,你可以通过SurfaceHolder传递的Canvas对象将你所需图形绘制到画布上。一旦使用画布完成绘制,调用unlockCanvasAndPost()传递你的Canvas对象。Surface就会绘制出你在画布上所绘制的图形。每次重新绘制你都需要按这个顺序调用locking跟unlocking。

Note:每次传递从SurfaceHolder检索出的Canvas对象,前一个Canvas的状态都会被保留。为了确保图形的正确绘制,你得重新绘制整个界面。比如,你可以调用drawColor()填充颜色或者drawBitmap()设置图片背景来清理前一个Canvas保留下来的状态。否则,你将看到绘制出前一个画布的轨迹。

可绘制图(Drawables)

Android提供可自定义绘制形状,图片的2D图形绘制库。在android.graphics.drawable包中你可以找到常见的2D绘图类。

本文将讨论Drawable对象以及一些Drawable的子类的基本使用。获取更多通过Drawable实现帧动画的信息,参考Drawable Animation.

Drawable是一个可绘制图形的抽象。你会发现Drawable类扩展出一些各种特定类型的可绘制图形,包括 BitmapDrawable, ShapeDrawable, PictureDrawable, LayerDrawable等等。当然,你可以通过你独特的方式扩展你自定义的可绘制对象。

通过资源文件创建(Creating from resource images)

一种通过项目资源引用图片文件添加图形的简单方式。这种方式支持的类型有png(首选),jpg(可接受),gif(不推荐)。这种方式明显也是应用程序图标,logo,或者比如游戏等其他图形的首选。

要使用图片资源,只需要将图片文件添加到项目的res/drawable/目录中,然后你就可以在代码或者xml布局中引用了。无论哪种方式,使用资源id是首选,因为它不需要文件的后缀名。(eg:my_image.png通过my_image来引用)。

Note:放置在res / drawable /中的图片资源可以在构建过程期间通过aapt工具自动无损图像压缩优化。例如,不需要多于256种颜色的真彩色PNG可以被转换为具有色板的8位PNG。这可以让同等质量的图片却需要更少的内存。所以注意二进制图片的放置位置,因为它可能在构建期间改变。如果你计划将图像读取为位流,以便将其转换为位图,请将图像放在res / raw /文件夹中,而不进行优化。
示例代码:

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对象进行处理。方式如下:

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

通过xml资源创建(Creating from resource XML)

现在,你已经很熟悉Android的UI开发的规则了。因此你明白Android的强大以及在XML中定义对象的灵活性。这些思想从Views参透到Drawables.如果你想创建初始化不依赖于代码或用户交互的Drawable对象,在XML中定义Drawable是个不错的选择。即使你想应用程序在用户体验时改变Drawable属性,你也应该考虑将其定义在XML中,因为你仍然可以在初始化之后修改Drawable的属性。

XML定义好Drawable后,将文件存放于项目的res/drawable/目录下。然后调用Resources.getDrawable()并传入XML资源文件的ID来初始化、获取。

任何定义在XML并在应用程序中初始化的Drawable子类都支持inflate()方法。每个Drawable都支持在XML中利用指定的XMl属性进行填充。
示例:这是一个定义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的使用:

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

可绘制形状(Shape Drawable)

当你想动态绘制一些2D图形,ShapeDrawable对象可能适合你的需求。

ShapeDrawable是Drawable的扩展类,任何使用Drawable的地方也可以用它。如可以为View对象通过setBackgroundDrawable()设置背景。当然,你也可以绘制某个形状作为自定义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);
  }
}

在构造函数中,ShapeDrawable声明为OvalShape,然后设置它的颜色和边界。如果不设置边界,形状将不会被绘制,但是如果不设置颜色,它将默认设置为黑色。

自定义View定义好后,你可以用任何你喜欢的方式进行绘制。如上的代码,可以在Activity中进行绘制:

CustomDrawableView mCustomDrawableView;

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

  setContentView(mCustomDrawableView);
}

如果你想在XML而不是在Activity中绘制自定义的Drawable。你需要重写CustomDrawableView类的构造函数View(Context, AttributeSet),因为当填充XML布局的时候会调用这个构造函数来初始化。XML添加CustomDrawable如下所示:

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

Nine-patch

这里省略了.9图的相关介绍,获取更多信息参考(刚好前两天Google开发者大会开放了国内Android官方网站,不需要翻墙)。
https://developer.android.google.cn/guide/topics/graphics/2d-graphics.html

可绘制向量图(Vector Drawables)

VectorDrawable是一组点,线,曲线以及关联的颜色信息所定义在XML文件的向量图。从Android 5.0(API 21)开始,有两个类支持向量图作为Drawable资源:VectorDrawable和AnimatedVectorDrawable。先于Android5.0,23.2或更高版本的支持库更全的支持了向量图和动画向量图。

获取更多关于向量图的使用或支持库的信息,前往Vector Drawable.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值