Android 动画

news2024/11/15 11:01:36

在App中合理地使用动画能够获得友好愉悦的用户体验,Android中的动画有View动画、属性动画、帧动画、布局动画、转场动画等,在5.x以后有又新增了矢量动画,这些动画在平常开发中使用较为普遍,所以有必要做一次完整的总结。


一、View动画

View动画定义了渐变Alpha、旋转Rotate、缩放Scale、平移Translate四种基本动画,并且通过这四种基本动画的组合使用,可以实现多种交互效果。

View动画使用非常简单,不仅可以通过XML文件来定义动画,同样可以通过Java代码来实现动画过程。

1.Xml文件定义View动画

通过xml来定义View动画涉及到一些公有的属性(在AndroidStudio上不能提示):

android:duration     动画持续时间
android:fillAfter    为true动画结束时,View将保持动画结束时的状态
android:fillBefore   为true动画结束时,View将还原到开始开始时的状态
android:repeatCount  动画重复执行的次数
android:repeatMode   动画重复模式 ,重复播放时restart重头开始,reverse重复播放时倒叙回放,该属性需要和android:repeatCount一起使用
android:interpolator 插值器,相当于变速器,改变动画的不同阶段的执行速度复制代码

这些属性是从Animation中继承下来的,在alpha、rotate、scale、translate标签中都可以直接使用。

利用xml文件定义View动画需要在工程的res目录下创建anim文件夹,所有的xml定义的View动画都要放在anim目录下。

渐变view_anim_alpha.xml:

<?xml version="1.0" encoding="utf-8"?><alphaxmlns:android="http://schemas.android.com/apk/res/android"android:duration="2000"android:fromAlpha="1.0"android:toAlpha="0"></alpha>复制代码

旋转view_anim_rotate.xml:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="2000"
        android:fillAfter="true"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="360">
</rotate>复制代码

缩放view_anim_scale.xml:

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
       android:duration="2000"
       android:fromXScale="1.0"
       android:fromYScale="1.0"
       android:pivotX="50%"
       android:pivotY="50%"
       android:toXScale="0.5"
       android:toYScale="0.5">
</scale>复制代码

平移view_anim_translate.xml:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
           android:duration="2000"
           android:fromXDelta="0"
           android:fromYDelta="0"
           android:toXDelta="100%"
           android:toYDelta="100%">
</translate>复制代码

rotate、scale动画的android:pivotX和android:pivotY属性、translate动画的android:toXDelta和android:toYDelta属性的取值都可以是都可以数值、百分数、百分数p,比如:50、50%、50%p,他们取值的代表的意义各不相同:

50表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移50px的位置;

50%表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移View宽度或高度的50%处的位置;

50%p表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移父控件宽度或高度的50%处的位置(p表示相对于ParentView的位置)。

"50"位置点

"50%"位置点

"50%p"位置点

通过定义xml动画资源文件,在Activity中调用:

public void clickToAlpha(View view) {
    Animation alphaAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_alpha);
    mTargetView.startAnimation(alphaAnim);
}

public void clickToRotate(View view) {
    Animation rotateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_rotate);
    mTargetView.startAnimation(rotateAnim);
}

public void clickToScale(View view) {
    Animation scaleAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_scale);
    mTargetView.startAnimation(scaleAnim);
}

public void clickToTranslate(View view) {
    Animation translateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_translate);
    mTargetView.startAnimation(translateAnim);
}

public void clickToSet(View view) {
    Animation setAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_set);
    mTargetView.startAnimation(setAnim);
}复制代码
2.Java代码实现View动画

在平常的业务逻辑中也可以直接用Java代码来实现Veiw动画,Android系统给我们提供了AlphaAnimation、RotateAnimation、ScaleAnimation、TranslateAnimation四个动画类分别来实现View的渐变、旋转、缩放、平移动画。

渐变:

public void clickToAlpha(View view) {
    AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
    alphaAnimation.setDuration(2000);
    mTargetView.startAnimation(alphaAnimation);
}复制代码

