Android 图形和动画

Android 为支持 2D 图形和动画提供了非常丰富和多样化的框架。 本主题介绍这些框架,并讨论如何创建自定义图形和动画以在 Xamarin.Android 应用程序中使用。

概述

尽管在传统上功能有限的设备上运行,但评级最高的移动应用程序通常具有复杂的用户体验 (UX) ,并提供高质量的图形和动画,提供直观、响应迅速、动态的感觉。 随着移动应用程序变得越来越复杂,用户开始对应用程序的期望越来越高。

幸运的是,现代移动平台具有非常强大的框架,可用于创建复杂的动画和自定义图形,同时保持易用性。 这使开发人员可以毫不费力地添加丰富的交互性。

Android 中的 UI API 框架大致可分为两类:图形和动画。

图形进一步拆分为执行 2D 和 3D 图形的不同方法。 可通过许多内置框架(例如 OpenGL ES (移动特定版本的 OpenGL) )和第三方框架(如 MonoGame (与 XNA 工具包) 兼容的跨平台工具包)提供 3D 图形。 尽管 3D 图形不在本文的范围内,但我们将探讨内置的 2D 绘图技术。

Android 提供两个不同的 API,用于创建 2D 图形。 一种是高级声明性方法,另一种是编程的低级别 API:

  • 可绘制资源 – 这些资源用于以编程方式创建自定义图形,或通过在 XML 文件中嵌入绘图指令来 (更通常) 。 可绘制资源通常定义为包含 Android 呈现 2D 图形的说明或操作的 XML 文件。

  • Canvas – 这是一个低级别 API,涉及直接在基础位图上绘制。 它提供对所显示内容的非常精细的控制。

除了这些 2D 图形技术外,Android 还提供了几种不同的方法来创建动画:

  • 可绘制动画 – Android 还支持称为 可绘制动画的逐帧动画。 这是最简单的动画 API。 Android 按顺序加载和显示可绘制资源, (非常类似于卡通) 。

  • 视图动画 - 视图动画 是 Android 中的原始动画 API,可在所有版本的 Android 中使用。 此 API 受到限制,因为它只能与 View 对象一起使用,并且只能对这些视图执行简单的转换。 视图动画通常在 文件夹中的 /Resources/anim XML 文件中定义。

  • 属性动画 - Android 3.0 引入了一组称为 属性动画的新动画 API。 这些新 API 引入了一个可扩展且灵活的系统,该系统可用于对任何对象的属性进行动画处理,而不仅仅是 View 对象。 这种灵活性允许将动画封装在不同的类中,从而使代码共享更加容易。

视图动画更适用于必须支持较旧的 Android 3.0 API (API 级别 11) 的应用程序。 否则,出于上述原因,应用程序应使用较新的属性动画 API。

所有这些框架都是可行的选项,但在可能的情况下,应优先使用属性动画,因为它是一个更灵活的 API。 属性动画允许将动画逻辑封装在不同的类中,从而使代码共享更加容易并简化代码维护。

可访问性

图形和动画有助于使 Android 应用具有吸引力和使用乐趣;但是,请务必记住,某些交互是通过屏幕阅读器、备用输入设备或辅助缩放进行的。 此外,某些交互可能会在没有音频功能的情况下发生。

在这些情况下,如果应用在设计时考虑到了辅助功能:在用户界面中提供提示和导航帮助,并确保 UI 的图形元素有文本内容或说明,则应用更可用。

有关如何利用 Android 辅助功能 API 的详细信息,请参阅 Google 的辅助功能指南。

2D 图形

可绘制资源是 Android 应用程序中的一种常用技术。 与其他资源一样,可绘制资源是声明性的 , 它们是在 XML 文件中定义的。 此方法允许将代码与资源完全分离。 这可以简化开发和维护,因为无需更改代码来更新或更改 Android 应用程序中的图形。 但是,尽管可绘制资源适用于许多简单和常见的图形要求,但它们缺乏 Canvas API 的功能和控制力。

另一种技术(使用 Canvas 对象)与其他传统 API 框架(如 System.Drawing 或 iOS 的核心绘图)非常相似。 使用 Canvas 对象可以最大程度地控制 2D 图形的创建方式。 它适用于可绘制资源不起作用或难以使用的情况。 例如,可能需要绘制一个自定义滑块控件,该控件的外观将根据与滑块值相关的计算而更改。

