布局动画顾名思义就是为一个布局添加动画,之前学习的视图动画只是为一个View添加动画,而如果需要给某个ViewGroup添加动画就需要使用布局动画了,使用了布局动画后在为这个Layout添加或者删除子View对象的时候。
在子View对象移动到新的位置的过程中就会应用指定的动画。
了解了布局动画是干什么后就接下来了解下怎么使用了,和其他的动画一样,它也可以有两种方式,一种是XML一种是Java代码形式。

首先是XML形式的用法:

和视图动画一样布局动画也是存放在/res/anim目录下:

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="30%"
android:animationOrder="reverse"
android:animation="@anim/slide"/>
android:delay 表示动画播放的延时,也就是两个子View 动画之间的间隔,既可以是百分比,也可以是float类型小数。
android:animationOrder表示动画的播放顺序,可以是normal(顺序)、reverse(反序)、random(随机)。
android:animation指向了子控件所要播放的动画。

有了布局动画就可以将layout animation应用到ViewGroup中了,用法很简单就是直接在Target ViewGroup下添加如下配置:

android:layoutAnimation="@anim/anim_layout"

最后还需要添加:

android:animateLayoutChanges="true"

这样就搞定了。

使用Java形式的用法:

简单的用法:

//加载动画
Animation animation = new AnimationUtils().loadAnimation(this, R.anim.layout_anims);
//将该动画设置为布局动画
LayoutAnimationController layoutAnimationController = new LayoutAnimationController(animation);
//设置延时
layoutAnimationController.setDelay(1000);
//设置动画顺序
layoutAnimationController.setOrder(LayoutAnimationController.ORDER_RANDOM);
//将新建的布局动画设置到布局中
gridLayout.setLayoutAnimation(layoutAnimationController);
//添加布局动画监听器
gridLayout.setLayoutAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {

}

@Override
public void onAnimationRepeat(Animation animation) {

}
});

通常情况下,布局动画会在ViewGroup 第一次进行布局的时候执行一次,每次调用addView的时候执行一次,如果要强制执行可以调用scheduleLayoutAnimation()完成。

下面是摘自http://blog.csdn.net/yegongheng/article/details/38455191 的一个例子。
了解下面例子之前我们先来看下从大段代码中抽取出的核心代码—-为布局添加Animator来实现动画的例子:

mLayoutTransition = new LayoutTransition();  
//为GridLayout设置mLayoutTransition对象
mGridLayout.setLayoutTransition(mLayoutTransition);
mLayoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
mLayoutTransition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
//设置每个动画持续的时间
mLayoutTransition.setDuration(300);
//为mLayoutTransition的各个时刻 设置Animator
mLayoutTransition.setAnimator(LayoutTransition.APPEARING, mAnimatorAppearing);
//为动画设置监听器
mAnimatorAppearing.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
super.onAnimationEnd(animation);
View view = (View) ((ObjectAnimator) animation).getTarget();
view.setRotationY(0.0f);
}
});

最重要的是如下的几个时间点的意义:

* APPEARING —— 元素在容器中显现时需要动画显示。 
* CHANGE_APPEARING —— 由于容器中要显现一个新的元素,其它元素的变化需要动画显示。
* DISAPPEARING —— 元素在容器中消失时需要动画显示。
* CHANGE_DISAPPEARING —— 由于容器中某个元素要消失,其它元素的变化需要动画显示。
/** 
* @author AndroidLeaf
* ViewGroup中Layout的动画实现
* 主要知识点:
* 调用 LayoutTransition 对象的 setAnimator() 方法来定义下列动画方式,调用参数是 Animator
* 对象和以下 LayoutTransition 常量:
* (1)APPEARING —— 元素在容器中显现时需要动画显示。
* (2)CHANGE_APPEARING —— 由于容器中要显现一个新的元素,其它元素的变化需要动画显示。
* (3)DISAPPEARING —— 元素在容器中消失时需要动画显示。
* (4)CHANGE_DISAPPEARING —— 由于容器中某个元素要消失,其它元素的变化需要动画显示。
*/
public class LayoutAnimatorFragment extends Fragment implements OnClickListener{

private Button mButtonAdd;
private Button mButtonReset;
private GridLayout mGridLayout;
private int buttonNumbers = 1;

private LayoutTransition mLayoutTransition;

@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View rootView = inflater.inflate(R.layout.fragment_layoutanimator, container, false);
initViews(rootView);
return rootView;
}

public void initViews(View rootView){

mButtonAdd = (Button)rootView.findViewById(R.id.layout_animator_addbutton);
mButtonAdd.setOnClickListener(this);
mButtonReset = (Button)rootView.findViewById(R.id.layout_animator_resetbutton);
mButtonReset.setOnClickListener(this);
mGridLayout = (GridLayout)rootView.findViewById(R.id.layout_animator_gridview);
mLayoutTransition = new LayoutTransition();
//为GridLayout设置mLayoutTransition对象
mGridLayout.setLayoutTransition(mLayoutTransition);
mLayoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
mLayoutTransition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
//设置每个动画持续的时间
mLayoutTransition.setDuration(300);
//初始化自定义的动画效果
customLayoutTransition();
}

@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();

}

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.layout_animator_addbutton:
addButton();
break;
case R.id.layout_animator_resetbutton:
resetButton();
break;
default:
break;
}
}

/**
* 增加Button
*/
public void addButton(){
Button mButton = new Button(getActivity());
LinearLayout.LayoutParams mLayoutParams = new LinearLayout.LayoutParams(50, 50);
mButton.setLayoutParams(mLayoutParams);
mButton.setText(String.valueOf(buttonNumbers++));
mButton.setTextColor(Color.rgb(0, 0, 0));
if(buttonNumbers % 2 == 1){
mButton.setBackgroundColor(Color.rgb(45, 118, 87));
}else{
mButton.setBackgroundColor(Color.rgb(225, 24, 0));
}
mButton.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
mGridLayout.removeView(v);
}
});