旋转:

public void clickToRotate(View view) {
    RotateAnimation rotateAnimation = new RotateAnimation(
            0, 360,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    rotateAnimation.setDuration(2000);
    mTargetView.startAnimation(rotateAnimation);
}复制代码

缩放:

public void clickToScale(View view) {
    ScaleAnimation scaleAnimation = new ScaleAnimation(
            1, 0.5f,
            1, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setDuration(2000);
    mTargetView.startAnimation(scaleAnimation);
}复制代码

平移:

public void clickToTranslate(View view) {
    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(2000);
    mTargetView.startAnimation(translateAnimation);
}复制代码

组合:

public void clickToSet(View view) {
    AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
    alphaAnimation.setDuration(2000);

    RotateAnimation rotateAnimation = new RotateAnimation(
            0, 360,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    rotateAnimation.setDuration(2000);

    ScaleAnimation scaleAnimation = new ScaleAnimation(
            1, 0.5f,
            1, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setDuration(2000);

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

    AnimationSet animationSet = new AnimationSet(true);
    animationSet.addAnimation(alphaAnimation);
    animationSet.addAnimation(rotateAnimation);
    animationSet.addAnimation(scaleAnimation);
    animationSet.addAnimation(translateAnimation);

    mTargetView.startAnimation(animationSet);
}复制代码

View动画效果

View动画可以设置一个动画执行的监听器:

animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        // 动画开始
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        // 动画结束
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
        //动画重复
    }
});复制代码

通过设置监听器可以在动画执行的开始、结束、重复时做一些其他的业务逻辑。


二、属性动画

所谓属性动画,就是改变对象Object的属性来实现动画过程。属性动画是对View的动画的扩展,通过它可以实现更多漂亮的动画效果。同时属性动画的作用对象不仅仅是View,任何对象都可以。

属性动画的作用效果就是:在一个指定的时间段内将对象的一个属性的属性值动态地变化到另一个属性值。

1.ObjectAnimator

ObjectAnimator是最常用的属性动画执行类。

privatevoidstartJavaPropertyAnimator() {
    ObjectAnimator
            .ofFloat(mImageView, "rotationY", 0f, 360f)
            .setDuration(2000)
            .start();
}复制代码

上面的代码就是通过ObjectAnimator在2000ms内将mImageView的rotationY属性的属性值从0f变化的360f。

ObjectAnimator实现属性动画

ObjectAnimtor可以用ofInt、ofFloat、ofObject等静态方法,传入动画作用的目标Object、属性字段、属性开始值、属性中间值、属性结束值等参数来构造动画对象。

在动画更新的过程中,通过不断去调用对象属性的setter方法改变属性值,不断重绘实现动画过程。如果没有给定动画开始属性值,那么系统会通过反射去获取Object对象的初始值作为动画的开始值。

属性动画也同样可以通过xml文件来定义,同样在工程的res目录下创建animator文件夹,xml文件定义的objectAnimator动画要放在该文件夹下。

property_animator.xml:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="2000"
                android:propertyName="rotationY"
                android:valueFrom="0"
                android:valueTo="360"
                android:valueType="floatType">
</objectAnimator>复制代码

Java代码调用:

private void startXmlPropertyAnimator() {
    Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), 
    R.animator.property_animator);
    animator.setTarget(mImageView);
    animator.start();
}复制代码

最终效果如上图。

属性动画也同样可以组合使用,通过AnimatorSet类和xml文件的set标签都可以同时改变对象的多个属性,实现更加丰富的动画效果。

通过AnimatorSet创建动画集:

private void startJavaPropertyAnimatorSet() {
    Animator scaleXAnimator = ObjectAnimator.ofFloat(mImageView, "scaleX", 1, 0.5f);
    scaleXAnimator.setDuration(2000);
    Animator scaleYAnimator = ObjectAnimator.ofFloat(mImageView, "scaleY", 1, 0.5f);
    scaleYAnimator.setDuration(2000);
    Animator rotationXAnimator = ObjectAnimator.ofFloat(mImageView, "rotationX", 0, 360);
    rotationXAnimator.setDuration(2000);
    Animator rotationYAnimator = ObjectAnimator.ofFloat(mImageView, "rotationY", 0, 360);
    rotationYAnimator.setDuration(2000);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(scaleXAnimator)
            .with(scaleYAnimator)
            .before(rotationXAnimator)
            .after(rotationYAnimator);
    animatorSet.start();
}复制代码

AnimatorSet通过before、with、after三个方法可以组合多个属性动画,with表示与给定动画同时执行,before在给定动画执行之前执行,after表示在给定动画执行之后执行。

AnimatorSet构建属性动画集

通过xml文件定义属性动画集:

property_animator_set.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="2000"
        android:propertyName="scaleX"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>
    <objectAnimator
        android:duration="2000"
        android:propertyName="scaleY"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>
    <objectAnimator
        android:duration="2000"
        android:propertyName="alpha"
        android:valueFrom="1"
        android:valueTo="0.5"
        android:valueType="floatType"/>
</set>复制代码

在Java代码中调用属性动画集:

private void startxmlPropertyAnimatorSet() {
    Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), 
    R.animator.property_animator_set);
    animator.setTarget(mImageView);
    animator.start();
}复制代码

xml定义属性动画集

同样,属性动画也可以添加动画执行监听器:

animator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        // 动画开始
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        // 动画结束
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        // 动画取消
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        // 动画重复
    }
});复制代码

在监听到属性动画开始、结束、取消、重复时可以去做一些其他的逻辑业务。

2.ValueAnimator

ValueAnimator是ObjectAnimator的父类,它继承自Animator。ValueAnimaotor同样提供了ofInt、ofFloat、ofObject等静态方法,传入的参数是动画过程的开始值、中间值、结束值来构造动画对象。可以将ValueAnimator看着一个值变化器,即在给定的时间内将一个目标值从给定的开始值变化到给定的结束值。在使用ValueAnimator时通常需要添加一个动画更新的监听器,在监听器中能够获取到执行过程中的每一个动画值。

privatevoidstartValueAnimator() {
    ValueAnimatorvalueAnimator= ValueAnimator.ofFloat(0, 1);
    valueAnimator.setDuration(300);
    valueAnimator.start();
    valueAnimator.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
        @OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
            // 动画更新过程中的动画值,可以根据动画值的变化来关联对象的属性,实现属性动画floatvalue= (float) animation.getAnimatedValue();
            Log.d("ValueAnimator", "动画值:" + value);
        }
    });
}复制代码

在300ms内将数值0变化到1的动画值的变化log:

02-25 23:16:57.586 D/ValueAnimator:动画值:0.002-25 23:16:57.596 D/ValueAnimator:动画值:0.00790217502-25 23:16:57.616 D/ValueAnimator:动画值:0.02955961202-25 23:16:57.636 D/ValueAnimator:动画值:0.06698727602-25 23:16:57.646 D/ValueAnimator:动画值:0.11810201402-25 23:16:57.666 D/ValueAnimator:动画值:0.1812879702-25 23:16:57.686 D/ValueAnimator:动画值:0.254548202-25 23:16:57.706 D/ValueAnimator:动画值:0.3306310202-25 23:16:57.716 D/ValueAnimator:动画值:0.416615702-25 23:16:57.736 D/ValueAnimator:动画值:0.505235902-25 23:16:57.746 D/ValueAnimator:动画值:0.593690602-25 23:16:57.766 D/ValueAnimator:动画值:0.6791839602-25 23:16:57.786 D/ValueAnimator:动画值:0.754520802-25 23:16:57.796 D/ValueAnimator:动画值:0.8267103402-25 23:16:57.826 D/ValueAnimator:动画值:0.8885729302-25 23:16:57.836 D/ValueAnimator:动画值:0.9381532702-25 23:16:57.856 D/ValueAnimator:动画值:0.972188202-25 23:16:57.876 D/ValueAnimator:动画值:0.9938441502-25 23:16:57.886 D/ValueAnimator:动画值:1.0复制代码

