您的位置:首页 > 移动开发 > Android开发

Android开发指南-二维图形

2011-10-10 10:01 218 查看


二维图形2D Graphics


Android 提供一个定制的2D图形库,用来绘制图形图像和制作动画。你将从android.graphics.drawableandroid.view.animation包中找到这些通用类。

本文简单介绍如何在Android应用程序中进行画图。我们将讨论使用Drawable对象画图的基础知识,如何使用几个Drawable子类,以及如何创建动画,一个图形的补间动画或者一系列图形的连续动画(就像电影胶卷一样)。

可绘制物Drawables

一个Drawable 是一个“某些可以被绘制的物体”的一般抽象。你将发现这个Drawable类扩展了多种具体可绘制图形类,包括BitmapDrawable,

ShapeDrawable,
PictureDrawable,
LayerDrawable, 等等。当然,你还可以扩展这些类来定义你自己的具有独特行为的可绘制对象。

有三种方式来定义和实例化一个Drawable:使用一个保存在你的项目资源中的图像;使用一个定义了Drawable属性的XML文件;或者使用通常的类构造函数。下面,我们将挨个讨论前面两种方法(对于一个经验丰富的开发人员而言,使用构造函数没什么新意)。

从资源图像中创建Creating from resource images

一个为你的应用程序增加图形的简单方法是通过引用项目资源中的一个图片文件。支持的图片文件格式有PNG(推荐的),JPG(可接受的)和GIF(不鼓励的)。这个技术将显然推荐使用在应用程序图标,logo,或者其它类似使用于游戏中的图形。

为了使用一个图片资源,只要把你的文件添加到你项目的res/drawable/目录即可。从那里,你可以在代码或XML布局中进行引用。任何一种方式,都是通过资源ID来引用,资源ID是不带扩展后缀的文件名(比如,my_image.png通过my_image来引用)。

注意: 图像资源被放在res/drawable/里。也许会通过aapt工具进行无损图像压缩而被自动优化。比如,一个不需要多于256色的真彩色PNG图片可能会被转换成一个带有调色板的8位PNG。这产生了相同质量但占用更少内存的图片。因此需要意识到该目录下的二进制图像可能会在编译期间被改变。如果你想以比特流读取一个图片并转换为一个位图,那么需要把你的图片文件放在res/raw/目录,这里的文件不会被优化。

示例代码Example code

下面的代码片断说明了如何构造一个ImageView,从drawable资源中使用并添加到布局中。

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


注意: 每个你项目里的唯一资源只能维护一个状态,而无论你为它实例化了多少个不同对象。比如,如果你从相同的图像资源实例化两个可绘制对象,然后改变其中之一的属性(比如alpha),那它也将作用于另一个。所以当处理一个图片资源的多个实例时,你应该实施一个tween
animation,而不是直接转换这个可绘制对象。

示例XML

下面的XML片断显示了如何添加一个可绘制资源到一个XML布局中的ImageView里(为了有趣些,添加一些红色渲染)。

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:tint="#55ff0000"

android:src="@drawable/my_image"/>


更多关于使用项目资源的信息,请阅读资源和资产Resources and Assets

从资源XML中创建Creating from resource XML

到目前为止,你应该对Android用户界面User Interface的开发原理比较熟悉。因此,你应该了解在XML中定义对象所固有的能力和灵活性。这个理念从视图延伸到可绘制对象。如果你想创建一个可绘制(Drawable)对象,而它并不初始依赖于你代码中的变量或者用户交互,那么在XML里面定义它是个好的选择。即便你预期这个可绘制对象在用户使用你的应用程序时将会改变其属性,你也应该考虑在XML里面定义它,因为一旦被实例化后,你就可以修改它的属性。

一旦你在XML中定义了你的可绘制对象,把这个文件保存到你项目中的res/drawable/ 目录下。然后,通过传递资源ID参数调用Resources.getDrawable()获取并实例化这个对象。 (参见example
below.)

任何支持inflate()方法的可绘制对象(Drawable)子类可以在XML里定义并由你的应用程序实例化。每个支持XML扩充的可绘制对象利用特定的XML属性来帮助定义对象属性(参见类参考了解这些属性)。查阅每个可绘制对象子类的类描述文档,以获知如何在XML中定义它。

例子Example

下面是一些定义了一个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文件后,下面的代码将用来实例化它并把它设置为一个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);


然后通过下面的方法让这个transition运转起来(每1秒转变一次):