让我们首先检查可绘制资源。 它们更简单,涵盖最常见的自定义绘图用例。

可绘制资源

可绘制资源在目录 /Resources/drawable的 XML 文件中定义。 与嵌入 PNG 或 JPEG 不同,无需提供特定于密度的可绘制资源版本。 在运行时,Android 应用程序将加载这些资源,并使用这些 XML 文件中包含的说明来创建 2D 图形。 Android 定义了几种不同类型的可绘制资源:

  • ShapeDrawable - 这是一个 Drawable 对象,用于绘制一个基元几何形状,并在该形状上应用一组有限的图形效果。 它们对于自定义按钮或设置 TextView 的背景等操作非常有用。 本文稍后将介绍如何使用 ShapeDrawable 的示例。

  • StateListDrawable - 这是一个可绘制资源,它将根据小组件/控件的状态更改外观。 例如,按钮可能会更改其外观,具体取决于是否按下。

  • LayerDrawable - 此可绘制资源,将一个其他几个可绘制资源堆叠在另一个可绘制资源之上。 以下屏幕截图显示了 LayerDrawable 的示例:

    LayerDrawable 示例

  • TransitionDrawable - 这是 LayerDrawable ,但有一个区别。 TransitionDrawable 能够对一个层在另一层上方显示动画。

  • LevelListDrawable - 这与 StateListDrawable 非常相似,因为它将基于特定条件显示图像。 但是,与 StateListDrawable 不同, LevelListDrawable 基于整数值显示图像。 LevelListDrawable 的一个示例是显示 WiFi 信号的强度。 随着 WiFi 信号强度的变化,显示的可绘制对象将相应地更改。

  • ScaleDrawable/ClipDrawable – 顾名思义,这些 Drawables 同时提供缩放和剪裁功能。 ScaleDrawable 将缩放另一个 Drawable,而 ClipDrawable 将剪裁另一个 Drawable。

  • InsetDrawable - 此 Drawable 将在另一个 Drawable 资源的两侧应用嵌入。 当视图需要小于视图的实际边界的背景时,将使用它。

  • XML BitmapDrawable - 此文件是 XML 中要对实际位图执行的一组指令。 Android 可以执行的一些操作包括平铺、抖动和抗锯齿。 此操作的常见用途之一是在布局的背景中平铺位图。

可绘制示例

让我们看一个有关如何使用 创建 2D 图形的 ShapeDrawable快速示例。 可以定义四个 ShapeDrawable 基本形状之一:矩形、椭圆、线条和环形。 还可以应用基本效果,例如渐变、颜色和大小。 以下 XML 是可在 ShapeDrawableAnimationsDemo 配套项目 (文件 Resources/drawable/shape_rounded_blue_rect.xml) 中找到的 。 它定义一个具有紫色渐变背景和圆角的矩形:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<!-- Specify a gradient for the background -->
<gradient android:angle="45"
          android:startColor="#55000066"
          android:centerColor="#00000000"
          android:endColor="#00000000"
          android:centerX="0.75" />

<padding android:left="5dp"
          android:right="5dp"
          android:top="5dp"
          android:bottom="5dp" />

<corners android:topLeftRadius="10dp"
          android:topRightRadius="10dp"
          android:bottomLeftRadius="10dp"
          android:bottomRightRadius="10dp" />
</shape>

我们可以在布局或其他 Drawable 中以声明方式引用此 Drawable 资源,如以下 XML 所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#33000000">
    <TextView android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerInParent="true"
              android:background="@drawable/shape_rounded_blue_rect"
              android:text="@string/message_shapedrawable" />
</RelativeLayout>

还可以以编程方式应用可绘制的资源。 以下代码片段演示如何以编程方式设置 TextView 的背景:

TextView tv = FindViewById<TextView>(Resource.Id.shapeDrawableTextView);
tv.SetBackgroundResource(Resource.Drawable.shape_rounded_blue_rect);

若要查看其外观,请运行 AnimationsDemo 项目,并从“main”菜单中选择“可绘制形状”项。 应会看到类似于以下屏幕截图的内容:

具有自定义背景的文本视图,可以使用渐变和圆角绘制

有关可绘制资源的 XML 元素和语法的更多详细信息,请参阅 Google 文档

使用画布绘图 API