ValueAnimator的使用一般会结合更新监听器AnimatorUpdateListener,大多数时候是在自定义控件时使用。

下面是用ValueAnimator自定义控件实现动画打开关闭效果。

expanded_veiw.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@android:color/white"
              android:divider="@drawable/divider"
              android:orientation="vertical"
              android:showDividers="middle">

    <LinearLayout
        android:id="@+id/ll_expanded_question"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:gravity="center_vertical">

        <TextView
            android:id="@+id/tv_expanded_question"
            android:layout_width="0dp"
            android:layout_height="48dp"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:padding="8dp"
            android:text="如何把一本很难的书看懂?"
            android:textColor="#999999"
            android:textSize="16sp"/>

        <ImageView
            android:id="@+id/iv_expanded_indicator"
            android:layout_width="16dp"
            android:layout_height="16dp"
            android:layout_marginRight="16dp"
            android:src="@drawable/img_up"/>

    </LinearLayout>


    <TextView
        android:id="@+id/tv_expanded_answer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:text="多读几遍。真的特别有用。至少看三遍。从开头看,看到中间,重头再来,再看得多一点,在从新开始,建议看到快结束时再从新开始。"
        android:textColor="#999999"
        android:textSize="16sp"/>
</LinearLayout>复制代码
publicclassExpandedViewextendsFrameLayout {

    private TextView mTvAnswer;
    privateboolean isClosed;
    private ImageView mIvIndicator;

    publicExpandedView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    privatevoidinit(Context context) {
        Viewview= LayoutInflater.from(context).inflate(R.layout.expanded_view, this, true);
        LinearLayoutllQuestion= (LinearLayout) view.findViewById(R.id.ll_expanded_question);
        llQuestion.setOnClickListener(newOnClickListener() {
            @OverridepublicvoidonClick(View v) {
                anim();
            }
        });
        mTvAnswer = (TextView) view.findViewById(R.id.tv_expanded_answer);
        mIvIndicator = (ImageView) view.findViewById(R.id.iv_expanded_indicator);
    }

    privatevoidanim() {
        // 指示器旋转ValueAnimatorvalueAnimator1= isClosed
                ? ValueAnimator.ofFloat(180, 0)
                : ValueAnimator.ofFloat(0, 180);
        valueAnimator1.setDuration(500);
        valueAnimator1.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
            @OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
                floatvalue= (float) animation.getAnimatedValue();
                mIvIndicator.setRotation(value);
            }
        });
        valueAnimator1.start();

        // 打开开关闭操作finalintanswerHeight= mTvAnswer.getMeasuredHeight();
        ValueAnimatorvalueAnimator2= isClosed
                ? ValueAnimator.ofInt(-answerHeight, 0)
                : ValueAnimator.ofInt(0, -answerHeight);
        valueAnimator2.setDuration(500);
        finalMarginLayoutParamsparams= (MarginLayoutParams) mTvAnswer.getLayoutParams();
        valueAnimator2.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
            @OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
                intvalue= (int) animation.getAnimatedValue();
                params.bottomMargin = value;
                mTvAnswer.setLayoutParams(params);
            }
        });
        valueAnimator2.addListener(newAnimator.AnimatorListener() {
            @OverridepublicvoidonAnimationStart(Animator animation) {

            }

            @OverridepublicvoidonAnimationEnd(Animator animation) {
                isClosed = !isClosed;
            }

            @OverridepublicvoidonAnimationCancel(Animator animation) {

            }

            @OverridepublicvoidonAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator2.start();
    }
}复制代码

ValueAnimator自定义控件效果图

3.TypeEvaluator

ObjectAnimator和ValueAnimator都有ofObject方法,传入的都有一个TypeEvaluator类型的参数。TypeEvaluator是一个接口,里面也只有一个抽象方法:

public T evaluate(float fraction, T startValue, T endValue);复制代码