mGridLayout.addView(mButton, Math.min(1, mGridLayout.getChildCount()));
}

/**
* 删除所有的Button,重置GridLayout
*/
public void resetButton(){
mGridLayout.removeAllViews();
buttonNumbers = 1;
}

public void customLayoutTransition(){

/**
* Add Button
* LayoutTransition.APPEARING
* 增加一个Button时,设置该Button的动画效果
*/
ObjectAnimator mAnimatorAppearing = ObjectAnimator.ofFloat(null, "rotationY", 90.0f,0.0f)
.setDuration(mLayoutTransition.getDuration(LayoutTransition.APPEARING));
//为LayoutTransition设置动画及动画类型
mLayoutTransition.setAnimator(LayoutTransition.APPEARING, mAnimatorAppearing);
mAnimatorAppearing.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
super.onAnimationEnd(animation);
View view = (View) ((ObjectAnimator) animation).getTarget();
view.setRotationY(0.0f);
}
});

/**
* Add Button
* LayoutTransition.CHANGE_APPEARING
* 当增加一个Button时,设置其他Button的动画效果
*/

PropertyValuesHolder pvhLeft =
PropertyValuesHolder.ofInt("left", 0, 1);
PropertyValuesHolder pvhTop =
PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight =
PropertyValuesHolder.ofInt("right", 0, 1);
PropertyValuesHolder pvhBottom =
PropertyValuesHolder.ofInt("bottom", 0, 1);

PropertyValuesHolder mHolderScaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f,0.0f,1.0f);
PropertyValuesHolder mHolderScaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f,0.0f,1.0f);
ObjectAnimator mObjectAnimatorChangeAppearing = ObjectAnimator.ofPropertyValuesHolder(this, pvhLeft,
pvhTop,pvhRight,pvhBottom,mHolderScaleX,mHolderScaleY).setDuration(mLayoutTransition
.getDuration(LayoutTransition.CHANGE_APPEARING));
mLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, mObjectAnimatorChangeAppearing);
mObjectAnimatorChangeAppearing.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
super.onAnimationEnd(animation);
View view = (View) ((ObjectAnimator) animation).getTarget();
view.setScaleX(1f);
view.setScaleY(1f);
}
});

/**
* Delete Button
* LayoutTransition.DISAPPEARING
* 当删除一个Button时,设置该Button的动画效果
*/
ObjectAnimator mObjectAnimatorDisAppearing = ObjectAnimator.ofFloat(null, "rotationX", 0.0f,90.0f)
.setDuration(mLayoutTransition.getDuration(LayoutTransition.DISAPPEARING));
mLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, mObjectAnimatorDisAppearing);
mObjectAnimatorDisAppearing.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
super.onAnimationEnd(animation);
View view = (View) ((ObjectAnimator) animation).getTarget();
view.setRotationX(0.0f);
}
});

/**
* Delete Button
* LayoutTransition.CHANGE_DISAPPEARING
* 当删除一个Button时,设置其它Button的动画效果
*/
//Keyframe 对象中包含了一个时间/属性值的键值对,用于定义某个时刻的动画状态。
Keyframe mKeyframeStart = Keyframe.ofFloat(0.0f, 0.0f);
Keyframe mKeyframeMiddle = Keyframe.ofFloat(0.5f, 180.0f);
Keyframe mKeyframeEndBefore = Keyframe.ofFloat(0.999f, 360.0f);
Keyframe mKeyframeEnd = Keyframe.ofFloat(1.0f, 0.0f);

PropertyValuesHolder mPropertyValuesHolder = PropertyValuesHolder.ofKeyframe("rotation",
mKeyframeStart,mKeyframeMiddle,mKeyframeEndBefore,mKeyframeEnd);
ObjectAnimator mObjectAnimatorChangeDisAppearing = ObjectAnimator.ofPropertyValuesHolder(this, pvhLeft,pvhTop,pvhRight,pvhBottom,mPropertyValuesHolder)
.setDuration(mLayoutTransition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
mLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, mObjectAnimatorChangeDisAppearing);
mObjectAnimatorChangeDisAppearing.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
super.onAnimationEnd(animation);
View view = (View) ((ObjectAnimator) animation).getTarget();
view.setRotation(0.0f);
}
});
}
}

存放路径:res/animator

一个属性动画在某一个时间段,只能改变一个对象的一个属性值,通过多个属性动画组合到AnimationSet就可以将多个属性同时形成一个动画。

属性动画的属性

属性动画有如下的属性:

  • Duration:动画的时长信息
  • TimeInterpolation: 所有插值器都必须实现此接口,用于计算变化率的信息
  • TypeEvaluator: 根据属性的起始、结束值和插值一起计算出当前时间的属性值
  • Frame refreash delay: 即每隔多少时间计算1次属性值,默认情况下为10ms。
  • Repeat Country and behavoir:重复次数与方式。
  • Animation sets: 动画集合,便可以同时对1个对象利用多个动画,这些动画可以同时播放也能对不同动画设置不同的延迟

属性动画属性确定过程

整个属性动画的属性值确定过程如下所示:

在属性动画开始的时候会先获取目标对象属性的开始值,结束值,以及动画的持续时间,将这些值传入ValueAnimator,根据当前时间计算出已完成动画的百分比,
但是我们这里的动画不一定是匀速的,它会受到时间插值器的影响,因此还需要将百分比数值传入给TimeInterpolator,计算interpolated(插值)分数当插值分数计算完成后,ValueAnimator 会根据插值分数调用适合的 TypeEvaluator去计算运动中的属性值。整个过程也可以通过下图来理解。

