官方Canvas and Drawables翻译以及批注

Android框架提供了一套二维绘图API,可让你将自己的自定义图形渲染到画布上或修改现有视图以自定义其外观和感觉。 你通常以以下方式之一绘制二维图形:

  • a:在布局中的View对象上绘制你的图形或动画。 使用此选项,会调用系统的渲染管道处理的图形 (需要在视图中定义自行定义)
  • b:在Canvas对象中绘制图形。使用此选项,需要你将画布传递到适当的类’onDraw(Canvas)’方法。你也可以使用Canvas中的绘图方法。此选项还可以控制任何动画。
    绘制一个视图是一个不错的选择,当你想绘制简单的图形,不需要动态变化,并且不是性能密集型应用程序,如游戏的一部分。例如,当你要在其他静态应用程序中显示静态图形或预定义的动画时,你应该将图形绘制到视图中。
    当你的应用程序需要定期重绘时,绘制到画布会更好。 应用程序(如视频游戏)应该自己绘制到画布上。 但是,有多种方式可以做到这一点:
  • a:在应用程序的主线程中,你可以在布局中创建自定义视图组件,调用invalidate(),然后处理onDraw(Canvas)回调。
  • b:在管理SurfaceView的工作线程中,使用Canvas的绘图方法。 你不需要调用invalidate()。

Draw with a canvas

你可以通过绘制由Canvas类表示的画布来满足需要专门绘制和/或控制图形动画的应用程序的要求。通过画布,你的应用程序将绘制到位于窗口中的底层Bitmap对象。

  • 1:如果你使用onDraw(Canvas)回调进行绘制操作,画布已经由系统提供,你只需要在其上绘制图形。
  • 2:如果你使用的是SurfaceView对象,则可以从lockCanvas()获取画布。
    (编者按:lockCanvas的源码如下:)
/**
     * Gets a {@link Canvas} for drawing into this surface.
     *
     * After drawing into the provided {@link Canvas}, the caller must
     * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
     *
     * @param inOutDirty A rectangle that represents the dirty region that the caller wants
     * to redraw.  This function may choose to expand the dirty rectangle if for example
     * the surface has been resized or if the previous contents of the surface were
     * not available.  The caller must redraw the entire dirty region as represented
     * by the contents of the inOutDirty rectangle upon return from this function.
     * The caller may also pass <code>null</code> instead, in the case where the
     * entire surface should be redrawn.
     * @return A canvas for drawing into the surface.
     *
     * @throws IllegalArgumentException If the inOutDirty rectangle is not valid.
     * @throws OutOfResourcesException If the canvas cannot be locked.
     */
    public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject != 0) {
                // Ideally, nativeLockCanvas() would throw in this situation and prevent the
                // double-lock, but that won't happen if mNativeObject was updated.  We can't
                // abandon the old mLockedObject because it might still be in use, so instead
                // we just refuse to re-lock the Surface.
                throw new IllegalArgumentException("Surface was already locked");
            }
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }

以下内容将讨论这两种情况:
如果需要创建一个新的Canvas对象,则必须定义底层Bitmap对象。
以下代码示例显示了如何从bitmap设置新的canvas :

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

可以通过使用drawBitmap()方法之一在不同的canvas 中使用bitmap 。但是,我们建议您使用由onDraw(Canvas)回调或lockCanvas()方法提供的canvas 。 有关更多信息,请参阅Drawing on a ViewDrawing on a SurfaceView
Canvas类有自己的绘图方法,包括drawBitmap(),drawRect(),drawText()等等。你可能使用的其他类也有draw()方法。例如,您可能有一些Drawable对象要放在画布上。Drawable类有自己的 draw(Canvas)方法,它将你的canvas 作为参数。

Drawing on a view

如果你的应用程序不需要大量处理或较高的帧速率(例如象棋游戏,贪吃蛇游戏或其他慢动画应用程序),那么你应该考虑在View.onDraw(Canvas)回调中使用画布创建自定义视图和绘图。 最方便的方面是,Android框架提供了一个可以执行绘图操作的预定义画布。
要开始的话,创建一个View的子类并实现onDraw(Canvas)回调,Android框架调用它来绘制视图。 然后通过框架提供的Canvas对象执行绘图操作。
Android框架仅在必要时调用onDraw(Canvas)。 当你重新绘制应用程序时,必须通过调用invalidate()来使视图无效。 调用invalidate()表示你希望绘制视图。 然后,Android会调用您的视图的onDraw(Canvas)方法,尽管不能保证是即时的。(编者按:会在未来的某个时间调用,但是不一定马上就可以调用到)
在视图的onDraw(Canvas)方法中,调用画布上的绘图方法或者可以将画布作为参数的其他类的方法。 一旦你的onDraw()完成,Android框架将使用你的canvas绘制由系统处理的bitmap。
注意:要从除应用程序主线程以外的线程中使视图无效,您必须调用postInvalidate()而不是invalidate()。