再看ofInt方法中没有传入该参数,但实际上调用ofInt方法时,系统已经有实现了TypeEvaluator接口的IntEvaluator,它的源码也非常简单:

public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
    int startInt = startValue;
    return (int)(startInt + fraction * (endValue - startInt));
}复制代码

fraction范围为0到1,表示动画执行过程中已完成程度。

泛型T即为动画执行的属性类型。

所以我们要用属性动画来执行复杂对象的动画过程,就需要自定义TypeEvaluator,实现动画逻辑。

先来定义一个对象

publicclassCircle {

    privateint raduis;         // 半径privateint color;          // 颜色privateint elevation;      // 高度publicCircle(int raduis, int color, int elevation) {
        this.raduis = raduis;
        this.color = color;
        this.elevation = elevation;
    }

    publicintgetRaduis() {
        return raduis;
    }

    publicvoidsetRaduis(int raduis) {
        this.raduis = raduis;
    }

    publicintgetColor() {
        return color;
    }

    publicvoidsetColor(int color) {
        this.color = color;
    }

    publicintgetElevation() {
        return elevation;
    }

    publicvoidsetElevation(int elevation) {
        this.elevation = elevation;
    }
}复制代码

自定义控件CircleView,将Circle作为它的一个属性:

public class CircleView extends View {
    private Circle circle;
    private Paint mPaint;

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        circle = new Circle(168, Color.RED, 0);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        setElevation(circle.getElevation());
        mPaint.setColor(circle.getColor());
        canvas.drawCircle(getMeasuredHeight() / 2, getMeasuredHeight() / 2, circle.getRaduis(), mPaint);
    }

    public void setCircle(Circle circle) {
        this.circle = circle;
        postInvalidate();
    }

    public Circle getCircle() {
        return circle;
    }
}复制代码

ObjectAnimator使用:

private void start1() {
    Circle startCircle = new Circle(168, Color.RED, 0);
    Circle middleCircle = new Circle(300, Color.GREEN, 15);
    Circle endCircle = new Circle(450, Color.BLUE, 30);
    ObjectAnimator.ofObject(mCircleView, "circle", new CircleEvaluator(), startCircle, middleCircle, endCircle)
            .setDuration(5000)
            .start();
}复制代码

ValueAnimator使用:

private void start2() {
    Circle startCircle = new Circle(168, Color.RED, 0);
    Circle middleCircle = new Circle(300, Color.GREEN, 15);
    Circle endCircle = new Circle(450, Color.BLUE, 30);
    ValueAnimator valueAnimator = ValueAnimator.ofObject(new CircleEvaluator(), startCircle, middleCircle, endCircle);
    valueAnimator.setDuration(5000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Circle circle = (Circle) animation.getAnimatedValue();
            mCircleView.setCircle(circle);
        }
    });
    valueAnimator.start();
}复制代码

自定义TypeEvaluator效果图

需要注意的是,系统调用获取控件setter、getter方法是通过反射获取的,属性的名称必须和getter、setter方法名称后面的字符串一致,比如上面的getter、setter方法分别为getCircle、setCircle,那么属性名字就必须为circle。


三、帧动画

帧动画需要开发者制定好动画每一帧,系统一帧一帧的播放图片。

private void start1() {
    AnimationDrawable ad = new AnimationDrawable();
    for (int i = 0; i < 7; i++) {
        Drawable drawable = getResources().getDrawable(getResources().getIdentifier("ic_fingerprint_" + i, "drawable", getPackageName()));
        ad.addFrame(drawable, 100);
    }
    ad.setOneShot(false);
    mImageView.setImageDrawable(ad);
    ad.start();
}复制代码

帧动画同样也可以在xml文件中配置,直接在工程drawable目录新建animation-list标签:

<?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_fingerprint_0" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_1" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_2" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_3" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_4" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_5" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_6" android:duration="100"/>
</animation-list>复制代码
private void start2() {
    mImageView.setImageResource(R.drawable.frame_anim);
    AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable();
    animationDrawable.start();
}复制代码