重要类的介绍:

ValueAnimator

用法

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); 
animation.setDuration(1000);
animation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("jimmy", ((Float) animation.getAnimatedValue()).toString());
}
});
animation.setInterpolator(new CycleInterpolator(3));
animation.start();

关键子类:ObjectAnimator
继承自ValueAnimator,允许你指定要进行动画的对象和该对象的一个属性。该类会根据计算得到的新值自动更新属性。大多数的情况,使用ObjectAnimator就足够了,由于它使得目标对象动画值的处理进程变得简单,它具有时序引擎和属性值计算和自动更新属性值的功能,不用再像ValueAnimator那样自己写动画更新的逻辑。但ObjectAnimator有一定的限制:
该对象的属性必须有get和set方法,并且方法的格式必须是驼峰式,格式为set(),由于ObjectAnimator会自动更新属性,它必须能够访问到属性的setter方法,比如属性名为pro,你就需要一个setPro()方法。
如果没有那么可以使用下面的三种方法:

  • 添加setter方法
  • 使用包装类
  • 使用ValueAnimator代替
    但是一般第一种方法往往无效,因为很少去直接修改某个类的代码

ObjectAnimator 的用法:我们通常使用ObjectAnimator设置View已知的属性来生成动画,而一般View已知属性变化时都会主动触发重绘图操作,所以动画会自动实现,这种情况下用法如下:

ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f); 
anim.setDuration(1000);
anim.start();

但是也可以手动完成属性的改变:

ObjectAnimator myObjectAnimator= ObjectAnimator.ofInt(view, "custome", 0,1).setDuration(2000);
mObjectAnimator.addUpdateListener(new AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator animation){
//int value = animation.getAnimatedValue();获取当前属性值
//view.setXXX(value);设置属性值
//view.postInvalidate();主动刷新
}
});
Evaluators

Evaluators 告知属性动画系统如何去计算一个属性值。
属性系统提供了以下几种Evaluators:

  • IntEvaluator
  • FloatEvaluator
  • ArgbEvaluator
  • TypeEvaluator 一个用于用户自定义计算器的接口,如果对象属性值类型,不是int,float,或color类型,你必须实现这个接口,去定义自己的数据类型。
XML方式使用属性动画

下面是摘自Google官方文档:

<set
android:ordering=["together" | "sequentially"]>
<!--android:ordering 控制子动画启动方式是先后有序的还是同时进行。sequentially:动画按照先后顺序;together(默认):动画同时启动;-->
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>

</set>

objectAnimator 属性:

android:propertyName 代表要执行动画的属性,这个必须填写
android:valueTo floatint或者color类型,表明动画结束的点;如果是颜色的话由6位十六进制的数字表示。
android:valueFrom 动画的起始点,如果没有指定,系统会通过属性的get方法获取,颜色也是6位十六进制的数字表示。
android:duration 动画的时长,int类型,以毫秒为单位,默认为300毫秒。
android:startOffset 动画延迟的时间,从调用start方法后开始计算,int型,毫秒为单位。
android:repeatCount 一个动画的重复次数,int型,”-1“表示无限循环,”1“表示动画在第一次执行完成后重复执行一次,也就是两次,默认为0,不重复执行。
android:repeatMode 重复模式:int型,当一个动画执行完的时候应该如何处理。该值必须是正数或者是-1,“reverse”会使得按照动画向相反的方向执行,可实现类似钟摆效果。“repeat”会使得动画每次都从头开始循环。
android:valueType 关键参数,如果该value是一个颜色,那么就不需要指定,因为动画框架会自动的处理颜色值。有intType和floatType(默认)两种:分别说明动画值为intfloat型。
Animator animator = AnimatorInflater.loadAnimator(myContext,R.animtor.property_anim);
animator.setTarget(Obj);
animator.start();

多属性动画:

有时候我们需要同时修改多个属性,那就可以用到PropertyValuesHolder类,它就相当于AnimationSet

PropertyValuesHolder anim1 = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);  
PropertyValuesHolder anim2 = PropertyValuesHolder.ofFloat("translationY", 0, 100);
ObjectAnimator.ofPropertyValuesHolder(view, anim1, anim2, ......).setDuration(1000).start();

存放路径:res/drawable

用法:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot=["true" | "false"] >
<item
android:drawable="@[package:]drawable/drawable_resource_name"
android:duration="integer" />
</animation-list>

android:oneshot true代表只执行一次,false循环执行。
item节点表示 一帧的动画资源。
item节点包含如下属性:
android:drawable frame的Drawable资源。
android:duration frame显示多长时间。

ImageView Image = (ImageView) findViewById(R.id.image);
Image.setBackgroundResource(R.drawable.animations);
drawableAnimation = (AnimationDrawable) Image.getBackground();
drawableAnimation.start();

需要注意的是AnimationDrawable的start()方法不能在Activity的onCreate方法中调用,因为AnimationDrawable还未完全附着到window上,最好的调度时机是onWindowFocusChanged()方法中。

存放路径:res/anim

类型:

Alpha 透明度动画,可以产生淡入淡出的效果 对应的类:AlphaAnimation
Scale 大小缩放动画 对应的类:ScaleAnimation
Translate 移动动画 对应的类:TranslateAnimation
Rotate 旋转变化 对应的类:RotateAnimation

动画集合

可以使用set标签创建一个动画集合,set还有如下属性用于控播放:
duration: 动画的持续时间
startOffset: 动画起始偏移,如果没有指定这个属性,动画将会同时播放。
fillBefore :在动画开始之前是否应用动画变形
fillAfter: 在动画开始之后是否应用动画变形
interpolator: 动画插值器

使用方式:

Animation animation = AnimationUtils.loadAnimation(this, R.anim.animation_demo);  
animation.setRepeatCount(Animation.INFINITE);  
imageView.startAnimation(animation);

动画属性

接下来我们来看下这些属性具体有哪些属性:

  • Animation属性:
xml属性 java方法 解释
android:detachWallpaper setDetachWallpaper(boolean) 是否在壁纸上运行
android:duration setDuration(long) 动画持续时间,毫秒为单位
android:fillAfter setFillAfter(boolean) 控件动画结束时是否保持动画最后的状态
android:fillBefore setFillBefore(boolean) 控件动画结束时是否还原到开始动画前的状态
android:fillEnabled setFillEnabled(boolean) 控件动画结束时是否还原到开始动画前的状态
android:interpolator setInterpolator(Interpolator) 设定插值器
android:repeatCount setRepeatCount(int) 重复次数
android:repeatMode setRepeatMode(int) 重复类型
android:startOffset setStartOffset(long) 调用start函数之后等待开始运行的时间,单位为毫秒
android:zAdjustment setZAdjustment(int) 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal
方法 解释
reset() 重置Animation的初始化
cancel() 取消Animation动画
start() 开始Animation动画
setAnimationListener(AnimationListener listener) 给当前Animation设置动画监听
hasStarted() 判断当前Animation是否开始
hasEnded() 判断当前Animation是否结束

我们知道Animation是上述所有动画的属性,因此上述的每个动画都有这些属性. 下面我们来看看每个动画都有哪些特定的属性:

  • Alpha属性:
xml属性 java方法 解释
android:fromAlpha AlphaAnimation(float fromAlpha, …) 动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明)
android:toAlpha AlphaAnimation(…, float toAlpha) 动画结束的透明度
  • Rotate属性:
xml属性 java方法 解释
android:fromDegrees RotateAnimation(float fromDegrees, …) 旋转开始角度,正代表顺时针度数,负代表逆时针度数
android:toDegrees RotateAnimation(…, float toDegrees, …) 旋转结束角度,正代表顺时针度数,负代表逆时针度数
android:pivotX RotateAnimation(…, float pivotX, …) 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY RotateAnimation(…, float pivotY) 缩放起点Y坐标,同上规律
  • Scale属性
xml属性 java方法 解释
android:fromXScale ScaleAnimation(float fromX, …) 初始X轴缩放比例,1.0表示无变化
android:toXScale ScaleAnimation(…, float toX, …) 结束X轴缩放比例
android:fromYScale ScaleAnimation(…, float fromY, …) 初始Y轴缩放比例
android:toYScale ScaleAnimation(…, float toY, …) 结束Y轴缩放比例
android:pivotX ScaleAnimation(…, float pivotX, …) 缩放起点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:pivotY ScaleAnimation(…, float pivotY) 缩放起点Y轴坐标,同上规律
  • Translate属性
xml属性 java方法 解释
android:fromXDelta TranslateAnimation(float fromXDelta, …) 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点)
android:fromYDelta TranslateAnimation(…, float fromYDelta, …) 起始点Y轴从标,同上规律
android:toXDelta TranslateAnimation(…, float toXDelta, …) 结束点X轴坐标,同上规律
android:toYDelta TranslateAnimation(…, float toYDelta) 结束点Y轴坐标,同上规律

由于这些年Android 更新很快,每年的Google IO都会引入很多特性,在兴奋于这些特性之余也常常需要忍受被墙之苦,但是好在很多好心人士,提供了SDK的镜像,之前一直在AndroidDevTool上下载更新,但是最近发现上面的更新速度也逐渐跟不上脚步了,所以下面推荐一个Android SDK 的镜像更新方式:
该镜像是由南阳理工提供:

手动更改配置文件

Utopic Unicorn (ubuntu 14.10) 编辑 /etc/apt/sources.list 文件 (需要使用 sudo), 修改文件内容为加以下条目(操作前请做好相应备份)

deb http://mirror.nyist.edu.cn/ubuntu/ utopic main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-security main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-updates main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-proposed main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-backports main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-security main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-updates main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-proposed main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-backports main restricted universe multiverse

Ubuntu14.04 LTS 配置文件编辑 /etc/apt/sources.list 文件 (需要使用 sudo), 修改文件内容为加以下条目(操作前请做好相应备份)

deb http://mirror.nyist.edu.cn/ubuntu/ trusty main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ trusty-security main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ trusty-updates main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ trusty-proposed main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ trusty-backports main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ trusty main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ trusty-security main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ trusty-updates main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ trusty-proposed main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ trusty-backports main restricted universe multiverse

Ubuntu14.10

deb http://mirror.nyist.edu.cn/ubuntu/ utopic main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-security main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-updates main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-proposed main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ utopic-backports main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-security main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-updates main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-proposed main restricted universe multiverse
deb-src http://mirror.nyist.edu.cn/ubuntu/ utopic-backports main restricted universe multiverse

Ubuntu13.10

deb http://mirror.nyist.edu.cn/ubuntu/ saucy main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ saucy-security main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ saucy-updates main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ saucy-proposed main restricted universe multiverse
deb http://mirror.nyist.edu.cn/ubuntu/ saucy-ba使用说明

配置步骤
  1. 启动 Android SDK Manager ,打开主界面,依次选择「Tools」、「Options…」,弹出『Android SDK Manager - Settings』窗口;

  2. 在『Android SDK Manager - Settings』窗口中,在「HTTP Proxy Server」和「HTTP Proxy Port」输入框内填入mirror.nyist.edu.cn和80,并且选中「Force https://… sources to be fetched using http://…」复选框。设置完成后单击「Close」按钮关闭『Android SDK Manager - Settings』窗口返回到主界面;

  3. 依次选择「Packages」、「Reload」。

  4. 完成