transition.startTransition(1000);


请参考上面所列的可绘制对象(Drawable)类以了解更多它们所支持的XML属性。

可绘制形状ShapeDrawable

当你想动态的画一些二维图形时,ShapeDrawable对象可能会满足你的要求。 通过一个ShapeDrawable,你可以绘制原始图形和以任何想象得到的方式设计它们。

ShapeDrawable是Drawable的一个扩展类,因此你能使用在任何可用Drawable的地方- 也许是视图背景,用setBackgroundDrawable()方法来设置。当然,你还能以它自己的视图来绘制图形,并添加到你的布局中只要你乐意。因为ShapeDrawable有它自己的draw()方法,你可以创建一个视图子类在View.onDraw()方法中绘制这个ShapeDrawable。下面这个视图类的一个基本扩展就是这么做的,把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。然后设置颜色和边界。如果你不设置边界,那么这个图形将不被绘制,不过如果你不设置颜色的话,它会设置成缺省的黑色。

通过这个定制视图,它能以任何你想要的方式绘制。通过上面的例子,我们能在一个活动中以编程的方式绘制这个图形:

CustomDrawableView mCustomDrawableView;

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

mCustomDrawableView = new CustomDrawableView(this);

setContentView(mCustomDrawableView);

}


如果你希望从XML布局中而不是活动中绘制这个自定义可绘制对象,那么这个CustomDrawable类必须重写View(Context, AttributeSet) 构造函数,这个当通过XML扩充来实例化一个视图时被调用。然后为这个XML添加一个CustomDrawable元素,如下:

<com.example.shapedrawable.CustomDrawableView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

/>


这个ShapeDrawable类(像很多其他在android.graphics.drawable包中的可绘制对象类型一样)允许你通过公共的方法来定义这个可绘制对象的各种属性。有一些属性,你可能会做一些调整,比如alpha透明度,颜色过滤器,抖动,模糊度和颜色。

可绘制九宫格

一个NinePatchDrawable图形是一个可拉伸位图图像,Android会自动调整来容纳视图的内容,你可以用来作为背景。一个使用九宫格的例子是Android按钮背景-按钮必须延伸以适应各种长度的字符串。一个九宫格可绘制对象是一个标准的PNG图像,并包含一个附加的单像素宽的边界。它必须以扩展名.9.png保存,并存放到res/drawable/目录。

这个边界用来定义这个图像的可拉伸和静态区域。通过在边界的左边和上面部分绘画单像素宽的一个或多条黑线来指示一个可拉伸区。(你能有任意多的可拉伸区)。这些可拉伸区的相对尺寸保持一样,因此最大的区域总是保持最大。

你也可以通过在右边和下面画线来定义这个图片(事实上,这些填充线)的一个可选的可绘制区。如果一个视图对象设置这个九宫格为其背景然后指定这个视图的文本,它将自我调整以使得文本适应在刚才通过右边和下面的画线指定的区域里面(如果被包含进来)。如果这些填充线未被包含,Android使用左边和上面的线来定义可绘制区域。

为了阐明不同线的区别,左边和上面定义了哪些图像像素允许被复制来拉伸这个图像。下面和右边的线则定义了这个图像中的相对区域,这个区域可允许视图内容显示于其中。

下面是一个用来定义一个按钮的九宫格文件例子:



这个九宫格通过左边和上面的线条定义了一个可拉伸区域以及通过下面和右边的线条定义了一个可绘制区域。在图中上半部分,灰色的虚线标示了可以被复制来拉伸图像的区域。而下半部分的粉红虚线框标示了允许显示视图内容的区域。如果内容不能适应这个区域,那么这个图像将被拉伸来适应它。

Draw 9-patch 工具提供了一个非常方便的方法来创建你的九宫格图像,使用一个所见即所得的图像编辑器。它甚至会在你的可拉伸区域会产生人工痕迹时(复制像素的副效果)提出告警。

示例XMLExample XML