可绘制性功能强大,但有其局限性。 某些事情要么是不可能的,要么 (非常复杂,例如:将筛选器应用于设备上相机拍摄的照片) 。 使用可绘制资源来应用红眼减少将是非常困难的。 相反,Canvas API 允许应用程序具有非常精细的控件,以便有选择地更改图片特定部分的颜色。

一个常用于 Canvas 的类是 Paint 类。 此类包含有关如何绘制的颜色和样式信息。 它用于提供颜色和透明度等内容。

Canvas API 使用 画家的模型 绘制 2D 图形。 操作在彼此之上的连续层中应用。 每个操作将涵盖基础位图的某些区域。 当区域与以前绘制的区域重叠时,新油漆将部分或完全遮盖旧区域。 这与许多其他绘图 API(如 System.Drawing 和 iOS 的核心图形)的工作方式相同。

有两种方法可以获取 Canvas 对象。 第一种方法涉及定义 Bitmap 对象,然后使用该对象实例化对象 Canvas 。 例如,以下代码片段创建具有基础位图的新画布:

Bitmap bitmap = Bitmap.CreateBitmap(100, 100, Bitmap.Config.Argb8888);
Canvas canvas = new Canvas(b);

获取 对象的另一种方法Canvas是通过提供 View 基类的 OnDraw 回调方法。 Android 在确定视图需要绘制自身并传入 Canvas 对象以供视图使用时,会调用此方法。

Canvas 类公开以编程方式提供绘图指令的方法。 例如:

  • Canvas.DrawPaint – 使用指定的绘图填充整个画布的位图。

  • Canvas.DrawPath – 使用指定的绘图绘制指定的几何形状。

  • Canvas.DrawText – 使用指定颜色绘制画布上的文本。 文本在位置 x,y 绘制。

使用 Canvas API 绘图

下面是运行中的 Canvas API 的示例。 以下代码片段演示如何绘制视图:

public class MyView : View
{
    protected override void OnDraw(Canvas canvas)
    {
        base.OnDraw(canvas);
        Paint green = new Paint {
            AntiAlias = true,
            Color = Color.Rgb(0x99, 0xcc, 0),
        };
        green.SetStyle(Paint.Style.FillAndStroke);

        Paint red = new Paint {
            AntiAlias = true,
            Color = Color.Rgb(0xff, 0x44, 0x44)
        };
        red.SetStyle(Paint.Style.FillAndStroke);

        float middle = canvas.Width * 0.25f;
        canvas.DrawPaint(red);
        canvas.DrawRect(0, 0, middle, canvas.Height, green);
    }
}

上面的代码首先创建一个红色油漆和一个绿色的画图对象。 它用红色填充画布的内容,然后指示画布绘制一个宽度为画布宽度的 25% 的绿色矩形。 本文源代码随附的 AnimationsDemo 项目中的 一个示例。 通过启动应用程序并从“main”菜单中选择“绘图”项,应会显示如下所示的屏幕:

带有红色油漆和绿色画图对象的屏幕

动画

用户喜欢在其应用程序中移动的内容。 动画是改善应用程序用户体验并帮助其脱颖而出的好方法。最好的动画是用户没有注意到的动画,因为他们感觉很自然。 Android 为动画提供以下三个 API:

  • 查看动画 - 这是原始 API。 这些动画绑定到特定的视图,并且可以对视图的内容执行简单的转换。 由于简单,此 API 仍可用于 alpha 动画、旋转等内容。

  • 属性动画 – Android 3.0 中引入了属性动画。 它们使应用程序能够对几乎所有内容进行动画处理。 属性动画可用于更改任何对象的任意属性,即使该对象在屏幕上不可见也是如此。

  • 可绘制动画 – 这是一种特殊的可绘制资源,用于将非常简单的动画效果应用于布局。

一般情况下,属性动画是首选系统,因为它更灵活,并提供更多功能。

查看动画

视图动画仅限于视图,并且只能对起点和终点、大小、旋转和透明度等值执行动画。 这些类型的动画通常称为 补间动画。 可以通过两种方式定义视图动画 - 以编程方式在代码中定义,或使用 XML 文件。 XML 文件是声明视图动画的首选方法,因为它们更具可读性且更易于维护。

