Android 动画/图形入门指南






4.99/5 (40投票s)
学习在 Android 中绘制和制作简单的 2D 和 3D 图形动画。
![]() |
引言
要使您的应用在竞争对手中脱颖而出,它必须拥有视觉吸引力且令人惊叹的动画。在这方面,Android 框架提供了一套丰富且强大的 API,用于将动画应用于 UI 元素和图形,以及绘制自定义的 2D 和 3D 图形。在本文中,您将通过一系列实践练习,学习如何利用这些 API 来实现简单的动画和创建简单的 2D 和 3D 图形。
动画概述
Android 提供了三种动画系统来满足 Android 应用的动画需求。从最复杂的“属性动画”到更简单的“视图动画”和“可绘制动画”。
- 属性动画
-
属性动画系统于 Android 3.0 (API 级别 11) 推出,是一个强大的框架,可让您为任何对象(视图对象或非视图对象)的任何属性以及任何自定义类型制作动画。它是 Android 中首选的动画方法。“android.animation”提供了处理属性动画的类。
- 视图动画
-
作为较旧的动画系统,“视图动画”也称为“补间动画”。它只能用于为视图的内容制作动画,并且仅限于简单的变换,如移动、缩放和旋转,但不能动画其背景颜色。“android.view.animation”提供了处理“视图动画”的类。
- 可绘制动画
-
可绘制动画通过逐帧显示一系列“Drawable”资源(即图像)在视图对象内播放来工作。它使用“AnimationDrawable”类来实现。
2D 和 3D 图形概述
根据图形类型和应用的性能需求,您可以从以下选项中选择在 Android 上绘制图形:
- 画布
-
Android 框架提供了一套 2D 绘图 API,用于使用“Canvas”类提供的各种绘图方法在画布上渲染自定义图形。
- OpenGL ES
-
如果您涉足 3D 图形或交互式游戏应用,则应考虑 Android 框架支持的“OpenGL ES”API。它提供了一套极其强大的工具,用于处理和显示高端动画 3D 图形,这些图形可以利用许多 Android 设备上提供的图形处理单元 (GPU) 的硬件加速。
准备工作
我已经准备了一个 Android 项目供下载 - “AndroidAnimation.zip”。“AndroidAnimation”是一个不完整的应用程序,包含一个主 Activity、六个骨架 Activity 以及后续练习所需的其他资源。启动页是标题为“AndroidAnimation”的“MainActivity”,其中包含六个按钮,用于导航到您将在本教程的系列练习中完成的其他六个 Activity。有关应用完全构建后的视觉组成,请参阅图 1。
![]() |
图 1:Android 动画应用的视觉组成
|
您将通过以下步骤进行准备:
-
下载并解压“AndroidAnimation.zip”,您的计算机上应该会显示“AndroidAnimation”文件夹。
-
启动 Android Studio。
-
如果它打开了一个现有项目,请点击文件 > 关闭项目切换到欢迎屏幕。
-
在欢迎屏幕的“快速入门”页面上,点击“导入项目…”。
-
浏览到“AndroidAnimation”项目,然后点击“确定”在 Android Studio 中打开它。
旅程开始…
属性动画
ValueAnimator
属性动画通过在指定的时间长度内,根据某个随时间变化的函数,将对象的一个属性(例如屏幕上的 x 位置)的值从起始值更改为结束值来制作动画。
在属性动画中,Android 提供了“ValueAnimator”类作为启动和管理整个动画过程的计时引擎。它包含每个动画的详细信息,例如正在动画的属性的起始值和结束值、动画的持续时间、一个定义动画变化速率的“TimeInterpolator”子类,以及一个定义如何计算动画值的“TypeEvaluator”子类。
在动画期间,“ValueAnimator”将已用时间量与动画总持续时间进行比较,得出 0 到 1 之间的“已用进度”,其中 0 表示动画完成 0%,1 表示动画完成 100%。例如,在总持续时间为 100ms 的动画中,25ms 时的已用进度将为 0.25。
TimeInterpolator
对于计算出的每个已用进度,“ValueAnimator”将根据为动画设置的“TimeInterpolator”来计算“插值进度”。时间插值器是一个定义动画过程中文档值变化速率的函数。例如,您可以使用“LinearInterpolator”(图 2)来指定动画随时间均匀发生,或者使用“AccelerateDecelerateInterpolator”(图 3)来指定动画开始和结束时缓慢,但在中间加速。从下面的图表中可以看出,两个插值器在 t=0.25 的已用进度时,插值进度(即 f(t)'s)分别约为 0.25 和 0.15。
![]() |
![]() |
图 2:LinearInterpolator |
图 3:AccelerateDecelerateInterpolator
|
有关 Android 提供的“TimeInterpolator”各种子类的摘要,请参阅表 1。
回调 | 描述 | 图 |
---|---|---|
LinearInterpolator |
一种基于时间的函数,其变化速率恒定(图 2)。 |
![]() |
AccelerateDecelerateInterpolator |
一种基于时间的函数,其变化速率在开始和结束时缓慢,但在中间加速(图 3)。 |
![]() |
AccelerateInterpolator |
一种基于时间的函数,其变化速率开始缓慢然后加速。 |
![]() |
DecelerateInterpolator | 一种基于时间的函数,其变化速率开始快速然后减速。这与“AccelerateInterpolator”相反。 | ![]() |
AnticipateInterpolator | 一种基于时间的函数,其变化开始向后然后向前移动。 | ![]() |
OvershootInterpolator | 一种基于时间的函数,其变化会超过结束值,然后最终回到结束值。 | ![]() |
AnticipateOvershootInterpolator | 变化的前半部分采用“AnticipateInterpolator”的特征,后半部分采用“OvershootInterpolator”的特征。动画开始向后,然后向前移动并超过结束值,最后回到结束值。 | ![]() |
BounceInterpolator | 一系列连续的基于时间的函数,在动画结束时产生弹跳效果。 | ![]() |
CycleInterpolator | 将动画重复指定的周期数。 | ![]() |
TypeEvaluator
对于计算出的每个插值进度,“ValueAnimator”会调用适当的“TypeEvaluator”来计算正在动画的属性值,该计算基于插值进度、起始值和动画的结束值。Android 提供了三个“TypeEvaluator”子类和一个用于创建自定义评估器的接口。有关“TypeEvaluator”各种实现的摘要,请参阅表 2。
类/接口 | 说明 |
---|---|
IntEvaluator |
执行 int 值之间的类型插值。 |
FloatEvaluator |
执行 float 值之间的类型插值。 |
ArgbEvaluator |
执行代表 ARGB 颜色的整数值之间的类型插值。 |
TypeEvaluator |
一个接口,可与“setEvaluator(TypeEvaluator)”方法一起使用,该方法允许实现任意属性类型动画的自定义评估器。 |
到目前为止,“ValueAnimator”在计算动画的动画值方面非常有用;但它并没有将这些值应用到正在动画的对象的属性上。您需要编写代码来监听“ValueAnimator”计算出的值的更新,并相应地修改动画对象。我真的需要这样做吗?不,您不需要。我们将调用“ValueAnimator”的子类,即“ObjectAnimator”。
ObjectAnimator
“ObjectAnimator”是“ValueAnimator”的子类,允许您设置要动画的对象及其属性。它可以计算动画值并相应地用新值更新属性。在创建对象动画时,您将大部分时间使用“ObjectAnimator”。
AnimatorSet
很多时候,您想同时、顺序或在指定延迟后播放多个动画。Android 提供了“AnimatorSet”类来处理此类需求。
代码还是 XML?
除了以编程方式创建对象动画之外,您还可以将属性动画声明为 XML 资源,以便在多个 Activity 中重用。在 XML 中编辑动画详细信息也比在代码中更容易。下面显示了代表相应属性动画类的 XML 标签:
-
“ValueAnimator”使用“<animator>”。
-
“ObjectAnimator”使用“<objectAnimator>”。
- “AnimatorSet”使用“<set>”。
好了,理论够多了,让我们付诸实践。
让事情发生
您将在“AndroidAnimation”项目中创建一个 Activity,该 Activity 将使用“属性动画”系统展示动画的组合。请参阅图 4 了解预期的结果。
![]() |
图 4:对象动画
|
图 4 中的屏幕包含一个切换按钮,用于切换动画的开始/停止。当动画开始时,太阳从地面(绿色区域)升向天空(紫红色区域),周围环境逐渐变亮。当太阳到达屏幕顶部时,它开始向地面下降,周围环境逐渐变暗。在早晨和下午之间,两朵云出现并从左向右在天空中流动。
让我们先从太阳开始。在 Android Studio IDE 的“项目”窗格中:
-
打开“activity_property_animation.xml”,并添加以下 XML 代码以创建布局和 UI 控件,如图 5 所示。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/sky" android:background="#00004C" tools:context="com.peterleow.androidanimation.PropertyAnimationActivity"> <ImageView android:id="@+id/sun" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/sun" android:src="@drawable/sun" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" /> <ImageView android:id="@+id/cloud1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/cloud" android:src="@drawable/cloud" android:layout_alignParentRight="true" android:layout_marginRight="-200dp" android:layout_marginTop="40dp" /> <ImageView android:id="@+id/cloud2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/cloud" android:src="@drawable/cloud" android:layout_alignParentRight="true" android:layout_marginRight="-300dp" android:layout_marginTop="100dp"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="300dp" android:id="@+id/ground" android:background="#004700" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_alignTop="@+id/sun" > </RelativeLayout> <ImageView android:id="@+id/window" android:layout_width="fill_parent" android:layout_height="fill_parent" android:contentDescription="@string/window" android:src="@drawable/window" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/toggleAnimate" android:textColor="#ffffff" android:textOn="@string/animation_on" android:textOff="@string/animation_off" android:checked="false" android:onClick="onToggleClicked" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_marginLeft="30dp" android:layout_marginBottom="30dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:textColor="#ffffff" android:id="@+id/textView" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:layout_marginRight="30dp" android:layout_marginBottom="40dp" /> </RelativeLayout>
图 5:activity_property_animation.xmlXML 文件有一个名为“sky”的整体 <RelativeLayout>,它充当动画的天空。它包含四个 <ImageView> 元素、一个嵌套的 <RelativeLayout>、一个 <TextView> 和一个 <ToggleButton>。
-
这些 <ImageView> 元素的源来自“app/src/main/res/drawable/”——“sun.xml”(android:src="@drawable/sun")定义了太阳的“ShapeDrawable”对象,“window.xml”(android:src="@drawable/window")定义了窗框的“ShapeDrawable”对象,以及“cloud.gif”(android:src="@drawable/cloud")。
-
嵌套的 <RelativeLayout> 称为“ground”,在动画中充当地面。
-
这个 <TextView> 用于在动画期间显示当天的适当问候语。
-
这个 <ToggleButton> 有一个 onClick 事件侦听器(android:onClick="onToggleClicked"),用于切换动画的开始/停止状态。
每个控件都已分配唯一的 ID,以便在代码中进行引用和操作。
-
- 要绘制太阳,请创建一个“Shape Drawable”资源,该资源代表太阳的“ShapeDrawable”对象,通过在 XML 文件中定义其几何形状,包括颜色和渐变。在“app/src/main/res/drawable/”目录中,创建一个名为“sun.xml”的 XML 文件并添加以下代码:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:dither="true" android:shape="oval" > <gradient android:endColor="#ffff7700" android:gradientRadius="250" android:startColor="#ffffff00" android:type="radial" android:useLevel="false" /> <size android:height="150dp" android:width="150dp" /> </shape>
这个 <shape> 声明了一个“椭圆”形状(android:shape="oval")的“ShapeDrawable”对象。然后通过 <gradient> 和 <size> 元素进一步定义此形状。
-
同样,要绘制窗框,请创建一个“Shape Drawable”资源,该资源代表窗框的“ShapeDrawable”对象。在“app/src/main/res/drawable/”目录中,创建一个名为“window.xml”的 XML 文件并添加以下代码:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <solid android:color="#00000000" /> <stroke android:width="20dp" android:color="#c16c11" /> </shape>
这个 <shape> 声明了一个“矩形”形状(android:shape="rectangle")的“ShapeDrawable”对象。然后通过 <stroke> 元素进一步定义此形状。
-
接下来,定义动画细节。在“app/src/main/res/”目录中,创建一个名为“animator”的子目录,这是存储对象动画资源的默认目录。在此目录中,添加两个 XML 文件,如下所示:
-
创建一个名为“sun_movement.xml”的动画资源文件,用于动画太阳,并添加以下代码:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:ordering="sequentially" > <objectAnimator android:duration="10000" android:propertyName="y" android:repeatCount="infinite" android:repeatMode="reverse" android:valueTo="20dp" android:valueType="floatType" /> </set>
这个 <set> 声明了一个“AnimatorSet”对象,它将“AccelerateDecelerateInterpolator”作为时间插值器(android:interpolator="@android:anim/accelerate_decelerate_interpolator"),并按顺序(android:ordering="sequentially")动画其动画器(在此示例中只有一个,尽管您可以添加更多动画器,例如用于动画 x 轴的动画器)。在这种情况下,<set> 仅包含一个“ObjectAnimator”类,其中包含各种属性,用于设置对象动画的详细信息。在此示例中,要动画的属性是 y 轴(android:propertyName="y"),要动画的值是 float 类型(android:valueType="floatType"),动画的结束值为 20dp(android:valueTo="20dp"),动画的持续时间为 10000ms,即 10 秒(android:duration="10000")。这些属性还指定动画在每次完成后反向播放(android:repeatMode="reverse")并且无限期重复(android:repeatCount="infinite")。
- 类似地,创建一个名为“cloud_movement.xml”的动画资源文件,用于动画云,并添加以下代码:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/linear_interpolator" android:ordering="together" > <objectAnimator android:duration="10000" android:propertyName="x" android:repeatCount="infinite" android:repeatMode="restart" android:valueTo="-300dp" android:valueType="floatType" /> <objectAnimator android:duration="2000" android:propertyName="y" android:repeatCount="infinite" android:repeatMode="reverse" android:valueTo="70dp" android:valueType="floatType" /> </set>
这个 <set> 声明了一个“AnimatorSet”对象,它将“LinearInterpolator”作为时间插值器(android:interpolator="@android:anim/linear_interpolator"),并同时(android:ordering="together")动画其两个“ObjectAnimator”元素——每个元素用于“x”和“y”坐标。
-
-
让我们盘点一下到目前为止您所做的工作:创建了一个布局文件和 UI 控件,在 XML 中定义了太阳和窗框的相应“ShapeDrawable”对象,并创建了太阳和云的相应动画资源文件。要触发动画,您需要向 Activity 添加代码。在 Android Studio 中打开“PropertyAnimationActivity.java”,然后按照以下步骤操作:
-
添加必要的包和成员变量,如下所示:
package com.peterleow.androidanimation; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; public class PropertyAnimationActivity extends Activity { AnimatorSet sunAnimatorSet; AnimatorSet cloud1AnimatorSet; AnimatorSet cloud2AnimatorSet; ValueAnimator skyAnimator; ValueAnimator groundAnimator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_property_animation); // ...
请注意,“android:animation”包提供了“属性动画”系统中使用的所有类。
-
添加以下代码以准备太阳的动画:
@Override protected void onCreate(Bundle savedInstanceState) { // ... sunAnimatorSet = (AnimatorSet)AnimatorInflater.loadAnimator(this, R.animator.sun_movement); ImageView sun = (ImageView) findViewById(R.id.sun); sunAnimatorSet.setTarget(sun); // ... }
代码首先加载名为“sun_movement.xml”(R.animator.sun_movement)的动画资源文件(其中包含一个“ObjectAnimator”对象的声明),并将其分配给一个名为“sunAnimatorSet”的“AnimatorSet”对象,然后调用此“AnimatorSet”对象的“setTarget”方法,将动画的目标设置为名为“sun”(R.id.sun)的 ImageView 对象(其图像源为“sun.xml”(android:src="@drawable/sun"))。要开始动画太阳,请调用“start()”方法。但是,稍后您将在切换按钮的 onClick 事件处理程序中添加“start()”方法。
-
通过扩展“AnimatorListenerAdapter”类,向“sunAnimatorSet”对象添加动画侦听器,以侦听“onAnimationStart()”和“onAnimationEnd()”等事件。例如:
@Override protected void onCreate(Bundle savedInstanceState) { // ... sunAnimatorSet.addListener( new AnimatorListenerAdapter() { public void onAnimationStart(Animator animation) { Toast.makeText(getApplicationContext(), "Animation started!", Toast.LENGTH_SHORT).show(); } public void onAnimationEnd(Animator animation) { Toast.makeText(getApplicationContext(), "Animation ended!", Toast.LENGTH_SHORT).show(); } }); // ... }
该侦听器将在“Toast”弹出消息中捕获并宣布动画的开始和结束事件。
-
添加以下代码以准备云的动画:
@Override protected void onCreate(Bundle savedInstanceState) { // ... // cloud1 ImageView cloud1 = (ImageView) findViewById(R.id.cloud1); cloud1AnimatorSet = (AnimatorSet)AnimatorInflater.loadAnimator(this, R.animator.cloud_movement); cloud1AnimatorSet.setTarget(cloud1); // cloud2 ImageView cloud2 = (ImageView) findViewById(R.id.cloud2); cloud2AnimatorSet = (AnimatorSet)AnimatorInflater.loadAnimator(this, R.animator.cloud_movement); cloud2AnimatorSet.setTarget(cloud2); // ... }
请注意,两朵云共享同一个动画资源文件——“cloud_movement.xml”——其中包含两个“ObjectAnimator”对象的声明,它们将并发动画。
-
添加以下代码以准备天空的动画:
@Override protected void onCreate(Bundle savedInstanceState) { // ... skyAnimator = ObjectAnimator.ofInt (findViewById(R.id.sky), "backgroundColor", Color.rgb(0x00, 0x00, 0x4c), Color.rgb(0xae, 0xc2, 0xff)); //set same duration and animation properties as others skyAnimator.setDuration(10000); skyAnimator.setEvaluator(new ArgbEvaluator()); skyAnimator.setRepeatCount(ValueAnimator.INFINITE); skyAnimator.setRepeatMode(ValueAnimator.REVERSE); // ... }
代码通过静态“ObjectAnimator.ofInt()”方法创建一个名为“skyAnimator”的“ObjectAnimator”对象(不是从 XML 创建)。此方法接受几个参数——以“R.id.sky”引用的天空对象作为动画目标,对象的属性——在本例中是“backgroundColor”——要动画,以及一组随时间动画的值,在本例中包含起始颜色值和结束颜色值。调用“setXxx()”方法的适当“ObjectAnimator”对象来设置动画的详细信息,例如“skyAnimator.setDuration(10000);”。
-
通过实现“onAnimationUpdate()”方法,向“skyAnimator”对象添加一个更新侦听器,以在每个动画帧上接收更新回调。例如:
@Override protected void onCreate(Bundle savedInstanceState) { // ... skyAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() { TextView textView = (TextView) findViewById(R.id.textView); float animatedFractionPrev = 0.0f; float animatedFractionCurr = 0.0f; @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animatedFractionCurr = valueAnimator.getAnimatedFraction(); if (animatedFractionCurr > animatedFractionPrev) { if (animatedFractionCurr > 0.0 && animatedFractionCurr <= 0.70) { textView.setText("Good morning!"); } else { textView.setText("Good day!"); } } else { if (animatedFractionCurr >= 0.8) { textView.setText("Good day!"); } else if (animatedFractionCurr < 0.8 && animatedFractionCurr >= 0.1) { textView.setText("Good afternoon!"); } else { textView.setText("Good Evening!"); } } animatedFractionPrev = animatedFractionCurr; } } ); // ... }
在每次回调时,通过调用“getAnimatedFraction()”来获取当前动画进度。此当前动画进度将用于确定要在“Toast”弹出消息中宣布的适当问候语。我将把这部分逻辑留给您来解决。
-
添加以下代码以准备地面的动画:
@Override protected void onCreate(Bundle savedInstanceState) { // ... groundAnimator = ObjectAnimator.ofInt (findViewById(R.id.ground), "backgroundColor", Color.rgb(0x00, 0x47, 0x00), Color.rgb(0x85, 0xae, 0x85)); //set same duration and animation properties as others groundAnimator.setDuration(10000); groundAnimator.setEvaluator(new ArgbEvaluator()); groundAnimator.setRepeatCount(ValueAnimator.INFINITE); groundAnimator.setRepeatMode(ValueAnimator.REVERSE); // ... }
代码与准备天空动画的代码类似。
-
添加名为“onToggleClicked”的 onClick 事件处理程序,以切换动画的开始/停止。例如:
public void onToggleClicked(View view) { boolean on = ((ToggleButton) view).isChecked(); if (on) { sunAnimatorSet.start(); skyAnimator.start(); groundAnimator.start(); cloud1AnimatorSet.start(); cloud2AnimatorSet.start(); } else { sunAnimatorSet.cancel(); skyAnimator.cancel(); groundAnimator.cancel(); cloud1AnimatorSet.cancel(); cloud2AnimatorSet.cancel(); } }}
-
在 Activity 的“onResume()”生命周期事件中添加以下代码,以在 Activity 恢复时将切换按钮状态重置为“未选中”:
@Override public void onResume() { super.onResume(); ((ToggleButton)findViewById(R.id.toggleAnimate)).setChecked(false); }
-
您已完成一项练习,即使用“属性动画”系统实现动画,请在真实设备或 AVD 上进行测试。
视图动画
继续进行一项练习,使用“视图动画”系统创建简单的补间动画。请参阅图 6 了解预期的结果。
![]() |
图 6:视图动画
|
图 5 中的屏幕包含一个按钮和五个在屏幕上散布的 CodeProject 吉祥物。当点击按钮时,五个吉祥物将同时旋转并移动到屏幕中心。
在 Android Studio IDE 中:
-
打开“activity_view_animation.xml”,并添加以下 XML 代码以创建布局和 UI 控件,如图 7 所示。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.peterleow.androidanimation.ViewAnimationActivity" android:background="@color/background"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnGetTogether" android:onClick="getTogether" android:text="@string/get_together" android:layout_centerHorizontal="true"/> <ImageView android:id="@+id/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/codeproject_mascot" android:layout_above="@+id/imageView2" android:layout_toLeftOf="@+id/imageView2" android:layout_toStartOf="@+id/imageView2" /> <ImageView android:id="@+id/imageView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/codeproject_mascot" android:layout_centerVertical="true" android:layout_alignParentRight="true"/> <ImageView android:id="@+id/imageView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/codeproject_mascot" android:layout_toStartOf="@+id/btnGetTogether" android:layout_above="@+id/imageView5" android:layout_alignRight="@+id/imageView4" android:layout_alignEnd="@+id/imageView4" /> <ImageView android:id="@+id/imageView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/codeproject_mascot" android:layout_toStartOf="@+id/btnGetTogether" android:layout_below="@+id/btnGetTogether" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" /> <ImageView android:id="@+id/imageView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/codeproject_mascot" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="43dp" /> </RelativeLayout>
图 7:activity_view_animation.xmlXML 文件包含以下元素:
-
这五个 <ImageView> 元素的源来自“app/src/main/res/drawable/”中的“codeproject_mascot.gif”(android:src="@drawable/codeproject_mascot")。
-
这个 <Button> 元素有一个 onClick 事件侦听器(android:onClick="getTogether"),用于启动动画。
-
-
要触发动画,您需要向 Activity 添加代码。在 Android Studio 中打开“ViewAnimationActivity.java”,然后按照以下步骤操作:
-
添加必要的包,如下所示:
package com.peterleow.androidanimation; import android.app.Activity; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.view.animation.TranslateAnimation; import android.widget.ImageView; public class ViewAnimationActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_animation); } // ...
请注意,“android.view.animation”提供了“视图动画”系统中使用的所有类。
-
添加事件处理程序“getTogether()”用于按钮的 onClick 事件侦听器(android:onClick="getTogether"),如下所示:
public void getTogether(View view) { ImageView imageView = (ImageView) findViewById(R.id.imageView1 ); move(imageView); imageView = (ImageView) findViewById(R.id.imageView2 ); move(imageView); imageView = (ImageView) findViewById(R.id.imageView3 ); move(imageView); imageView = (ImageView) findViewById(R.id.imageView4); move(imageView); imageView = (ImageView) findViewById(R.id.imageView5); move(imageView); }
代码只是从 XML 文件获取五个 ImageView 控件,并将它们提交给 move() 方法进行动画处理。
-
动画代码包含在 move() 方法中,如下所示:
private void move( View view ) { // Get the x, y coordinates of the screen center DisplayMetrics displayMetrics = new DisplayMetrics(); this.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); int x_centerOfScreen = (displayMetrics.widthPixels / 2) - (view.getMeasuredWidth() / 2); int y_centerOfScreen = (displayMetrics.heightPixels / 2) - (view.getMeasuredHeight() / 2); // Get the starting coordinates of the view int startPosition[] = new int[2]; view.getLocationOnScreen(startPosition); AnimationSet animationSet = new AnimationSet(false); RotateAnimation rotate = new RotateAnimation(0,360, Animation.RELATIVE_TO_SELF, 0.5f , Animation.RELATIVE_TO_SELF,0.5f ); rotate.setInterpolator(new LinearInterpolator()); rotate.setRepeatCount(Animation.INFINITE); rotate.setDuration(2000); animationSet.addAnimation(rotate); TranslateAnimation translate = new TranslateAnimation(Animation.ABSOLUTE, 0.0f, Animation.ABSOLUTE, x_centerOfScreen - startPosition[0], Animation.ABSOLUTE, 0.0f, Animation.ABSOLUTE, y_centerOfScreen - startPosition[1]); translate.setInterpolator(new AccelerateDecelerateInterpolator()); translate.setDuration(2000); animationSet.addAnimation(translate); animationSet.setFillAfter(true); view.startAnimation(animationSet); }
除了获取屏幕中心和 ImageView 对象的起始位置的 x 和 y 坐标外,代码还从“android.view.animation”包创建一个“AnimationSet”对象,名为“animationSet”,用于组合一组将一起播放的动画。
创建两种类型的动画——“RotateAnimation”和“TranslateAnimation”。前者用于旋转视图,后者用于更改其在屏幕上的位置。这两种动画中的每一种都将具有由各种“setXxx()”方法(例如 setInterpolator()、setDuration() 等)定义的自己的动画参数(例如插值器、持续时间等)。通过调用“addAnimation()”方法将这些动画添加到“animationSet”中。要动画视图,请调用 View 的“startAnimation()”方法,并将“animationSet”对象传递给它。“setFillAfter(true)”指定变换将在完成后保留,换句话说,所有五个吉祥物将在动画结束后停留在屏幕中心。但是,如果您再次单击按钮,五个吉祥物将出现在原始位置,然后再次动画到屏幕中心。这告诉你什么?嗯,视图动画系统下的动画不会修改原始视图,它只是在它们的新位置重新绘制它们。
-
您已完成一项练习,即使用“视图动画”系统实现动画,请在真实设备或 AVD 上进行测试。
可绘制动画
继续进行一项练习,创建“可绘制动画”,它像幻灯片一样逐个播放一系列可绘制资源。请参阅图 8 了解预期的结果。
![]() |
图 8:可绘制动画
|
图 8 中的屏幕显示一张照片。当您单击照片时,它将按顺序循环显示八张照片,每张照片显示 1500ms。
在 Android Studio IDE 中:
-
打开“activity_drawable_animation.xml”,并添加一个 <ImageView> 元素,如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.peterleow.androidanimation.DrawableAnimationActivity" android:background="@color/background"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageViewSlideShow" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="43dp" /> </RelativeLayout>
此 <ImageView> 将是照片在幻灯片中更改的占位符。
-
在“/app/src/main/res/drawable/”目录中,创建一个名为“slide_show.xml”的 XML 文件并添加以下代码:
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/photo_1" android:duration="1500" /> <item android:drawable="@drawable/photo_2" android:duration="1500" /> <item android:drawable="@drawable/photo_3" android:duration="1500" /> <item android:drawable="@drawable/photo_4" android:duration="1500" /> <item android:drawable="@drawable/photo_5" android:duration="1500" /> <item android:drawable="@drawable/photo_6" android:duration="1500" /> <item android:drawable="@drawable/photo_7" android:duration="1500" /> <item android:drawable="@drawable/photo_8" android:duration="1500" /> </animation-list>
XML 文件包含一个 <animation-list> 节点,该节点包含八个 <item> 节点。每个 <item> 节点代表动画中的一帧,并分配给一张照片(android:drawable="@drawable/photo_1"),并持续动画 1500ms。动画将无限重复,如 <animation-list> 中的“android:oneshot="false"”所指定。
-
要触发动画,您需要向 Activity 添加代码。打开 Android Studio 中的“DrawableAnimationActivity.java”,然后按照以下步骤操作:
-
添加必要的包,如下所示:
package com.peterleow.androidanimation; import android.app.Activity; import android.graphics.drawable.AnimationDrawable; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.widget.ImageView; public class DrawableAnimationActivity extends Activity { AnimationDrawable slideShowAnimation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drawable_animation); // ...
请注意,“AnimationDrawable”类在“android.graphics.drawable”包中,负责创建逐帧动画。
-
添加以下代码以准备动画,如下所示:
@Override protected void onCreate(Bundle savedInstanceState) { // ... ImageView slideShow = (ImageView) findViewById(R.id.imageViewSlideShow); slideShow.setBackgroundResource(R.drawable.slide_show); slideShowAnimation = (AnimationDrawable) slideShow.getBackground(); }
代码只是获取可绘制资源,即“slide_show.xml”,并将其添加为 ImageView 的背景图像。
-
添加“onTouchEvent()”,如下所示:
public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { slideShowAnimation.start(); return true; } return super.onTouchEvent(event); }
当您触摸屏幕上的照片时,此方法将被触发,并调用“start()”方法来启动幻灯片动画。
-
您已完成一项练习,即使用“可绘制动画”系统实现动画,请在真实设备或 AVD 上进行测试。
画布
在前面的练习中,您使用了现成的图像和 XML 定义的图形作为可绘制资源。这些可绘制资源是静态的,无法更改。在某些情况下,您需要动态地在屏幕上绘制形状。为此,您必须使用“Canvas”类。
例如,要在屏幕上动态绘制矩形,有两种方法:
- 通过“ShapeDrawable”类
-
扩展“View”类,创建一个“RectShape”对象(它是“Shape”类的子类),并根据用户输入设置必要的尺寸,然后将其传递给“ShapeDrawable”对象以管理其在屏幕上的存在。当需要绘制矩形时,调用 View 的“invalidate()”方法,该方法会调用“onDraw()”方法来执行实际绘制。
ShapeDrawable shapeDrawable; //... Shape shape = new RectShape(); shapeDrawable = new ShapeDrawable(shape); shapeDrawable.setBounds(left, top, right, bottom); shapeDrawable.getPaint().setColor(Color.BLUE); // ... invalidate();
重写 View 的“onDraw()”方法,通过调用“draw()”方法来执行绘制,该方法以“Canvas”对象作为参数。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); shapeDrawable.draw(canvas); }
使用此方法,您将创建一个允许用户在屏幕上自由绘制矩形的 Activity。请参阅图 9 了解预期的结果。
图 9:在 Canvas 上绘图当您连续触摸、移动并抬起手指时,将绘制一个蓝色矩形。
首先,创建一个名为“ShapeDrawableView.java”的自定义“View”类,如下所示:
package com.peterleow.androidanimation; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RectShape; import android.graphics.drawable.shapes.Shape; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; import java.util.List; /** * Created by Peter Leow on 28/9/2014. */ public class ShapeDrawableView extends View { int startX, startY, endX, endY; private List<ShapeDrawable> shapeDrawables = new ArrayList<ShapeDrawable>(); public ShapeDrawableView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for(ShapeDrawable shapeDrawable: shapeDrawables) { shapeDrawable.draw(canvas); } } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { startX = (int)event.getX(); startY = (int)event.getY(); return true; } else if (event.getAction() == MotionEvent.ACTION_UP){ endX = (int)event.getX(); endY = (int)event.getY(); if (startX > endX) { int tmp = startX; startX = endX; endX = tmp; } if (startY > endY) { int tmp = startY; startY = endY; endY = tmp; } Shape shape = new RectShape(); ShapeDrawable shapeDrawable = new ShapeDrawable(shape); shapeDrawable.setBounds(startX, startY, endX, endY); shapeDrawable.getPaint().setColor(Color.BLUE); shapeDrawables.add(shapeDrawable); invalidate(); return true; } return false; } }
代码的一部分将所有绘制的矩形存储在“ArrayList”中,以便在每次“onDraw()”回调时重新绘制它们。代码还包括确定要绘制的矩形的起点和终点的逻辑,通过检测相应手势事件中的手指位置——“MotionEvent.ACTION_DOWN”和“MotionEvent.ACTION_UP”。
接下来,打开“ShapeDrawableActivity”并添加以下代码以显示“ShapeDrawableView”视图。
package com.peterleow.androidanimation; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class ShapeDrawableActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ShapeDrawableView shapeDrawableView = new ShapeDrawableView(this); setContentView(shapeDrawableView); } // ... }
现在可以进行测试了!
- 通过“Canvas”的 drawXxx() 方法
-
扩展“View”类,创建一个“Paint”对象,该对象定义了要绘制的形状的样式和颜色信息。当需要绘制形状时,调用 View 的“invalidate()”方法,该方法会导致回调到“onDraw()”方法执行实际绘制。
Paint paint = new Paint(); //... paint.setColor(Color.WHITE); paint.setAntiAlias(true); paint.setTextSize(45f); paint.setStrokeWidth(2.0f); paint.setStyle(Paint.Style.STROKE); paint.setShadowLayer(5.0f, 10.0f, 10.0f, Color.BLACK); // ... invalidate();
重写 View 的“onDraw()”方法,通过调用“drawRect()”方法来绘制矩形,该方法属于“Canvas”对象,并由“onDraw()”方法作为参数。矩形将根据传递到“drawRect()”方法中的“Paint”对象的样式和颜色信息进行渲染。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawRect(left, top, right, bottom, paint); }
使用此方法,您将创建一个 Activity,该 Activity 将在屏幕上以不同角度重复“绘制”文本消息。请参阅图 10 了解预期的结果。
图 10:在 Canvas 上绘图首先,创建一个名为“RotateTextView.java”的自定义“View”类,如下所示:
package com.peterleow.androidanimation; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; /** * Created by Peter Leow on 28/9/2014. */ public class RotateTextView extends View { private String message = " Code Project"; public RotateTextView(Context context) { super(context); this.setBackgroundColor(Color.GRAY); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int viewWidth = getWidth(); int viewHeight = getHeight(); canvas.translate(viewWidth/2, viewHeight/2); Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setAntiAlias(true); paint.setTextSize(45f); paint.setStrokeWidth(2.0f); paint.setStyle(Paint.Style.STROKE); paint.setShadowLayer(5.0f, 10.0f, 10.0f, Color.BLACK); for(int i = 0; i < 10; i++) { canvas.drawText(message, 0, 0, paint); canvas.rotate(36); } } }
代码将以不同角度绘制文本消息十次。
接下来,打开“RotateTextViewActivity”并添加以下代码以显示“RotateTextView”视图。
package com.peterleow.androidanimation; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; public class RotateTextViewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); RotateTextView rotateTextView = new RotateTextView(this); setContentView(rotateTextView); } // ... }
现在可以进行测试了!
OpenGL ES
2D 图形和动画到此为止,让我们继续对如何使用 OpenGL ES API 在 Android 中创建 3D 图形和动画进行基本了解。
了解基础知识
一般来说,OpenGL ES API 在 Android 框架中提供了两个主要类来实现此目的——“GLSurfaceView”和“GLSurfaceView.Renderer”。
“GLSurfaceView”提供了一个视图对象,可以在其中绘制和操作 3D 图形。实际绘制由实现“GLSurfaceView.Renderer”接口并附加到“GLSurfaceView”对象的渲染器对象执行。例如:
package com.peterleow.androidanimation; import android.app.Activity; import android.opengl.GLSurfaceView; public class OpenGLActivity extends Activity { private GLSurfaceView glSurfaceView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); glSurfaceView = new GLSurfaceView(this); glSurfaceView.setRenderer(new OpenGLRenderer()); setContentView(glSurfaceView); } // ...
渲染器对象必须实现“GLSurfaceView.Renderer”接口的以下三个方法:
-
onSurfaceCreated() - 在“GLSurfaceView”创建时调用一次。用于设置和初始化 OpenGL 环境参数和图形对象。
-
onSurfaceChanged() - 在每次重绘“GLSurfaceView”时调用。
-
onDrawFrame() - 当“GLSurfaceView”的几何形状发生变化时调用,例如当设备从纵向更改为横向时。
下面显示了渲染器类的骨架:
public class OpenGLRenderer implements GLSurfaceView.Renderer { public OpenGLRenderer() { // ... } public void onSurfaceCreated(GL10 gl, EGLConfig config) { // ... } public void onSurfaceChanged(GL10 gl, int width, int height) { // ... } @Override public void onDrawFrame(GL10 gl) { // ... } }
让事情发生
继续进行一项练习,通过使用 OpenGL ES 1.0 创建一个围绕其 3 个轴旋转的立方体。(有不同版本的 API 可用,我选择在这里使用 OpenGL ES 1.0,它足以满足此入门级别。)请参阅图 11 了解预期的结果。
![]() |
图 11:旋转立方体
|
首先,创建一个名为“Cube”的 Java 类来表示立方体对象。请按照以下步骤操作:
-
添加必要的包,特别是这个——“javax.microedition.khronos.opengles.GL10”,并声明一个“ByteBuffer”、“ByteOrder”和“FloatBuffer”。
package com.peterleow.androidanimation; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; /** * Created by Peter Leow on 29/9/2014. */ public class Cube { private FloatBuffer vertexBuffer; private ByteBuffer drawListBuffer; private FloatBuffer colorBuffer; // ...
-
在名为“cubeCoords”的数组中定义立方体的八个顶点的坐标,如下所示:
// ... public class Cube { // ... // Coordinates for vertices static float cubeCoords[] = { -1.0f, -1.0f, 1.0f, // vertex 0 (x0, y0, z0) 1.0f, -1.0f, 1.0f, // vertex 1 (x1, y1, z1) 1.0f, 1.0f, 1.0f, // vertex 2 (x2, y2, z2) -1.0f, 1.0f, 1.0f, // vertex 3 (x3, y3, z3) -1.0f, -1.0f, -1.0f, // and so on... 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f }; // ... }
-
在名为“colors”的数组中定义每个顶点的颜色,如下所示:
// ... public class Cube { // ... // Color definition private float colors[] = { 1.0f, 0.0f, 0.0f, 1.0f, // red for vertex 0 0.0f, 1.0f, 0.0f, 1.0f, // gree for vertex 1 0.0f, 0.0f, 1.0f, 1.0f, // blue for vertex 2 1.0f, 1.0f, 0.0f, 1.0f, // yellow for vertex 3 1.0f, 1.0f, 0.0f, 1.0f, // and so on... 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f }; } // ...
-
在名为“drawOrder”的数组中指定绘制立方体的顺序,如下所示:
// ... public class Cube { // ... // Drawing order of cubeCoords[] private byte drawOrder[] = { 0, 1, 3, 1, 3, 2, 1, 2, 6, 1, 6, 5, 0, 3, 7, 0, 7, 4, 4, 7, 6, 4, 6, 5, 3, 7, 2, 7, 2, 6, 0, 4, 1, 4, 1, 5 }; // ... }
数组中每个元素的值对应于“cubeCoords”中顶点的坐标。例如,值 2 指的是顶点 2 (x2, y2, z2),依此类推。
-
在构造函数中,将顶点、颜色和绘制顺序的信息放入相应的缓冲区。
public Cube() { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(cubeCoords.length * 4); byteBuffer.order(ByteOrder.nativeOrder()); vertexBuffer = byteBuffer.asFloatBuffer(); vertexBuffer.put(cubeCoords); vertexBuffer.position(0); byteBuffer = ByteBuffer.allocateDirect(colors.length * 4); byteBuffer.order(ByteOrder.nativeOrder()); colorBuffer = byteBuffer.asFloatBuffer(); colorBuffer.put(colors); colorBuffer.position(0); drawListBuffer = ByteBuffer.allocateDirect(drawOrder.length); drawListBuffer.put(drawOrder); drawListBuffer.position(0); }
-
创建一个“draw()”方法,该方法调用 OpenGL ES 1.0 API 的各种方法来组合对象并渲染立方体。有关这些方法的详细信息,请参阅“javax.microedition.khronos.opengles.GL10”。
public void draw(GL10 gl) { // Enable vertex array buffer to be used during rendering gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // Tell openGL where the vertex array buffer is gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); // Enable color array buffer to be used during rendering gl.glEnableClientState(GL10.GL_COLOR_ARRAY); // Tell openGL where the color array buffer is gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer); // Draw each plane as a pair of triangles, based on the drawListBuffer information gl.glDrawElements(GL10.GL_TRIANGLES, drawOrder.length, GL10.GL_UNSIGNED_BYTE, drawListBuffer); }
您已创建了一个代表立方体对象的 Java 类“Cube”。接下来,创建一个名为“OpenGLRenderer”的渲染器类,该类实现“GLSurfaceView.Renderer”接口来渲染立方体。请按照以下步骤操作:
-
添加必要的包,并在构造函数中实例化一个“Cube”对象,如下所示:
package com.peterleow.androidanimation; import android.opengl.GLSurfaceView; import android.opengl.GLU; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** * Created by Peter Leow on 29/9/2014. */ public class OpenGLRenderer implements GLSurfaceView.Renderer { private Cube cube; private float rotationAngle; public OpenGLRenderer() { cube = new Cube(); } // ...
-
添加代码以实现“GLSurfaceView.Renderer”接口的三个方法。
public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glEnable(GL10.GL_DEPTH_TEST); } public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); float fovy = 50.0f; // Field of view angle, in degrees, in the Y direction. float aspect = (float)width / (float)height; float zNear = 0.1f; float zFar = 100.0f; // Set up a perspective projection matrix GLU.gluPerspective(gl, fovy, aspect, zNear, zFar); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); } @Override public void onDrawFrame(GL10 gl) { gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // Replace the current matrix with the identity matrix. gl.glLoadIdentity(); gl.glTranslatef(0.0f, 0.0f, -6.0f); gl.glScalef(0.8f, 0.8f, 0.8f); gl.glRotatef(rotationAngle, 1.0f, 1.0f, 1.0f); cube.draw(gl); rotationAngle -= 0.4f; } }
打开“OpenGLActivity”以添加显示 3D 立方体的代码。请按照以下步骤操作:
-
添加“android.opengl.GLSurfaceView”包,实例化一个“GLSurfaceView”视图,该视图接受前面创建的“OpenGLRenderer”类的实例作为参数,并将此“GLSurfaceView”视图设置为 Activity 的内容视图。
package com.peterleow.androidanimation; // ... import android.opengl.GLSurfaceView; public class OpenGLActivity extends Activity { private GLSurfaceView glSurfaceView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); glSurfaceView = new GLSurfaceView(this); glSurfaceView.setRenderer(new OpenGLRenderer()); setContentView(glSurfaceView); } // ...
-
在 Activity 生命周期各自的“onPause()”和“onResume()”方法中添加代码以暂停和恢复渲染。
@Override protected void onPause() { super.onPause(); glSurfaceView.onPause(); } @Override protected void onResume() { super.onResume(); glSurfaceView.onResume(); }
摘要
在这段旅程中,您通过一系列实践练习学习并探索了各种 API 来实现简单的动画和创建简单的 2D 和 3D 图形。为了方便参考,我将它们放在一个链接中,以便快速跳转到本文的相应主题。