Android客户端 | 青训营笔记
这是我参与「第四届青训营 -Android场」笔记创作活动的的第5天
动画
帧动画
帧动画,从字面意思来理解,帧:就是影像动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。 一帧就是一副静止的画面,连续的帧就形成动画,如电视图象等。简单点说就是类似幻灯片播放的那种效果,因此帧动画的本质就是将一张张的图片,通过代码对这些图片进行连续的活动(这样就形成了动画)
帧动画示例
<?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/ic_wifi_0" android:duration="100"/>
<item android:drawable="@drawable/ic_wifi_1" android:duration="100"/>
<item android:drawable="@drawable/ic_wifi_2" android:duration="100"/>
<item android:drawable="@drawable/ic_wifi_3" android:duration="100"/>
<item android:drawable="@drawable/ic_wifi_4" android:duration="100"/>
<item android:drawable="@drawable/ic_wifi_5" android:duration="100"/>
</animation-list>
private void playAnimation() {
mImageView.setImageResource(R.drawable.frame_anim);
AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable();
animationDrawable.start();
...
animationDrawable.stop();
}
关于帧动画使用XML形式编写值得一提的是: animation-list必须是根节点,这个根节点包含一个或者多个item元素,item简单理解就是类似一帧的动画资源。item属性有:
android:oneshot属性,其中true代表只执行一次,false循环执行;
android:drawable ,代表一个frame的Drawable资源;
android:duration,代表一个frame显示多长时间。
补间动画
Andoird所支持的补间动画效果有如下五种:
- AlphaAnimation: 透明度渐变效果,创建时许指定开始以及结束透明度,还有动画的持续 时间,透明度的变化范围(0,1),0是完全透明,1是完全不透明;对应<alpha/>标签!
- ScaleAnimation:缩放渐变效果,创建时需指定开始以及结束的缩放比,以及缩放参考点, 还有动画的持续时间;对应<scale/>标签!
- TranslateAnimation:位移渐变效果,创建时指定起始以及结束位置,并指定动画的持续 时间即可;对应<translate/>标签!
- RotateAnimation:旋转渐变效果,创建时指定动画起始以及结束的旋转角度,以及动画 持续时间和旋转的轴心;对应<rotate/>标签
- AnimationSet:组合渐变,就是前面多种渐变的组合,对应<set/>标签
补间动画示例
public void tweenedAnimation(View view) {
// 创建一个透明度动画,透明度从1渐变至0
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
alphaAnimation.setDuration(3000);
// 创建一个旋转动画,从0度旋转至360度
RotateAnimation rotateAnimation = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(3000);
ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0.5f, 1, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(3000);
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(3000);
// 组合上述4种动画
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(translateAnimation);
view.startAnimation(animationSet);
}
差值器示例
属性动画
属性动画(Property) 控制属性来实现动画。
特点:最为强大的动画,弥补了补间动画的缺点,实现位置+视觉的变化。并且可以自定义插值器,实现各种效果
属性动画示例
private void startObjectAnimatorSet() {
// 创建一个ObjectAnimator,将mImageView的scaleX属性值从1变化到0.5
Animator scaleXAnimator = ObjectAnimator.ofFloat(mImageView, "scaleX", 1, 0.5f);
scaleXAnimator.setDuration(2000);
// 创建一个ObjectAnimator,将mImageView的scaleY属性值从1变化到0.5
Animator scaleYAnimator = ObjectAnimator.ofFloat(mImageView, "scaleY", 1, 0.5f);
scaleYAnimator.setDuration(2000);
// 创建一个ObjectAnimator,将mImageView的rotationX属性值从0变化到360
Animator rotationXAnimator = ObjectAnimator.ofFloat(mImageView, "rotationX", 0, 360);
rotationXAnimator.setDuration(2000);
// 创建一个ObjectAnimator,将mImageView的rotationY属性值从0变化到360
Animator rotationYAnimator = ObjectAnimator.ofFloat(mImageView, "rotationY", 0, 360);
rotationYAnimator.setDuration(2000);
// 组合上述4种动画
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(scaleXAnimator).with(scaleYAnimator)
.before(rotationXAnimator).after(rotationYAnimator);
animatorSet.start();
}
动画总结
自定义View
自定义View的实现方式有以下几种
| 类型 | 定义 |
|---|---|
| 自定义组合控件 | 多个控件组合成为一个新的控件,方便多处复用 |
| 继承系统View控件 | 继承自TextView等系统控件,在系统控件的基础功能上进行扩展 |
| 继承View | 不复用系统控件逻辑,继承View进行功能定义 |
| 继承系统ViewGroup | 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展 |
| 继承ViewViewGroup | 不复用系统控件逻辑,继承ViewGroup进行功能定义 |
创建View
public class SwitchButton extends View implements Checkable {
public SwitchButton(Context context) {
super(context);
init(context, null);
}
public SwitchButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
处理View布局
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY);
}
if(heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST){
heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
float viewPadding = Math.max(shadowRadius + shadowOffset, borderWidth);
height = h - viewPadding - viewPadding;
width = w - viewPadding - viewPadding;
...
}
绘制View
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制白色背景的圆角矩形
paint.setStrokeWidth(borderWidth);
paint.setStyle(Paint.Style.FILL);
paint.setColor(background);
drawRoundRect(canvas, left, top, right, bottom, viewRadius, paint);
//绘制关闭状态的边框
paint.setStyle(Paint.Style.STROKE);
paint.setColor(uncheckColor);
drawRoundRect(canvas,left, top, right, bottom, viewRadius, paint);
...
//绘制按钮左边绿色长条遮挡
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(1);
drawArc(canvas, left, top, left + 2 * viewRadius, top + 2 * viewRadius,90, 180, paint);
canvas.drawRect( left + viewRadius, top,viewState.buttonX,
top + 2 * viewRadius,paint);
...
//绘制按钮
drawButton(canvas, viewState.buttonX, centerY);
}
处理用户交互
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!isEnabled()) { return false; }
switch (actionMasked){
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_MOVE:
if(isPendingDragState()){ //在准备进入拖动状态过程中,可以拖动按钮位置
...
}else if(isDragState()){ //拖动按钮位置,同时改变对应的背景颜色
...
}
break;
case MotionEvent.ACTION_UP:
if(System.currentTimeMillis() - touchDownTime <= 300){ //点击时间小于300ms,认为是点击操作
toggle();
}else if(isDragState()){ //在拖动状态,计算按钮位置,设置是否切换状态
...
}
break;
case MotionEvent.ACTION_CANCEL:
removeCallbacks(postPendingDrag);
break;
}
return true;
}
处理动画
// 初始化View时设置动画
valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(effectDuration);
valueAnimator.setRepeatCount(0);
valueAnimator.addUpdateListener(animatorUpdateListener);
// 点击开关后启动动画
valueAnimator.start();
// 简单动画更新回调,触发View绘制
private ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float) animation.getAnimatedValue();
switch (animateState) {
...
case ANIMATE_STATE_SWITCH:
viewState.buttonX = beforeState.buttonX + (afterState.buttonX - beforeState.buttonX) * value;
float fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX);
viewState.checkStateColor = (int) argbEvaluator.evaluate( fraction,uncheckColor,checkedColor);
viewState.radius = fraction * viewRadius;
viewState.checkedLineColor = (int) argbEvaluator.evaluate(fraction,Color.TRANSPARENT, checkLineColor);
break;
}
postInvalidate();
}
};
自定义View小结