目标

掌握常用绘图类的使用 ,能够绘制不同的图形掌握Matrix类的使用方式 ,能够实现为图片添加特效的功能掌握动画的使用 ,能够实现补间动画与逐帧动画的效果

​ 图形图像在Android应用中会经常用到,如一些程序的图标、界面的美化等都离不开图形图像。Android系统对图形图像的处理非常强大,对于2D图像它没有沿用Java中的图形处理类,而是使用了自定义的处理类,接下来本章将针对Android常用的绘图类、图形图像特效以及动画进行讲解 。

一、常用的绘图类

目标

掌握常用绘图类的使用 ,能够绘制不同的图形

​ Android中常用的绘图类有Bitmap类、BitmapFactory类、Paint类以及Canvas类,通过对这几个类的使用可以分别实现创建位图、将指定资源解析为位图、创建画笔、绘制画布等功能。

Bitmap类:可以获取图像文件信息,对图像进行剪切、旋转、缩放等操作,并可以指定格式保存图像文件。 BitmapFactory类:是位图工厂,它是一个工具类。 Paint类:代表画笔,用来描述图形的颜色及风格。 Canvas类:代表画布,通过该类提供的方法,可以绘制各种图形。

1.1 Bitmap类

Bitmap类提供了一些静态方法,具体如下表所示。

方法名称功能描述createBitmap(int width, int height, Config config)创建位图,width代表要创建的图片的宽度,height代表高度,config代表图片的配置信息createBitmap(int colors[], int offset, int stride,int width, int height, Config config)使用颜色数组创建一个指定宽高的位图,颜色数组的个数为width*heightcreateBitmap(Bitmap src)使用源位图创建一个新的位图createBitmap(Bitmap source, int x, int y, int width, int height)从源位图的指定坐标开始剪切指定宽高的一块图像,用于创建新的位图createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)按照Matrix规则从源位图的指定坐标开始剪切指定宽高的一块图像,用于创建新的位图。

1.2BitmapFactory类

方法名称功能描述decodeFile(String pathName)将指定路径的文件解码为位图decodeStream(InputStream is)将指定输入流解码为位图decodeResource(Resources res, int id)将给定的资源id解析为位图

Bitmap bitmap =

BitmapFactory.decodeResource(this.getResources(),R.drawable.icon);

//通过decodeResource()方法将drawable文件夹中的icon.png图片资源解码为位图

1.3 Paint类

方法名称功能描述setARGB(int a, int r, int g, int b)设置颜色,各参数值均为0~255之间的整数,几个参数分别用于表示透明度、红色、绿色和蓝色的值setColor(int color)设置颜色setAlpha(int a)设置透明度setAntiAlias(boolean aa)设置画笔是否使用抗锯齿功能setTextAlign(Align align)设置绘制文本时的文字对齐方式。参数值为Align.CENTER、Align.LEFT、Align.RIGHT,分别表示居中,左或右对齐setTextSize(float textSize)设置绘制文本时的文字大小setFakeBoldText(boolean fakeBoldText)设置绘制文字时是否为粗体文字setDither(boolean dither)指定是否使用图像抖动处理,如果使用会使图像颜色更加平滑、饱满、清晰setShadowLayer(float radius, float dx, float dy, int color)设置阴影。radius表示阴影的角度,dx和dy表示阴影在x轴和y轴上的距离,color表示阴影的颜色setXfermode(Xfermode xfermode)设置图像的混合模式

接下来定义一个画笔,并指定该画笔的颜色为红色,示例代码如下:

Paint paint = new Paint();

paint.setColor(Color.RED); //指定画笔颜色为红色

1.4 Canvas类

方法名称功能描述drawRect(Rect r, Paint paint)使用画笔绘制矩形drawOval(RectF oval, Paint paint)使用画笔绘制椭圆形drawCircle(float cx, float cy, float radius, Paint paint)使用画笔在指定位置画出指定半径的圆drawLine(float startX, float startY, float stopX, float stopY, Paint paint)使用画笔在指定位置画线drawRoundRect(RectF rect, float rx, float ry, Paint paint)使用画笔绘制指定圆角矩形,其中rx表示X轴圆角半径,ry表示Y轴圆角半径

在View的onDraw()方法中使用画笔Paint在画布上绘制矩形。

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

Paint paint = new Paint(); //创建画笔

paint.setColor(Color.RED);