动画 XML 文件将存储在 /Resources/anim Xamarin.Android 项目的 目录中。 此文件必须具有以下元素之一作为根元素:

  • alpha – 淡入或淡出动画。

  • rotate – 旋转动画。

  • scale – 调整动画大小。

  • translate – 水平和/或垂直运动。

  • set – 可以保存一个或多个其他动画元素的容器。

默认情况下,XML 文件中的所有动画将同时应用。 若要使动画按顺序发生,请在 android:startOffset 上面定义的元素之一上设置 属性。

使用 内插器可以影响动画中的更改率。 使用内插器可以加速、重复或减速动画效果。 Android 框架提供多个现成的内插器,例如 (但不限于) :

  • AccelerateInterpolator / DecelerateInterpolator – 这些内插器可增加或降低动画中的更改率。

  • BounceInterpolator – 更改在末尾反弹。

  • LinearInterpolator – 更改速率为常量。

以下 XML 演示组合了其中一些元素的动画文件示例:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android=http://schemas.android.com/apk/res/android
     android:shareInterpolator="false">

    <scale android:interpolator="@android:anim/accelerate_decelerate_interpolator"
           android:fromXScale="1.0"
           android:toXScale="1.4"
           android:fromYScale="1.0"
           android:toYScale="0.6"
           android:pivotX="50%"
           android:pivotY="50%"
           android:fillEnabled="true"
           android:fillAfter="false"
           android:duration="700" />

    <set android:interpolator="@android:anim/accelerate_interpolator">
        <scale android:fromXScale="1.4"
               android:toXScale="0.0"
               android:fromYScale="0.6"
               android:toYScale="0.0"
               android:pivotX="50%"
               android:pivotY="50%"
               android:fillEnabled="true"
               android:fillBefore="false"
               android:fillAfter="true"
               android:startOffset="700"
               android:duration="400" />

        <rotate android:fromDegrees="0"
                android:toDegrees="-45"
                android:toYScale="0.0"
                android:pivotX="50%"
                android:pivotY="50%"
                android:fillEnabled="true"
                android:fillBefore="false"
                android:fillAfter="true"
                android:startOffset="700"
                android:duration="400" />
    </set>
</set>

此动画将同时执行所有动画。 第一个缩放动画将水平拉伸图像并垂直收缩图像,然后图像将同时逆时针旋转 45 度并缩小,从屏幕上消失。

可以通过对动画进行膨胀,然后将其应用于视图,以编程方式将动画应用于视图。 Android 提供帮助程序类,该类 Android.Views.Animations.AnimationUtils 将扩充动画资源并返回 的 Android.Views.Animations.Animation实例。 此对象通过调用 StartAnimation 并传递 Animation 对象应用于 View。 以下代码片段演示了一个示例:

Animation myAnimation = AnimationUtils.LoadAnimation(Resource.Animation.MyAnimation);
ImageView myImage = FindViewById<ImageView>(Resource.Id.imageView1);
myImage.StartAnimation(myAnimation);

现在,我们已经对视图动画的工作原理有了基本的了解,让我们转到属性动画。

属性动画

属性动画器是 Android 3.0 中引入的新 API。 它们提供了一个更具可扩展性的 API,可用于对任何对象上的任何属性进行动画处理。

所有属性动画都由 Animator 子类的实例创建。 应用程序不直接使用此类,而是使用其中一个子类:

  • ValueAnimator – 此类是整个属性动画 API 中最重要的类。 它计算需要更改的属性的值。 ViewAnimator不会直接更新这些值;而是引发可用于更新动画对象的事件。

  • ObjectAnimator – 此类是 的 ValueAnimator 子类。 它旨在通过接受要更新的目标对象和属性来简化对象动画处理的过程。

  • AnimationSet – 此类负责协调动画彼此相对的运行方式。 动画可以同时运行、按顺序运行,也可以按指定的延迟运行。

计算器 是动画师用于在动画过程中计算新值的特殊类。 Android 现成提供以下计算器:

如果要进行动画处理的属性不是 floatint 或 颜色,则应用程序可以通过实现 ITypeEvaluator 接口来创建自己的计算器。 (实现自定义计算器超出了本主题的范围。)

使用 ValueAnimator

任何动画都有两个部分:计算动画值,然后在某个对象的属性上设置这些值。 ValueAnimator 将仅计算值,但不直接对对象进行操作。 相反,对象将在将在动画生命周期内调用的事件处理程序内更新。 此设计允许从一个动画值更新多个属性。