Drawing on a SurfaceView

SurfaceView是View的一个特殊子类,它在视图层次结构中提供了一个专用的绘图面。目标是向应用程序的工作线程提供此绘图面。这样,应用程序不需要等到系统的视图层次结构准备绘制(なるほど) 。具有对SurfaceView 对象的引用的工作线程可以以其自己的节奏绘制到其它的画布上。
首先,你需要创建一个扩展SurfaceView的新类。 该类还应该实现SurfaceHolder.Callback接口,该接口提供在底层Surface对象中发生的事件,例如创建,更改或销毁。这些事件让你知道何时可以开始绘制,无论你是否需要根据新的Surface属性进行调整,以及何时停止绘图并可能终止某些任务。扩展SurfaceView的类也是定义工作线程的好地方,它调用画布中的所有绘图过程。
你应该通过SurfaceHolder处理the Surface对象而不是直接处理它。在你的SurfaceView对象初始化之后,你可以通过调用getHolder()获取一个SurfaceHolder对象。 你应该注册您的SurfaceView对象,通过调用addCallback()来接收来自SurfaceHolder的通知。 然后在你的SurfaceView类中实现每个SurfaceHolder.Callback抽象方法。
你可以从具有访问SurfaceHolder对象的工作线程绘制到the surface canvas 。 每次应用程序需要重新绘制表面时,请从工作线程中执行以下步骤:

  • 1:使用lockCanvas()来取回canvas 。
  • 2:在画布上执行绘图操作。
  • 3:通过调用 unlockCanvasAndPost(Canvas) 来解锁画布。
    The surface绘制画布考虑到你在上面执行的所有操作
    注意:每次从SurfaceHolder检索画布时,画布的先前状态都将被保留。为了正确地动画你的图形,你必须重绘整个表面。例如,你可以使用drawColor()方法填充颜色或使用drawBitmap()方法设置背景图像来清除画布的上一个状态。 否则,你的画布可以显示以前的图纸的痕迹。

Drawables

Android框架提供了一个用于绘制形状和图像的自定义2D图形库。 android.graphics.drawable包中包含用于绘制二维的常用类。
本节讨论使用可绘制对象绘制图形的基本知识,以及如何使用Drawable类的几个子类。 有关如何使用drawable进行逐帧动画的信息,请参阅可 Drawable Animation
Drawable是可以绘制的东西的一般抽象。 Android框架提供了一组Drawable的直接和间接子类,你可以在各种场景中使用。 你还可以扩展这些类以定义你自己的以可以独特的方式运行的自定义可绘制对象。
除了使用标准类构造函数之外,还有两种方法来定义和实例化一个Drawable:

  • 1:使用项目中保存的资源图像。
  • 2:使用可绘制属性的XML资源。

Creating drawables from resource images

你可以通过引用项目资源中的图像文件来将图形添加到应用程序。 支持的文件类型是PNG(首选),JPG(可接受)和GIF(不鼓励)。 应用程序图标,icon和其他图形,如游戏中使用的图形,非常适合这种技术。
以下代码片段演示了如何构建使用从可绘制资源创建的图像的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);

  // set the ImageView bounds to match the Drawable's dimensions
  i.setAdjustViewBounds(true);
  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);

注意:你的项目中的每个唯一资源只能维护一个状态,无论你为其实例化了多少个不同的对象。 例如,如果你从同一个图像资源中实例化两个Drawable对象,并更改一个对象的属性(例如alpha),那么它也会影响另一个对象。 处理图像资源的多个实例时,而不是直接转换Drawable对象,你应该执行补间动画。
下面的XML代码片段显示了如何在XML布局中将可绘制的资源添加到ImageView中:

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/my_image" />

Creating drawables from XML resources

如果你想要创建一个Drawable对象,最初不依赖于你的代码或用户交互定义的变量,那么在XML中定义Drawable是一个很好的选择。 即使你希望Drawable在用户与应用程序交互期间更改其属性,你应该考虑以XML定义对象,因为你可以在实例化之后修改属性。(编者按:你也可以实现如此实现动画)

<!-- res/drawable/expand_collapse.xml -->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image_expand">
    <item android:drawable="@drawable/image_collapse">
</transition>