由于网络关系有的时候会下载不成功,这时候被别放弃,^ V ^ 多试几次。

滑动原理

实现View的滑动如下图所示:
图上通过滑动从将圆形从黄色地方移到灰色地方,从图上很明显看出,要实现这种移动只需要计算出滑动的偏移量,然后通过原始坐标就可以计算出最终坐标的位置,因此我们需要获得如下信息:

  • 第一次相对于View的坐标
  • 再次按下的时候相对于View的坐标
  • 根据上面两个坐标即可计算出两次的偏移量,有了偏移量就可以移动View了,不断重复上述步骤就实现了滑动过程。

大体的结构如下,接下来我们的重点将放在如何实现processTouchEvent这个方法来实现滑动。

private float originX = -1;
private float originY = -1;
private void processTouchEvent(float offsetX,float offsetY) {

}
@Override
public boolean onTouchEvent(MotionEvent event) {
float destX = event.getX();
float destY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
originX = destX;
originY = destY;
break;
case MotionEvent.ACTION_MOVE:
float offsetX = destX - originX;
float offsetY = destY - originY;
processTouchEvent(offsetX,offsetY);
break;
default:
break;
}
return true;
}

滑动的实现

使用layout方法

在绘制View的时候,会调用layout方法并传入left,top,right,bottom来移动View。

private void processTouchEvent(Context context,float offsetX,float offsetY) {
layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
}

使用LayoutParams

这种适用于要移动的View位于一个Layout中的情形,因为移动一个View通常是通过修改它在Parent View 中的margin来实现的。所以可以通过这种方式来实现:

private void processTouchEvent(float offsetX,float offsetY) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = (int) (getLeft() + offsetX);
layoutParams.topMargin = (int) (getTop() + offsetY);
setLayoutParams(layoutParams);
}

使用ScrollTo和ScrollBy

在使用这两个方法移动视图的时候,移动的不是View或者ViewGroup本身,而是View 或者ViewGroup的包含的内容,比如TextView 使用这两种方式移动的时候移动的将是TextView的文本,
而一个Layout移动的时候移动的将是里面的子View,那么我们怎样移动一个View本身,而不是仅仅其内容呢?很简单换位思考下,站在要移动View的父类的角度,它不就是父类的内容吗?
有了这个思路就好办了。

private void processTouchEvent(float offsetX,float offsetY) {
((View)getParent()).scrollBy((int)offsetX,(int)offsetY);
}

但是这里我们需要时时刻刻让自己记住,这里移动的是父类的位移,而子View的移动偏移实际上是和父View移动位移的相反数。因此要达到我们的移动目标必须将上面的偏移量取相反数。

private void processTouchEvent(float offsetX,float offsetY) {
((View)getParent()).scrollBy(-(int)offsetX,-(int)offsetY);
}

使用Scroller