Rect r = new Rect(40,40,200,100); //构建矩形对象并指定位置、宽高

canvas.drawRect(r,paint); //调用绘制矩形的方法

}

1.5 实战演练—绘制小狗

​ 本节我们将通过一个绘制小狗的案例来演示如何使用这些常用的绘图类,本案例的界面效果如下图所示。

绘制小狗的自定义View类 drawdog\DrawView.java

package cn.itcast.drawdog;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Canvas;

import android.graphics.Paint;

import android.util.AttributeSet;

import android.view.View;

public class DrawView extends View {

private Context mContext;

public DrawView(Context context, AttributeSet attrs) {

super(context, attrs);

this.mContext=context;

}

@Override

protected void onDraw(Canvas canvas) {

this.setBackgroundResource(R.drawable.bg);//设置画布的背景图片

//将dog.png图片资源解码为位图

Bitmap dogBitmap = BitmapFactory.decodeResource(mContext.getResources(),R.drawable.dog);

Paint paint = new Paint();//创建画笔

canvas.drawBitmap(dogBitmap, 300, 550,paint); //绘制图片

}

}

放置界面控件 res\layout\activity_main.xml

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

二、为图像添加特效

目标

掌握Matrix类的使用方式,能够实现为图片添加特效的功能

特效方法名称功能描述平移setTranslate(float dx,float dy)指定图像在X、Y轴移动dx和dy的距离平移preTranslate(float dx, float dy)使用前乘的方式计算在X、Y轴平移的距离。平移postTranslate(float dx,float dy)使用后乘的方式计算在X、Y轴平移的距离。旋转setRotate(float degrees)指定图片旋转degrees度旋转preRotate(float degrees)使用前乘的方式指定图片旋转degrees度旋转postRotate(float degrees, float px, float py)使用后乘的方式控制Matrix以参数px和py为轴心旋转degrees度缩放setScale(float sx, float sy)指定图像在X轴和Y轴的缩放比例为sx和sy缩放preScale(float sx, float sy)使用前乘的方式计算图像在X轴和Y轴的缩放比例缩放postScale(float sx, float sy)使用后乘的方式计算图像在X轴和Y轴的缩放比例倾斜setSkew(float kx, float ky)指定图像在X、Y轴的倾斜值倾斜preScale(float kx, float ky)使用前乘的方式设置图像在X、Y轴的倾斜值倾斜postScale(float kx, float ky)使用后乘的方式设置图像在X、Y轴的倾斜值

接下来我们通过一个案例来演示如何使用Matrix类为图片添加特效。本案例的界面效果如下图所示。

创建TranslateView类 specialeffect\TranslateView.java

package cn.itcast.specialeffect;

import android.content.Context;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Canvas;

import android.graphics.Matrix;

import android.graphics.Paint;

import android.util.AttributeSet;

import android.view.View;

public class TranslateView extends View {

public TranslateView(Context context) {

super(context);

}

public TranslateView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public TranslateView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

Paint paint = new Paint(); //创建画笔

// 将图片husky.png解析为bitmap位图对象

Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.husky);

Matrix matrix = new Matrix(); //创建一个矩阵

matrix.setTranslate(100,100); //将矩阵向右(X轴)平移100,向下(Y轴)平移100

canvas.drawBitmap(bitmap, matrix, paint);//将图片按照矩阵的位置绘制到界面上

}

}

引用TranslateView类 res\layout\activity_main.xml

android:layout_width="match_parent"

android:layout_height="match_parent">

android:layout_width="wrap_content"

android:layout_height="wrap_content">

三、动画

目标

掌握动画的使用,能够实现补间动画与逐帧动画的效果

​ 在Android开发中,避免不了用到动画,Android系统给我们提供了三种实现动画效果的方式,分别为补间动画、逐帧动画和属性动画。

3.1 补间动画

在Android中,提供了四种补间动画:

透明度渐变动画

​ 透明度渐变动画是通过改变View组件透明度来实现的渐变效果。它主要通过指定动画开始时View的透明度、结束时View的透明度以及动画持续时间来实现的。

旋转动画

​ 旋转动画是通过对View指定动画开始时的旋转角度、结束时的旋转角度以及动画播放时长来实现的。

pivotX: 可选50、50%、50%p (p表示parent)