其中android:onshot属性和setOneShot方法表示是否只执行一次。

AnimationDrawable实际是上是一个Drawable动画,动画执行的过程总会给你不断重绘Drawable的每一帧图像,实现动态播放效果。

帧动画效果图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/190527.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

130道python练习题 完整版PDF

今天跟大家分享一些干货&#xff0c;在学python的朋友可以动起来了&#xff01; python基础知识练习题&#xff0c;常见常用的&#xff0c;可以作为参考&#xff0c;挺不错的&#xff0c;也有许许多多的讲解&#xff0c;适合python巩固基础知识和入门 130道练习题&#xff0c…

电脑网速慢怎么解决?提升网速真的很容易!

我们经常会使用电脑&#xff0c;如果加载网页的时间过长&#xff0c;或者是出现未响应的提示等问题&#xff0c;会非常影响我们使用电脑的观感。 针对这种问题&#xff0c;电脑网速慢怎么解决&#xff1f;来看看下面造成电脑网速慢的主要原因&#xff0c;以及我们该怎么提升电…

Python父类方法重写

在 Python 中&#xff0c;子类继承了父类&#xff0c;那么子类就拥有了父类所有的类属性和类方法。通常情况下&#xff0c;子类会在此基础上&#xff0c;扩展一些新的类属性和类方法。但凡事都有例外&#xff0c;我们可能会遇到这样一种情况&#xff0c;即子类从父类继承得来的…

vue前框框架课程笔记(六)

目录单文件组件.vue文件单文件组件常用结构App.vueStudent.vueSchool.vuemain.jsindex.html运行结果vue脚手架具体步骤项目架构其他配置项props配置项mixin混入插件scoped属性本博客参考尚硅谷官方课程&#xff0c;详细请参考 【尚硅谷bilibili官方】 本博客以vue2作为学习目…

zookeeper源码分享四 ---- RequestProcessor 处理链路

单机zookeeper RequestProcessor 处理链路 将请求放入LinkedBlockingQueue队列中&#xff0c;通过一个队列中。启动一个线程去消费这个队列&#xff0c;避免了阻塞。 zookeeper的处理是实现RequestProcessor接口的processRequest(Request request) 方法。 PrepRequestProcesso…

exsi删除虚拟机提示在当前状况下不允许执行此操作解决方法、vmware删除虚拟机提示在当前状况下不允许执行此操作解决方法

说明 我这exsi版本为5.5 今天在回收虚拟机的时候有些虚拟机无法删除&#xff0c;提示下面内容。 解决方法 方法1 直接给删除失败的虚拟机开机&#xff0c;开机完毕以后再关机就能直接删除了。 方法2 删除失败的虚拟机&#xff0c;也无法开机的情况下&#xff0c;实用该方…

春节档的观影“热”,拯救不了影视圈的“冷”?

配图来自Canva可画 疫情三年&#xff0c;影视业也随着“冬眠”了三年。 先是疫情爆发影响影视业上下游的正常生产&#xff0c;而后国家加速规范影视行业税收秩序&#xff0c;资本相继撤离&#xff0c;再后来影视企业入不敷出业绩巨亏&#xff0c;影视寒冬来临成为行业共识。在…

MMCV - dataset_analysis.py 可视化检测和跟踪任务自定义数据集神器

做视觉检测跟踪任务时,我们需要在论文插图中体现出我们数据集的信息,这个时候就有一个非常好用的神器:dataset_analysis.py的文件。该文件能够帮助用户直接可视化custom数据集的bbox实例信息,如上图所示,包括:显示类别和 bbox 实例个数的分布图;显示类别和 bbox 实例宽/…

中科院和人社部职称评审所需材料内容对比

目录1.前言2.中科院所需材料2.1 基本情况2.2 工作经历2.3 主要专业技术工作业绩2.4 重要著&#xff08;译&#xff09;作、论文及技术工作报告2.5 继 续 教 育 情 况2.6 考试、考核情况2.7 单位推荐意见2.8 材料审核意见2.9 个人技术报告2.10 未来工作展望3.人社部所需材料3.1 …