下面是一些示例布局XML来说明怎么添加一个九宫格图像到几个按钮中。(这个九宫格图像被保存为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和上面显示的九宫格图像进行绘制。注意按钮的高度和宽度如何根据文本大小进行相应调整,以及背景图片自动拉伸来容纳它。

补间动画Tween Animation

一个补间动画可以在视图对象的内容上执行一系列的简单变换(位置,尺寸,旋转和透明度)。因此,如果你有一个文本视图TextView对象,你可以移动,旋转,扩展或者收缩这个文本。如果它有一个背景图像,也将随着文本一起变换。动画包animation package 提供了所有补间动画需要使用的类。

一系列动画指令定义了这个补间动画,通过XML或者代码。就像定义一个布局一样,XML文件是推荐使用的方法,因为它比硬编码这个动画要更容易阅读,可复用和可交换。在下面的例子中,我们使用XML。(为了学习更多关于在你的应用程序中定义一个动画方面的知识,请参考AnimationSet 类和其他Animation
子类。)

动画指令定义你想要发生的变换,什么时候发生以及多长时间。变换可以是顺序的或者是同时发生的-比如,你可以让一个TextView的内容从左到右移动,然后旋转180度,或者你让它同时移动和旋转。每个变换采用一组特定参数(尺寸变化的起动以及结束尺寸,旋转的起动和结束角度,等等),和一组通用参数(比如,起动时间和持续时间)。要使若干变换同时发生,可以为它们设定相同的起动时间;要让它们顺序发生,计算起动时间为前置变换起动时间加上持续时间。

这个动画XML文件归属于res/anim/目录。该文件必须有一个单独的根元素:这将会是一个单独的<alpha>, <scale>, <translate>, <rotate>,插值器(interpolator)元素, 或包含这些元素组的<set> 元素(可能含有另一个<set>元素)。缺省情况下,所有的动画指令都是同时发生的。要让它们顺序发生,你必须指定startOffset 属性,如下例所示:

下面来自ApiDemos的XML文件被用来拉伸,然后同时旋转一个视图对象。

<set 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:fillAfter="false"
android:duration="700" />
<set android:interpolator="@android:anim/decelerate_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:startOffset="700"
android:duration="400"
android:fillBefore="false" />
<rotate
android:fromDegrees="0"
android:toDegrees="-45"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400" />
</set>
</set>


屏幕坐标(未在本例中使用)是基于左上角的 (0,0),向下和向右时坐标值增加。

一些数值,比如pivotX,可以被指定为对象自己或父对象的相对值。确保使用合适的格式(“50”表示相对于父对象的50%,或者“50%”表示相对于自己的50%)。

你可以通过分配一个插值器Interpolator来决定一个变换的时间特性。Android包含一些插值器子类来指定可以用在XML里的各种速度曲线属性值。

把这个XML保存为res/anim/目录下的hyperspace_jump.xml文件,下面的Java代码将从布局中引用它并把它应用到一个ImageView 对象。

ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);


作为startAnimation()方法的一个替代,你可以通过Animation.setStartTime()定义一个动画起动时间,然后通过View.setAnimation()给这个视图分配一个动画。

更多关于XML语法,可用标签和属性的信息,参见可用资源Available Resources一章中关于动画部分的讨论。

注意: 不管你的动画将如何移动和调整大小,容纳你的动画的视图边界将不会自动调整来适应它。即便如此,动画将仍然会被绘制到边界以外而不会被裁剪。不过,如果超过父视图的边界则将被裁剪。

帧动画Frame Animation

这是一个经典动画,由一连串不同的图像,顺序播放而创建,就像电影胶卷一样。AnimationDrawable 类是帧动画技术实现的基础。

虽然你可以在代码中使用AnimationDrawable类的API定义一个动画的各个帧,但是通过在一个单独的XML文件中列举所有组成该动画的帧的方式实现起来要简单得多。和上面的补间动画一样,这种动画的XML文件归属于项目的res/anim/目录下。在这个情况下,指令是每个动画帧的顺序和持续时间。

这个XML文件由一个根元素<animation-list>和一系列定义帧的子<item>节点组成:一个表示帧的可绘制资源和帧持续时间。下面是一个例子XML文件:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>


这个动画只有三帧。通过设置android:oneshot属性为true,它将只循环一次然后停在最后那一帧上。如果设置成false,那么动画将不断循环运行。通过把XML保存为res/anim/目录下的rocket_thrust.xml,它可以被添加为一个视图的背景图像然后播放。下面是一个例子活动,其中动画被添加到一个ImageView
并在触摸屏幕时启动动画:

AnimationDrawable rocketAnimation;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.anim.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}

public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}


注意在AnimationDrawable上调用的start()方法不能在你的活动的onCreate()中调用,因为这个AnimationDrawable还没有被完全附着到窗口上。如果你想立即播放这个动画,而不需要请求交互,那么你可能要从活动的onWindowFocusChanged()函数中调用它,这将在窗口获得焦点时被调用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: