Android客户端 | 青训营笔记

117 阅读5分钟

Android客户端 | 青训营笔记

这是我参与「第四届青训营 -Android场」笔记创作活动的的第5天

动画

image.png

帧动画

帧动画,从字面意思来理解,帧:就是影像动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。 一帧就是一副静止的画面,连续的帧就形成动画,如电视图象等。简单点说就是类似幻灯片播放的那种效果,因此帧动画的本质就是将一张张的图片,通过代码对这些图片进行连续的活动(这样就形成了动画)

帧动画示例

<?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显示多长时间。

image.png

补间动画

Andoird所支持的补间动画效果有如下五种:

  • AlphaAnimation: 透明度渐变效果,创建时许指定开始以及结束透明度,还有动画的持续 时间,透明度的变化范围(0,1),0是完全透明,1是完全不透明;对应<alpha/>标签!
  • ScaleAnimation:缩放渐变效果,创建时需指定开始以及结束的缩放比,以及缩放参考点, 还有动画的持续时间;对应<scale/>标签!
  • TranslateAnimation:位移渐变效果,创建时指定起始以及结束位置,并指定动画的持续 时间即可;对应<translate/>标签!
  • RotateAnimation:旋转渐变效果,创建时指定动画起始以及结束的旋转角度,以及动画 持续时间和旋转的轴心;对应<rotate/>标签
  • AnimationSet:组合渐变,就是前面多种渐变的组合,对应<set/>标签

image.png

补间动画示例

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

image.png

差值器示例

image.png

image.png

属性动画

属性动画(Property) 控制属性来实现动画。
特点:最为强大的动画,弥补了补间动画的缺点,实现位置+视觉的变化。并且可以自定义插值器,实现各种效果

image.png 属性动画示例

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

image.png 动画总结 image.png

自定义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小结

image.png