50: 表示在当前View左上角的X轴坐标加上50px的位置,作为旋转点的x轴坐标50%: 表示在当前View左上角的X轴坐标加上View自己宽度的50%的位置,作为旋转点的x轴坐标50%p: 表示在当前View左上角的X轴坐标加上父控件宽度的50%的位置,作为旋转点的x轴坐标

缩放动画

​ 缩放动画是通过对动画指定开始时的缩放系数、结束时的缩放系数以及动画持续时长来实现的。

平移动画

​ 平移动画是通过指定动画的开始位置、结束位置以及动画持续时长来实现的。

接下来,我们通过一个案例来演示4种补间动画的效果。本案例的界面效果如下图所示。

放置界面控件 res\layout\activity_main.xml

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_marginBottom="5dp">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/iv_tween"

android:layout_centerInParent="true"

android:id="@+id/iv_bean"/>

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_alignParentBottom="true">

android:id="@+id/btn_one"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:text="渐变"/>

android:id="@+id/btn_two"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:text="旋转"/>

android:id="@+id/btn_three"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:text="缩放"/>

android:id="@+id/btn_four"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1"

android:text="移动" />

实现补间动画的4种效果 tween\MainActivity.java

package cn.itcast.tween;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.view.View;

import android.view.animation.Animation;

import android.view.animation.AnimationUtils;

import android.widget.Button;

import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private Button buttonOne;

private Button buttonTwo;

private Button buttonThree;

private Button buttonFour;

private ImageView ivBean;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//初始化控件并为对应的控件加添点击事件的监听器

buttonOne = findViewById(R.id.btn_one);

buttonTwo = findViewById(R.id.btn_two);

buttonThree = findViewById(R.id.btn_three);

buttonFour = findViewById(R.id.btn_four);

ivBean = findViewById(R.id.iv_bean);

buttonOne.setOnClickListener(this);

buttonTwo.setOnClickListener(this);

buttonThree.setOnClickListener(this);

buttonFour.setOnClickListener(this);

}

public void onClick(View v) {

switch (v.getId()) {

case R.id.btn_one: //"渐变"按钮的点击事件

Animation alpha = AnimationUtils.loadAnimation(this,

R.anim.alpha_animation);

ivBean.startAnimation(alpha);

break;

case R.id.btn_two: //"旋转"按钮的点击事件

Animation rotate = AnimationUtils.loadAnimation(this,

R.anim.rotate_animation);

ivBean.startAnimation(rotate);

break;

case R.id.btn_three://"缩放"按钮的点击事件

Animation scale = AnimationUtils.loadAnimation(this,

R.anim.scale_animation);

ivBean.startAnimation(scale);

break;

case R.id.btn_four: //"平移"按钮的点击事件

Animation translate = AnimationUtils.loadAnimation(this,

R.anim.translate_animation);

ivBean.startAnimation(translate);

break;

}

}

}

3.2 逐帧动画

逐帧动画是按照准备好的静态图像顺序播放的,利用人眼的“视觉暂留”原理,造成动画的错觉。

逐帧动画的原理与放胶片看电影的原理是一样的,它们都是一张一张地播放事先准备好的静态图像。

​ 接下来,我们通过一个案例来讲解如何使用帧动画来实现动态的Wi-Fi信号效果。本案例的界面效果如下图所示。

放置界面控件 res\layout\activity_main.xml

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@android:color/white"

tools:context=".MainActivity">

android:id="@+id/iv_wifi"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:layout_marginTop="100dp"

android:layout_marginBottom="20dp"

android:background="@drawable/frame" />

android:id="@+id/btn_play"

android:layout_width="70dp"

android:layout_height="70dp"

android:layout_below="@+id/iv_wifi"

android:layout_centerHorizontal="true"

android:layout_marginTop="20dp"

android:background="@android:drawable/ic_media_play" />

创建动画资源 res\drawable\frame.xml

实现逐帧动画的效果 frame\MainActivity.java

package cn.itcast.frame;

import android.graphics.drawable.AnimationDrawable;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.view.View;

import android.widget.Button;

import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener

{

private ImageView iv_wifi;

private Button btn_start;

private AnimationDrawable animation;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

iv_wifi = findViewById(R.id.iv_wifi);

btn_start = findViewById(R.id.btn_play);

btn_start.setOnClickListener(this);

//获取AnimationDrawable对象

animation = (AnimationDrawable) iv_wifi.getBackground();

}

@Override