Scroller存在的意义就是为了避免ScrollTo和ScrollBy带来的瞬间移动的效果,它的移动效果会更加平滑。

  • 创建Scroller
    mScroller = new Scroller(context);
  • 覆写computeScroll方法
    computeScroll 方法是什么时候执行的呢?我们知道在调用invalidate会调用onDraw来重绘当前的页面,在onDraw方法中会调用computeScroll
    下面是一个典型的实现,首先调用computeScrollOffset来判断时候完成整个滑动,如果返回true,则表示滑动过程还没结束。我们需要继续滑动
    @Override
    public void computeScroll() {
    super.computeScroll();
    //If it returns true, the animation is not yet finished.
    if(mScroller.computeScrollOffset()) {
    ((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
    invalidate();
    }
    }
  • 调用startScroll启动滑动过程:
    case MotionEvent.ACTION_UP:
    mScroller.startScroll(xpos,ypos,((View)getParent()).getScrollX(),((View)getParent()).getScrollY());
    invalidate();

由于这种方法十分重要所以在这里将对Scroller常用的方法做个总结:

mScroller.getCurrX() //获取mScroller当前水平滚动的位置
mScroller.getCurrY() //获取mScroller当前竖直滚动的位置
mScroller.getFinalX() //获取mScroller最终停止的水平位置
mScroller.getFinalY() //获取mScroller最终停止的竖直位置
mScroller.setFinalX(int newX) //设置mScroller最终停留的水平位置,没有动画效果,直接跳到目标位置
mScroller.setFinalY(int newY) //设置mScroller最终停留的竖直位置,没有动画效果,直接跳到目标位置
//开启滚动,startX, startY为开始滚动的位置,dx,dy为滚动的偏移量, duration为完成滚动的时间
mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默认完成时间250ms
mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)
第一个参数是起始移动的x坐标值, 正值表明滚动将向左滚动
第二个是起始移动的y坐标值, 正值表明滚动将向上滚动
dx 水平方向滑动的距离,正值会使滚动向左滚动
dy 垂直方向滑动的距离,正值会使滚动向上滚动
mScroller.computeScrollOffset() //返回值为boolean,true说明滚动尚未完成,false说明滚动已经完成。这是一个很重要的方法,通常放在View.computeScroll()中,用来判断是否滚动是否结束。

MotionEvent

我们在Android进阶之绘图中介绍了如何在界面上进行绘制的部分,但是这仅仅是静态的画面,它属于输出部分,但是只有输出往往是不够的,还需要通过某种方式接收用户的输入,这才能构成交互。
在我们解除屏幕到离开屏幕会产生一系列触摸事件,要响应这些事件必须复写onTouchEvent(MotionEvent e):
这里传入的参数MotionEvent是一个很重要的参数,它包含了当前触摸点的坐标,以及触摸事件的类型:
当用户触摸屏的时候,就会触发onTouchEvent事件处理函数,每当位置发生改变的时候触发一次,当触摸结束的时候还会触发一次。
它有一个返回值如果当前的onTouchEvent已经处理了屏幕触摸,就返回true,否则,就返回false。从而通过View或者Activity堆栈来传递事件,直到成功处理触摸为止。这部分涉及到事件的传递和分发机制
在后面会详细介绍,我们先来认识下MotionEvent:

通过event.getAction() 可以获取事件的类型:
MotionEvent.ACTION_UP 触摸结束,ACTION_UP
MotionEvent.ACTION_DOWN 开始触摸,第一个手指头按下的时候触发
MotionEvent.ACTION_POINT_DOWN 其他手指再按下的时候触发
MotionEvent.ACTION_POINT_UP 其他手指抬起来的时候触发
MotionEvent.ACTION_MOVE 按下手指在屏幕上移动的时候
MotionEvent.ACTION_CANCEL 触摸事件取消的时候
MotionEvent.ACTION_OUTSIDE 移动操作发生在被监控的屏幕元素边界之外的时候

通过event.getX() event.getY() event.getRawX() event.getRawY() 可以分别获得当前触点距离父容器以及屏幕的坐标位置;这个可以在绘图介绍中的坐标体系中看到每个方法的返回值意义

还可以通过下面的方法获取到事件开始和结束的时间信息:
getDownTime() 事件开始时间
getEventTime() 事件结束时间

多点触控事件:

现在的电容屏一般都支持多点触控技术,判断当前触摸事件为单点触控还是多点触控可以使用getPointerCount()方法,如果大于1表示为多点触控。
多点触控每个点称为一个pointer,MotionEvent包含该时刻所有point的信息。每个point都有唯一的ID,这个ID是在该point刚触碰屏幕的时候赋值的。有效期至该点离开屏幕或者取消。
我们要获得每个点的信息,必须通过它的index,而要获得index必须使用ACTION_POINTER_ID_MASK从Action中获得Action Point id

多点触控的处理:

int xpos = -1;
int ypos = -1;
int action = event.getAction();
if(event.getPointerCount() >1) {
int actionPointerid = action & MotionEvent.ACTION_POINTER_ID_MASK;
int actionEvent = action & MotionEvent.ACTION_MASK;
int pointerIndex = event.findPointerIndex(actionPointerid);
xPos = (int)event.getX(pointerIndex);
yPos = (int)event.getY(pointerIndex);
}else {
xPos = (int)event.getX();
yPos = (int)event.getY();
}

跟踪触摸的移动:

每当当前的触摸接触位置,压力或者区域大小发生改变的时候,将会触发ACTION_MOVE。MotionEvent中包含有着这系列变化的历史值。可以使用如下方式获取历史值:

单点触控:
int historySize = event.getHistorySize();//返回当前事件可用的移动位置的数量
for(int i=0;i<historySize;i++){
float x = event.getHistoricalX(i);
float y = event.getHistoricalY(i);
}

多点触控:
int historySize = event.getHistorySize();//返回当前事件可用的移动位置的数量
for(int i=0;i<historySize;i++){
int id = event.getPointerId(i);
float x = event.getHistoricalX(id, i);
float y = event.getHistoricalY(id, i);
}

处理触摸屏移动事件:
处理移动事件一般首先处理每个历史事件,然后再处理当前的MotionEvent值

switch (action) {
case MotionEvent.ACTION_MOVE:
int historySize = event.getHistorySize();
for(int i=0;i<historySize;i++){
float x = event.getHistoricalX(i);
float y = event.getHistoricalY(i);
Log.i("MainActivity","历史坐标值:("+x+"),("+y+")");
}

float currentx = event.getX();
float currenty = event.getY();
Log.i("MainActivity","当前值:("+currentx+"),("+currenty+")");
Log.i("MainActivity", "ACTION_MOVE");
return true;
default:return super.onTouchEvent(event);
}

为控件添加触摸事件监听器:

可以使用setOnTouchLinstener方法为某个View对象监听触摸事件。

键盘事件:

除了上述的显示屏事件外Android中还提供了键盘事件的处理:
所有的硬件按键产生的事件都是由处于活动状态的Activity或者当前前台View进行处理。Android中控件在处理物理按键事件时,提供的回调方法有onKeyUp() ,onKeyDown(),onKeyLongPress()
要想让Activity或者View对按键的按下做出响应,需要重写onKeyDown和onKeyUp事件处理方法:
方法中的keyCode包含了被按下的键的值,把它和KeyEvent类中的静态的Keycode进行比较,就可以执行特定按键的处理。

要在Activity的View中对按键按下做出响应,需要实现OnKeyListener并使用setOnKeyListener方法将其分配给一个View。
OnKeyListener并不是为了单独的按键按下和释放分别实现独立的方法,而是使用单一的onKey事件。ACTION_DOWN表示按下按键,ACTION_UP表示释放按键

事件分发和事件拦截机制

上面所讲述的情况是相对理想的情况,也就是事件已经传送到当前的View的情形,但是一般一个View 往往都是处在某个ViewGroup中,这就引入了一个新的问题,事件是如何传递分发和拦截的。
我们先看下下面这张图:
绿色的代表一个View,它被layout在两个ViewGoup内,当一个事件发生的时候,首先从底层的父ViewGroup开始传输的,在介绍整个传输过程之前先介绍下面两个方法:

  • onInterceptTouchEvent()是用于事件的预处理并改变事件的传递方向,也就是决定是否允许Touch事件继续子控件传递,如果返回True表示事件在当前的viewGroup中会被处理,
    则向下传递被截断,所有子控件将没有机会参与Touch事件,同时把事件传递给当前的控件的onTouchEvent()处理;返回false,则把事件交给子控件的onInterceptTouchEvent()
    onInterceptTouchEvent()是ViewGroup的一个方法,目的是在系统向该ViewGroup及其各个childView触发onTouchEvent()之前对相关事件进行一次拦截.不能包含子view的控件是没有这个方法的.

  • onTouchEvent()这个上面介绍了用于处理事件,返回值决定当前控件是否消费了这个事件,也就是说在当前控件在处理完Touch事件后,是否还允许Touch事件继续向父控件传递,一但返回True,
    则父控件不用操心自己来处理Touch事件。返回false,则向上传递给父控件.

如果layoutview1,layoutview2 onInterceptTouchEvent都返回false,也就是不拦截,那么整个事件的传递如下图所示:

如果layoutview1 onInterceptTouchEvent 返回false ,layoutview2 onInterceptTouchEvent都返回true,那么整个事件的传递如下图所示:

如果layoutview1,layoutview2 onInterceptTouchEvent都返回false,也就是不拦截,childview onTouchEvent 返回true 那么整个事件的传递如下图所示:

绘图与硬件加速:

在Android 3.0后,Android的2D渲染通道开始支持硬件加速,意味着所有View的Canvas绘画动作都会使用GPU启用硬加速,使用GPU的特性,可以使得界面渲染更加平滑,在4.0之后更是默认开启了硬件加速,
但是硬加速不是被所有的2D绘制所支持,并且会消耗更多内存RAM,并且由于硬件加速属于双缓冲机制,使用显存进行页面渲染,这导致了更频繁的显存操作,可能引起白屏、花屏、闪屏的现象。甚至在低RAM内存配置手机上闪退。
一般可以通过如下两种方法解决这类问题。
1.降低页面的内存占用,给硬件加速腾出RAM
2.在适当的地方关闭硬件加速。
前者操作起来比较麻烦,因此在可能的情况下最优先选择的是关闭硬件加速。

不过不支持仅仅是针对自定义View Android可以保证内置的组件和应用支持硬件加速,因此,如果应用中只使用了标准UI组件,可以放心开启硬件加速。
下面介绍下如何在各个层级中关闭硬件加速:

  1. Application 应用程序等级控制硬件加速,在AndroidManifest.xml中添加:
<application android:hardwareAccelerated="true" ...>  
  1. Activity Activity等级的控制还是在AndroidManifest.xml中进行控制:
<application android:hardwareAccelerated="true">  
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
  1. Window 窗口级控制:

    getWindow().setFlags(  
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  2. View 在运行时控制单个View是否使用硬件加速:

setLayerType(View.LAYER_TYPE_SOFTWARE, null);  LAYER_TYPE_HARDWARE即为使用硬件加速(GPU),LAYER_TYPE_SOFTWARE使用CPU进行绘制。

检测当前View或者Canvas是否使用硬件加速
View.isHardwareAccelerated()
Canvas.isHardwareAccelerated()

Drawable资源

  • Color Drawable
<?xml version="1.0" encoding="utf-8"?>
<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="#563459" />
  • Shape Drawable

每个Shape Drawable都包含如下属性:
类型:通过shape属性来指定:

Line 一条横跨了父类View的宽度的水平线。
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line" >
<stroke android:width="2dp" android:color="#ff0000" />
</shape>
rectangle 矩形
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<stroke android:width="2dp" android:color="#ff0000" />
</shape>

oval 圆弧
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<stroke android:width="2dp" android:color="#ff0000" />
</shape>

ring 圆环
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadius="100dp"
android:thickness="10dp"
>
<stroke android:width="2dp" android:color="#ff0000" />
</shape>


使用stroke可以指定:轮廓的颜色,线宽,虚线长度,虚线间隔
<stroke android:width="2dp" android:color="#ff0000" android:dashWidth="10dp" android:dashGap="5dp"/>

填充颜色:
<solid android:color="#324390"/>

边角
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="20dp" />
内边距
<padding android:left="35dp"
android:right="35dp"
android:bottom="35dp"
android:top="35dp"/>

渐变效果
线性渐变:
<gradient
android:angle="45"
android:centerColor="#ffffff"
android:centerX="60dp"
android:centerY="100dp"
android:endColor="#000000"
android:startColor="#000000"
android:type="linear"
android:useLevel="false" />


辐射渐变
<gradient
android:centerColor="#ffffff"
android:endColor="#FF0000"
android:startColor="#000000"
android:type="radial"
android:useLevel="false"
android:gradientRadius="300"/>


扫描渐变
<gradient
android:centerColor="#ffffff"
android:endColor="#000000"
android:startColor="#000000"
android:type="sweep"
android:useLevel="false"
android:gradientRadius="100" />


变换Drawable–ScaleDrawable

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_launcher"
//缩放中心
android:scaleGravity="center_horizontal|clip_vertical“
//相对于原始Drawable的包围框的目标宽度和高度
android:scaleHeight="100%"
android:scaleWidth="100%" />
在部件文件中使用scaleDrawable
<ImageView
android:id="@+id/imagescale"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/scale" />
在Activity中添加:
mImageScaleIv = (ImageView) findViewById(R.id.imagescale);
mImageScaleIv.setImageLevel(5000);
//Level值在0-10000之间,5000表示50%

变换Drawable–RotateDrawable

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_launcher"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="180" />

在部件文件中使用rotate Drawable
<ImageView
android:id="@+id/imagerotate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/rotate" />
在Activity中添加:
mImageRotateIv = (ImageView) findViewById(R.id.imagerotate);
mImageRotateIv.setImageLevel(5000);
//Level值在0-10000之间,5000表示50%

LayerDrawable

LayerDrawable允许在Drawable资源之上组合多个Drawable资源,如果定义一个半透明的Drawable数组,那么可以将他们彼此堆叠起来,以创建动态形状和变化的复杂组合,LayerDrawable的定义如下所示,其中第一个定义的位于对下方:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable=“@drawable/image1"/>
<item android:drawable="@drawable/image2“/>
<item android:drawable="@drawable/image3“/>
</item>
</layer-list>

在布局中使用LayerDrawable
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/layerdrawable"
/>

状态列表Drawable

允许根据包含该状态列表的View的状态指定一个不同的Drawable。
状态有如下几种:
State_pressed:按下或没有按下
State_focus:有焦点或者没有焦点
State_hovered:光标在View上悬停或者不悬停
State_selected:选中或者没选中
State_checkable能或者不能被选中
State_checked:被选中或者没有被选中
State_enabled:启用或者禁止
State_activited:激活或者未激活
State_window_focus:父窗口有没有焦点
每个状态可以设置为true或者false。对于一个给定的View确定该显示哪个Drawable时,Android会使用和当前状态匹配的状态列表中的第一项。因此默认值应该放在最后。

<selector>
<item />
</selector>

级别Drawable

使用级别列表可以创建Drawable的资源序列,并给每一层指定一个整形的索引值,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@drawable/nw0" android:maxLevel="0"/>
<item android:drawable="@drawable/nw1" android:maxLevel="1"/>
<item android:drawable="@drawable/nw2" android:maxLevel="2"/>
<item android:drawable="@drawable/nw3" android:maxLevel="3"/>
<item android:drawable="@drawable/nw4" android:maxLevel="4"/>
<item android:drawable="@drawable/nw5" android:maxLevel="5"/>
<item android:drawable="@drawable/nw6" android:maxLevel="6"/>
<item android:drawable="@drawable/nw7" android:maxLevel="7"/>
<item android:drawable="@drawable/nw8" android:maxLevel="8"/>
<item android:drawable="@drawable/nw9" android:maxLevel="9"/>
</level-list>

在代码中选择要显示的图像:可以使用如下所示

public void onClick(View v) {
mCount=(mCount+1)%9;
mImageIV.setImageLevel(mCount);
}

Bitmap && BitmapFactory

decodeFile(stringpath) 用于从给定的路径中解析Bitmap
decodeFileDescriptor(FileDescriptor fd) 用于从FileDescriptor对应的文件中解析
decodeRescource(Resouce res,int id) 用于根据给定的资源ID从指定的资源中解析
decodeStream(InputStream is) 用于从指定的输入流中解析

compress(Bitmap.CompressFormat format,int quality,Outputstream stream)
用于将Bitmap对象压缩为指定格式并保留到指定的文件输出流中,其中format参考值可以是
Bitmap.compressFormat.PNG,Bitmap.compressFormat.JPEG,.compressFormat.WEBP

createBitmap(Bitmap source) 使用位图对象创建新的位图对象
createBitmap(Bitmap source,int x,int y,int width,int height); 用于从源位图的指定坐标点开始,挖取指定宽度和高度的一块图像来创建新的Bitmap对象
createBitmap(Bitmap source,int x,int y,int with,int height,Matrix matrix,boolean filter) 用于从源位图的指定坐标点,“挖取”指定宽度和高度的一块图像来创建新的Bitmap对象,并按Matrix指定规则进行变换
createBitmap(int width,int height,Bitmap.Config config) 用于创建一个指定宽度高度的新的Bitmap对象
createScaledBitmap(Bitmap source,int dstwidth,int dstheight,,boolean filter) 用于将源位图缩放为指定宽度和高度的新的Bitmap对象
createBitmap(int colors[],int width,int height,Bitmap.Config) 用指定的颜色数组,创建一个指定宽度和高度的Bitmap对象,数组元素的个数为width*height
isRecycled() 用于判断图片是否被回收
recycle() 回收图片

Bitmap 和 Drawable之间的转换

Drawable转化为Bitmap

public static Bitmap drawableToBitmap(Drawable drawable) {    
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
drawable.setBounds(0, 0, width, height);

Bitmap bitmap = Bitmap.createBitmap(width, height, drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);

drawable.draw(canvas);
return bitmap;
}

private void drawableToBitamp(Drawable drawable){
BitmapDrawable bd = (BitmapDrawable) drawable;
bitmap = bd.getBitmap();
}

Bitmap 转换为 Drawable

public static Drawable bitmapToDrawble(Bitmap bitmap,Context mcontext){  
Drawable drawable = new BitmapDrawable(mcontext.getResources(), bitmap);
return drawable;
}

Matrix图像变换

我们在介绍Paint的时候接触到了ColorMatrix,它是一个颜色矩阵用于颜色变换的,这里需要介绍的是图像变换矩阵:
图像变换常见的有如下几种:
1.平移变化 matrix.setTranslate
2.缩放变化 matrix.setScale
3.旋转变化 matrix.setRotate
4.倾斜变化 matrix.setSkew
运用上述的方式会重置所有的值,只设置当前的变化,所以不会变化不会累加。
如果需要累加变化则需要使用postXXXX和preXXXX
postXXXX 是会在前面的变化结束后叠加post的动画
而preXXXX是会在当前的变化前叠加pre的动画

如果上述的还不能满足我们的要求,我们还可以直接设置矩阵值:

float[] matrixArray = new float[9];
Matrix matrix = new Matrix();
matrix.setValues(matrixArray);

同样我们在使用上述的几种便捷方式设置好变换矩阵后还可以通过下面的方式获得矩阵的值:

float[] matrixValues = new float[9];  
matrix.getValues(matrixValues);

那么如何应用到实际的图像中呢?
还记得前面介绍Canvas.drawBitmap把这里就有一个需要Matrix,还有一个是在创建Bitmap的时候,这里就不写了,大家可以看看前面的内容。