然后,通过调用Resources.getDrawable()来检索和实例化对象,并传递XML文件的资源ID。 任何支持inflate()方法的Drawable子类可以在XML中定义,并由你的应用程序实例化。 支持XML扩展的每个可绘制类都使用特定的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);

// Then you can call the TransitionDrawable object's methods
transition.startTransition(1000);

Shape drawables

当你想要动态绘制二维图形时,ShapeDrawable对象可以是一个很好的选择。 你可以以编程方式在ShapeDrawable对象上绘制原始形状,并应用您的应用程序需要的样式。ShapeDrawable是Drawable的一个子类。 因此,你可以使用一个ShapeDrawable,无论何时可以使用Drawable。 例如,你可以使用ShapeDrawable对象通过将视图传递给视图的setBackgroundDrawable()方法来设置视图的背景。 您还可以将自己的形状绘制为自己的自定义视图,并将其添加到应用程序中的布局。
因为ShapeDrawable具有自己的draw()方法,所以可以创建一个在onDraw()事件期间绘制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());
    // If the color isn't set, the shape uses black as the default.
    mDrawable.getPaint().setColor(0xff74AC23);
    // If the bounds aren't set, the shape can't be drawn.
    mDrawable.setBounds(x, y, x + width, y + height);
  }

  protected void onDraw(Canvas canvas) {
    mDrawable.draw(canvas);
  }
}

你可以在上述代码示例中使用CustomDrawableView 类,就像使用任何其他自定义视图一样。 例如,你可以以编程方式将其添加到应用程序中的活动中,如以下示例所示:

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

ShapeDrawable类与android.graphics.drawable包中的许多其他可绘制类型一样,允许你使用公共方法定义对象的各种属性。 您可能要调整的一些示例属性包括Alpha透明度,颜色过滤器,抖动,不透明度和颜色。

NinePatch drawables

NinePatchDrawable图形是一个可伸缩的位图图像,可以用作视图的背景。 Android会自动调整图形大小以适应视图的内容。 NinePatch图像的示例使用是标准Android按钮使用的背景 – 按钮必须伸展以适应各种长度的字符串。 NinePatch图形是包含一个额外的1像素边框的标准PNG图像。 必须使用9.png扩展名保存在项目的res / drawable /目录中。
使用边框定义图像的可拉伸和静态区域。 你可以通过在边框的左侧和顶部绘制一个(或多个)1像素宽的黑线(其他边框像素应完全透明或白色)来指示可伸缩部分。 你可以拥有尽可能多的伸缩部分。 可伸缩部分的相对尺寸保持不变,所以最大部分总是保持最大。
你还可以通过在右侧绘制一条线和底部的一条线来定义图像的可选绘制部分。 如果View对象将NinePatch图形设置为其背景,然后指定视图的文本,则它将自动展开,以使所有文本仅占据由右侧和底部指定的区域(如果包含)。 如果不包括填充行,Android将使用左侧和上侧的行来定义该可绘制区域。
为了澄清线之间的差异,左和右线定义允许复制图像的哪些像素以拉伸图像。 底部和右侧的行定义图像内允许占据的内容的相对区域。
图1显示了用于定义按钮的NinePatch图形的示例:

《官方Canvas and Drawables翻译以及批注》 ninepatch_raw.png

虚线灰色线标识复制图像的区域以拉伸图像。 底部图像中的粉色矩形标识允许视图内容的区域。 如果内容不适合该区域,则图像被拉伸以使其适合。

Draw 9补丁工具使用WYSIWYG图形编辑器提供了一种非常方便的创建NinePatch图像的方法。 如果您为可伸缩区域定义的区域有可能由于像素复制而产生绘图工件,那么它甚至会引发警告。

以下示例布局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"/>

请注意,layout_width和layout_height属性设置为wrap_content以使按钮整齐地贴合在文本上。
图2显示了从上面显示的XML和NinePatch图像呈现的两个按钮。 请注意按钮的宽度和高度如何随着文字而变化,背景图片会延伸以适应它。

《官方Canvas and Drawables翻译以及批注》 ninepatch_examples.png

Vector drawables

矢量drawable是一个在XML文件中定义的向量图形,它是一组点,线和曲线以及相关联的颜色信息。 Android框架提供了VectorDrawable和AnimatedVectorDrawable类,它们支持矢量图形作为可绘制的资源。
针对Android版本低于5.0(API级别21)的应用程序可以使用支持库版本23.2或更高版本来获取矢量绘图和动画矢量绘图的支持。 有关使用向量可绘制类的更多信息,请参阅Vector Drawable

《官方Canvas and Drawables翻译以及批注》 low_stiffness.gif

    原文作者:独倚栏杆
    原文地址: https://www.jianshu.com/p/00e483591de5
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