public void onClick(View v) {

if (!animation.isRunning()) { //如果动画当前没有播放

animation.start();//播放动画

btn_start.setBackgroundResource(android.R.drawable.ic_media_pause);

} else {

animation.stop(); //停止动画

btn_start.setBackgroundResource(android.R.drawable.ic_media_play);

}

}

@Override

protected void onDestroy() {

super.onDestroy();

if(animation.isRunning()){

animation.stop();

}

iv_wifi.clearAnimation();//清空控件的动画

}

}

3.3 属性动画

​ 在Android 3.0之后,Android系统给我们提供了一种全新的动画模式,属性动画(Property Animation),它是一种不断地对属性值进行操作的模式,也就是可以将值赋值到指定对象的指定属性上,该指定属性可以是任意对象的任意属性。通过属性动画我们仍然可以对一个View进行移动、缩放、旋转和透明度渐变等操作,同时也可以对自定义View中的Point(点)对象进行动画操作,在实现这些动画操作时,我们只需要设置动画的运行时长、动画的类型、动画属性的初始值和结束值即可。

​ 属性动画弥补了补间动画的一些缺陷,例如补间动画只能作用在View上,只能对View实现移动、缩放、旋转和透明度渐变动画,只能改变View的位置,不能对View自身进行修改。

1.Animator类

接下来针对属性动画的Animator类、评估程序、插值器、动画监听器进行详细讲解。

类名说明ValueAnimator属性动画的主计时引擎,它也可以计算要添加动画效果的属性值。它具有计算属性值所需要的核心功能,同时包含每个动画的计时详情、有关动画是否重复播放的信息、用于接收更新事件的监听器以及设置待评估自定义类型的功能ObjectAnimatorValueAnimator的子类,用于设置目标对象和对象属性以添加动画效果AnimatorSet此类提供一种将所有动画组合在一起的机制,使这些动画可以一起运行。我们可以将动画设置为一起播放、按顺序播放或者在指定的延迟时间后播放

使用ValueAnimator类添加动画效果

​ 我们可以调用ValueAnimator类中的ofInt()方法、ofFloat()方法或ofObject()方法来获取要添加动画效果的值。以获取float类型的动画效果值为例,示例代码如下。

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f); //获取float类型的动画效果值

animation.setDuration(1000); //设置动画播放时长

animation.start(); //开始播放动画

使用ObjectAnimator类添加动画效果

​ 当实例化ObjectAnimator类时,可以指定需要添加动画的对象与该对象属性的名称,同时还可以指定在哪些值之间添加动画效果。

//获取float类型的动画效果值

//translationX 表示动画的对象相对于最初位置的x轴方向的偏移量

//100f 表示动画的结束值

ObjectAnimator animation = ObjectAnimator.ofFloat(textView,"translationX", 100f);

animation.setDuration(1000); //设置动画播放时长

animation.start(); //开始播放动画

使用AnimatorSet类添加多个动画效果

​ 通常情况下,我们会遇到根据一个动画的开始或结束时间来播放另一个动画。在Android系统中,我们可以将这些需要一起播放的动画存放在AnimatorSet类中,便于指定这些动画是同时播放、按顺序播放、还是在指定的延迟时间后播放,同时我们还可以使用AnimatorSet类播放另一个AnimatorSet类对象中的动画。

2.评估程序

​ 评估程序(类/接口)主要用于告知属性动画系统如何计算指定属性的值。评估程序使用Animator 类提供的计时数据(动画的起始值和结束值)来计算属性添加动画效果后的值。

类名/接口名说明IntEvaluator用于计算int类型的属性值的默认评估程序FloatEvaluator用于计算float类型的属性值的默认评估程序ArgbEvaluator用于计算颜色类型的属性值(用十六进制值表示)的默认评估程序TypeEvaluator此接口用于自定义一个评估程序。如果要添加动画效果的对象属性值不是int类型、float类型或颜色类型,那么必须实现TypeEvaluator接口,才能指定如何计算对象的属性添加动画效果之后的值。

3.插值器

​ 插值器Interpolator(类/接口)指定了如何根据时间计算动画中的特定值。android.view.animation包中包含的插值器如下表所示。

类名/接口名说明AccelerateDecelerateInterpolator该插值器的变化率在开始和结束时缓慢但在中间会加快AccelerateInterpolator该插值器的变化率在开始时较为缓慢,然后会加快AnticipateInterpolator该插值器先反向变化,然后再急速正向变化AnticipateOvershootInterpolator该插值器先反向变化,再急速正向变化,然后超过定位值,最后返回到最终值……