三十八、Kubernetes1.25中数据存储第二篇

1、概述在前面已经提到&#xff0c;容器的生命周期可能很短&#xff0c;会被频繁地创建和销毁。那么容器在销毁时&#xff0c;保存在容器中的数据也会被清除。这种结果对用户来说&#xff0c;在某些情况下是不乐意看到的。为了持久化保存容器的数据&#xff0c;kubernetes引入了…

行为型模式 - 状态模式State

状态模式的定义与特点 状态&#xff08;State&#xff09;模式的定义&#xff1a;对有状态的对象&#xff0c;把复杂的“判断逻辑”提取到不同的状态对象中&#xff0c;允许状态对象在其内部状态发生改变时改变其行为。 状态模式是一种对象行为型模式&#xff0c;其主要优点…

Oracle用户权限管理

一、运行企业管理器 管理员身份运行客户端的企业管理器 首次打开需要添加数据库信息&#xff0c;这里使用已经存在的数据库orcl 使用系统用户登录 用户名&#xff1a;system&#xff0c;口令orcl 或者不输入使用SYSDBA登录 可以查看所有用户的详细信息: 用户名, 账户状态,…

union和union all 的区别

1&#xff1a;union用于合并两个或多个select 语句的结果集。 注意&#xff1a; 1&#xff1a;union内部的select 语句必需拥有相同数量的列&#xff0c;列也夜必须拥有相似的数据类型&#xff0c;同时每条select 语句中的列的顺序必须相同。 2&#xff1a;union结果集中的列…

JVM垃圾回收与性能调优策略

一、JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范&#xff0c;JVM将内存划分为&#xff1a; New&#xff08;年轻代&#xff09; Tenured&#xff08;年老代&#xff09; 永久代&#xff08;Perm&#xff09; 其中New和Tenured属于堆内存&#xff0c;堆内存会从JVM启动参…

项目代码版本控制与保护

一、版本命名规则 1.1 需求开发分支命名规则 格式&#xff1a;dev_v版本号_需求名称 案例&#xff1a;dev_v01.31_TX202301141 dev_v01.31_数字产品平台订单查询优化 1.2 测试环境发布分支命名规则 格式&#xff1a;uat_deploy 1.3 预上环境分支命名规则 格式&#xff1a…

Apsara Clouder云计算专项技能认证:云服务器ECS入门

文章目录 一、什么是云服务器ECS二、如何获取一台云服务器ECS三、5分钟学会管理云服务器ECS四、【实验】ECS云服务器新手上路五、【实验】基于ECS搭建云上博客入口: https://edu.aliyun.com/certification/cldc15 一、什么是云服务器ECS

8K超高清企业获广东省专精特新称号,背后意味着什么?

2023年开年之际&#xff0c;广州博冠光电科技股份有限公司及其子公司广州博冠智能科技有限公司通过“2022年广东省专精特新中小企业”和“2022年广东省名优高新技术产品”2项权威认定荣誉。广东省“专精特新”认定是什么&#xff1f;“专精特新”是指中小企业具备专业化、精细化…

github入门指南

文章目录什么是开源开源项目托管平台github使用介绍什么是github使用github&#xff08;1&#xff09;注册&#xff08;2&#xff09;创建远程仓库&#xff08;3&#xff09;github功能介绍本地创建仓库与远程仓库进行关联什么是开源 开源&#xff0c;即开放源代码&#xff0c…

呆头鹅批量剪辑视频软件:玩的就是裂变,剪辑需求者的终身助理

文案来自于公众号&#xff1a;生财风暴 图片来自于公众号&#xff1a;生财风暴 随着5G时代到来&#xff0c;短视频&#xff0c;电商&#xff0c;自媒体等平台全面崛起&#xff0c;目前影视制作行业前景的9字真言: 需求大&#xff01;门槛低&#xff01;薪资高&#xff01; 呆头…