通过调用以下工厂方法之一来获取 的 ValueAnimator 实例:

  • ValueAnimator.OfInt
  • ValueAnimator.OfFloat
  • ValueAnimator.OfObject

完成此操作后, ValueAnimator 实例必须设置其持续时间,然后才能启动该实例。 以下示例演示如何在 1000 毫秒的跨度内对值从 0 到 1 进行动画处理:

ValueAnimator animator = ValueAnimator.OfInt(0, 100);
animator.SetDuration(1000);
animator.Start();

但上述代码片段本身并不十分有用 - 动画程序将运行,但更新的值没有目标。 当 Animator 类确定有必要通知侦听器新值时,它将引发 Update 事件。 应用程序可以提供事件处理程序来响应此事件,如以下代码片段所示:

MyCustomObject myObj = new MyCustomObject();
myObj.SomeIntegerValue = -1;

animator.Update += (object sender, ValueAnimator.AnimatorUpdateEventArgs e) =>
{
    int newValue = (int) e.Animation.AnimatedValue;
    // Apply this new value to the object being animated.
    myObj.SomeIntegerValue = newValue;
};

了解 后, ValueAnimator让我们详细了解 ObjectAnimator

使用 ObjectAnimator

ObjectAnimator 是 的 ViewAnimator 子类,它将 的计时引擎和值计算 ValueAnimator 与连接事件处理程序所需的逻辑相结合。 ValueAnimator要求应用程序显式连接事件处理程序 - ObjectAnimator 将为我们完成此步骤。

ObjectAnimator API 与 的 ViewAnimatorAPI 非常相似,但需要提供 对象和要更新的属性的名称。 以下示例演示使用 ObjectAnimator的示例:

MyCustomObject myObj = new MyCustomObject();
myObj.SomeIntegerValue = -1;

ObjectAnimator animator = ObjectAnimator.OfFloat(myObj, "SomeIntegerValue", 0, 100);
animator.SetDuration(1000);
animator.Start();

如前面的代码片段所示, ObjectAnimator 可以减少和简化对对象进行动画处理所需的代码。

可绘制动画

最终的动画 API 是可绘制动画 API。 可绘制动画逐个加载一系列可绘制资源,并按顺序显示它们,类似于翻转动画。

可绘制资源在 XML 文件中定义,该文件将 <animation-list> 元素作为根元素,并包含一系列用于定义动画中每个帧的 <item> 元素。 此 XML 文件存储在 /Resource/drawable 应用程序的 文件夹中。 以下 XML 是可绘制动画的示例:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@drawable/asteroid01" android:duration="100" />
  <item android:drawable="@drawable/asteroid02" android:duration="100" />
  <item android:drawable="@drawable/asteroid03" android:duration="100" />
  <item android:drawable="@drawable/asteroid04" android:duration="100" />
  <item android:drawable="@drawable/asteroid05" android:duration="100" />
  <item android:drawable="@drawable/asteroid06" android:duration="100" />
</animation-list>

此动画将运行六个帧。 属性 android:duration 声明每个帧的显示时长。 下一个代码片段演示了创建可绘制动画并在用户单击屏幕上的按钮时启动它的示例:

AnimationDrawable _asteroidDrawable;

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.Main);

    _asteroidDrawable = (Android.Graphics.Drawables.AnimationDrawable)
    Resources.GetDrawable(Resource.Drawable.spinning_asteroid);

    ImageView asteroidImage = FindViewById<ImageView>(Resource.Id.imageView2);
    asteroidImage.SetImageDrawable((Android.Graphics.Drawables.Drawable) _asteroidDrawable);

    Button asteroidButton = FindViewById<Button>(Resource.Id.spinAsteroid);
    asteroidButton.Click += (sender, e) =>
    {
        _asteroidDrawable.Start();
    };
}

此时,我们介绍了 Android 应用程序中可用的动画 API 的基础。

总结

本文介绍了许多新概念和 API,以帮助向 Android 应用程序添加一些图形。 首先,它讨论了各种 2D 图形 API,并演示了 Android 如何允许应用程序使用 Canvas 对象直接绘制到屏幕。 我们还了解了一些替代技术,这些技术允许使用 XML 文件以声明方式创建图形。 然后,我们讨论了用于在 Android 中创建动画的新旧 API。