动画监听器

​ 动画监听器(一个接口)主要用于监听动画播放期间的重要事件,动画监听器有Animator.AnimatorListener和ValueAnimator.AnimatorUpdateListener。

(1)Animator.AnimatorListener

​ 动画监听器Animator.AnimatorListener(接口)中有4个方法,如下所示:

onAnimationStart():动画开始播放时调用该方法。 onAnimationEnd():动画结束播放时调用该方法。 onAnimationRepeat():动画重复播放时调用该方法。 onAnimationCancel():动画取消播放时调用该方法,取消动画时也会调用onAnimationEnd()方法。

(2)ValueAnimator.AnimatorUpdateListener

​ 动画监听器ValueAnimator.AnimatorUpdateListener中只有1个方法onAnimationUpdate(),该方法在动画播放的每一帧都会被调用。如果使用ValueAnimator.AnimatorUpdateListener监听某个动画播放的每一帧事件,那么可以调用ValueAnimator类的getAnimatedValue()方法获取动画添加完效果之后生成的值。

​ 如果某个类中使用了ValueAnimator类,那么该类必须实现ValueAnimator.AnimatorUpdateListener监听器。

3.4 实战演练—飞舞的蝴蝶和鸟

​ 本节我们通过一个综合案例飞舞的蝴蝶和鸟来演示如何使用逐帧动画与属性动画,本案例的界面效果如下图所示。

放置界面控件 res\layout\activity_main.xml

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@drawable/bg">

android:id="@+id/iv_bird"

android:layout_width="120dp"

android:layout_height="120dp"

android:layout_marginTop="20dp"

android:background="@drawable/bird_animation" />

android:layout_marginBottom="100dp"

android:layout_alignParentBottom="true"

android:id="@+id/iv_butterfly"

android:layout_width="120dp"

android:layout_height="120dp"

android:layout_marginTop="20dp"

android:background="@drawable/butterfly_animation" />

实现界面效果 butterfliesandbirds\MainActivity.java

package cn.itcast.butterfliesandbirds;

import android.animation.AnimatorSet;

import android.animation.ObjectAnimator;

import android.graphics.drawable.AnimationDrawable;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.util.DisplayMetrics;

import android.view.animation.Animation;

import android.view.animation.LinearInterpolator;

import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

private int screenWidth;

private ImageView iv_butterfly,iv_bird;

private AnimationDrawable animation;

private AnimatorSet flyAnimatorSet;

private ObjectAnimator objectAnimator;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

init();

}

private void init(){

getWindowWidth();

iv_butterfly=findViewById(R.id.iv_butterfly);

iv_bird=findViewById(R.id.iv_bird);

flyAnimation(1); //实现蝴蝶飞舞的效果

flyAnimation(2); //实现小鸟飞舞的效果

}

/**

* 获取屏幕宽度

*/

private void getWindowWidth(){

DisplayMetrics dm=new DisplayMetrics();

getWindowManager().getDefaultDisplay().getMetrics(dm);

screenWidth= dm.widthPixels;

}

/**

* 实现飞舞的效果

*/

private void flyAnimation(int flag){

flyAnimatorSet=new AnimatorSet();

if (flag==1) {

//获取逐帧动画

animation= (AnimationDrawable) iv_butterfly.getBackground();

//设置蝴蝶在水平方向移动的距离为屏幕的宽度-270

// translationX 水平方向移动

objectAnimator = ObjectAnimator.ofFloat(iv_butterfly,

"translationX", screenWidth - 270);

objectAnimator.setDuration(3*1000);//设置动画时间为3秒

}else if (flag==2){

//获取逐帧动画

animation= (AnimationDrawable) iv_bird.getBackground();

//设置小鸟在水平方向移动的距离为屏幕的宽度

objectAnimator = ObjectAnimator.ofFloat(iv_bird,

"translationX", screenWidth);

objectAnimator.setRepeatCount(Animation.RESTART); //重新开始播放动画

objectAnimator.setRepeatCount(Animation.INFINITE);//循环播放动画

objectAnimator.setDuration(10*1000);//设置动画时间为10秒

}

objectAnimator.setInterpolator(new LinearInterpolator());//设置线性插值器

flyAnimatorSet.play(objectAnimator);

animation.start(); //开启逐帧动画

flyAnimatorSet.start();//开启属性动画

}

}

查看原文