深度剖析:Android View 动画原理大揭秘

深度剖析:Android View 动画原理大揭秘

一、引言

在 Android 应用开发中,动画效果能够极大地提升用户体验,使界面更加生动、交互性更强。从简单的淡入淡出效果到复杂的 3D 变换,Android 提供了丰富的动画框架来满足各种需求。然而,要想充分发挥这些动画的威力,深入理解其背后的原理是必不可少的。

本文将从源码级别出发,全面深入地分析 Android View 动画的原理。我们将详细探讨不同类型动画的实现机制、动画的执行流程以及相关的关键类和方法。通过对源码的解读,你将能够更好地掌握 Android 动画的本质,从而在实际开发中更加灵活地运用动画效果。

二、Android 动画概述

2.1 动画的分类

Android 动画主要分为以下几类:

  • 视图动画(View Animation):也称为补间动画,是 Android 早期提供的动画机制,它通过对 View 的内容进行平移、缩放、旋转和透明度变化等操作来实现动画效果。视图动画的优点是使用简单,缺点是只能对 View 进行操作,且没有真正改变 View 的属性。
  • 属性动画(Property Animation):是 Android 3.0(API 级别 11)引入的一种动画机制,它可以直接改变 View 的属性值,实现更加灵活和复杂的动画效果。属性动画可以对任何对象的任何属性进行动画操作,并且会真正改变对象的属性值。
  • 帧动画(Frame Animation):通过依次显示一系列的图片来实现动画效果,类似于传统的动画片。帧动画适合实现一些简单的动画效果,如加载动画等。

2.2 动画的应用场景

动画在 Android 应用中有广泛的应用场景,例如:

  • 界面过渡:在不同的界面之间添加动画过渡效果,使界面切换更加平滑和自然。
  • 提示用户:通过动画效果吸引用户的注意力,提示用户某些操作的完成或错误信息。
  • 增强交互性:在用户与界面进行交互时,添加动画效果可以增强交互的趣味性和反馈性。

三、视图动画原理

3.1 视图动画的基本概念

视图动画主要通过 Animation 类及其子类来实现,常见的子类包括 TranslateAnimation(平移动画)、ScaleAnimation(缩放动画)、RotateAnimation(旋转动画)和 AlphaAnimation(透明度动画)。视图动画的核心思想是在一段时间内,根据一定的算法计算出 View 在不同时刻的状态,并将这些状态应用到 View 上,从而实现动画效果。

3.2 视图动画的关键类和方法

3.2.1 Animation

Animation 类是所有视图动画的基类,它定义了动画的基本属性和方法。以下是 Animation 类的部分关键属性和方法的源码分析:

java 复制代码
// Animation 类的部分关键属性
// 动画的持续时间,单位为毫秒
protected long mDuration = 0;
// 动画的开始延迟时间,单位为毫秒
protected long mStartOffset = 0;
// 动画的重复模式,有 RESTART 和 REVERSE 两种模式
protected int mRepeatMode = RESTART;
// 动画的重复次数
protected int mRepeatCount = 0;

// 构造函数,初始化动画的一些基本属性
public Animation(Context context, AttributeSet attrs) {
    TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animation);
    // 获取动画的持续时间
    mDuration = a.getInt(com.android.internal.R.styleable.Animation_duration, 0);
    // 获取动画的开始延迟时间
    mStartOffset = a.getInt(com.android.internal.R.styleable.Animation_startOffset, 0);
    // 获取动画的重复模式
    mRepeatMode = a.getInt(com.android.internal.R.styleable.Animation_repeatMode, RESTART);
    // 获取动画的重复次数
    mRepeatCount = a.getInt(com.android.internal.R.styleable.Animation_repeatCount, 0);
    a.recycle();
}

// 计算动画在指定时间点的变换矩阵
@Override
protected boolean applyTransformation(float interpolatedTime, Transformation t) {
    return false;
}

// 开始动画
public void start() {
    mStartTime = -1;
    mEndTime = -1;
    mStarted = true;
    mEnded = false;
    mCycleFlip = false;
    mRepeated = 0;
    mMore = true;
    mOneMoreTime = true;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    if (mStartOffset > 0) {
        mStartTime += mStartOffset;
    }
}

在上述代码中,Animation 类定义了动画的基本属性,如持续时间、开始延迟时间、重复模式和重复次数等。applyTransformation 方法用于计算动画在指定时间点的变换矩阵,子类需要重写该方法来实现具体的动画效果。start 方法用于启动动画,设置动画的开始时间和状态。

3.2.2 TranslateAnimation

TranslateAnimation 类用于实现平移动画,它继承自 Animation 类。以下是 TranslateAnimation 类的部分关键方法的源码分析:

java 复制代码
// 平移动画的起始 X 坐标
private float mFromXDelta;
// 平移动画的结束 X 坐标
private float mToXDelta;
// 平移动画的起始 Y 坐标
private float mFromYDelta;
// 平移动画的结束 Y 坐标
private float mToYDelta;

// 构造函数,初始化平移动画的起始和结束坐标
public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
    mFromXDelta = fromXDelta;
    mToXDelta = toXDelta;
    mFromYDelta = fromYDelta;
    mToYDelta = toYDelta;
}

// 计算平移动画在指定时间点的变换矩阵
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float dx = mFromXDelta;
    float dy = mFromYDelta;
    if (mFromXDelta != mToXDelta) {
        // 根据插值时间计算当前的 X 坐标偏移量
        dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
    }
    if (mFromYDelta != mToYDelta) {
        // 根据插值时间计算当前的 Y 坐标偏移量
        dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
    }
    // 应用平移变换到变换矩阵中
    t.getMatrix().setTranslate(dx, dy);
}

在上述代码中,TranslateAnimation 类定义了平移动画的起始和结束坐标。applyTransformation 方法根据插值时间计算当前的 X 和 Y 坐标偏移量,并将平移变换应用到变换矩阵中。

3.2.3 ScaleAnimation

ScaleAnimation 类用于实现缩放动画,它继承自 Animation 类。以下是 ScaleAnimation 类的部分关键方法的源码分析:

java 复制代码
// 缩放动画的起始 X 缩放比例
private float mFromX;
// 缩放动画的结束 X 缩放比例
private float mToX;
// 缩放动画的起始 Y 缩放比例
private float mFromY;
// 缩放动画的结束 Y 缩放比例
private float mToY;
// 缩放动画的中心点 X 坐标
private float mPivotX;
// 缩放动画的中心点 Y 坐标
private float mPivotY;

// 构造函数,初始化缩放动画的起始和结束缩放比例以及中心点坐标
public ScaleAnimation(float fromX, float toX, float fromY, float toY, float pivotX, float pivotY) {
    mFromX = fromX;
    mToX = toX;
    mFromY = fromY;
    mToY = toY;
    mPivotX = pivotX;
    mPivotY = pivotY;
}

// 计算缩放动画在指定时间点的变换矩阵
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float sx = 1.0f;
    float sy = 1.0f;
    if (mFromX != 1.0f || mToX != 1.0f) {
        // 根据插值时间计算当前的 X 缩放比例
        sx = mFromX + ((mToX - mFromX) * interpolatedTime);
    }
    if (mFromY != 1.0f || mToY != 1.0f) {
        // 根据插值时间计算当前的 Y 缩放比例
        sy = mFromY + ((mToY - mFromY) * interpolatedTime);
    }
    if (sx != 1.0f || sy != 1.0f) {
        // 应用缩放变换到变换矩阵中
        Matrix matrix = t.getMatrix();
        matrix.preScale(sx, sy, mPivotX, mPivotY);
    }
}

在上述代码中,ScaleAnimation 类定义了缩放动画的起始和结束缩放比例以及中心点坐标。applyTransformation 方法根据插值时间计算当前的 X 和 Y 缩放比例,并将缩放变换应用到变换矩阵中。

3.2.4 RotateAnimation

RotateAnimation 类用于实现旋转动画,它继承自 Animation 类。以下是 RotateAnimation 类的部分关键方法的源码分析:

java 复制代码
// 旋转动画的起始角度
private float mFromDegrees;
// 旋转动画的结束角度
private float mToDegrees;
// 旋转动画的中心点 X 坐标
private float mPivotX;
// 旋转动画的中心点 Y 坐标
private float mPivotY;

// 构造函数,初始化旋转动画的起始和结束角度以及中心点坐标
public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY) {
    mFromDegrees = fromDegrees;
    mToDegrees = toDegrees;
    mPivotX = pivotX;
    mPivotY = pivotY;
}

// 计算旋转动画在指定时间点的变换矩阵
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
    if (mPivotX == 0.0f && mPivotY == 0.0f) {
        // 应用旋转变换到变换矩阵中,中心点为 (0, 0)
        t.getMatrix().setRotate(degrees);
    } else {
        // 应用旋转变换到变换矩阵中,指定中心点坐标
        t.getMatrix().setRotate(degrees, mPivotX, mPivotY);
    }
}

在上述代码中,RotateAnimation 类定义了旋转动画的起始和结束角度以及中心点坐标。applyTransformation 方法根据插值时间计算当前的旋转角度,并将旋转变换应用到变换矩阵中。

3.2.5 AlphaAnimation

AlphaAnimation 类用于实现透明度动画,它继承自 Animation 类。以下是 AlphaAnimation 类的部分关键方法的源码分析:

java 复制代码
// 透明度动画的起始透明度
private float mFromAlpha;
// 透明度动画的结束透明度
private float mToAlpha;

// 构造函数,初始化透明度动画的起始和结束透明度
public AlphaAnimation(float fromAlpha, float toAlpha) {
    mFromAlpha = fromAlpha;
    mToAlpha = toAlpha;
}

// 计算透明度动画在指定时间点的透明度值
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    // 根据插值时间计算当前的透明度值
    final float alpha = mFromAlpha + ((mToAlpha - mFromAlpha) * interpolatedTime);
    // 设置变换的透明度值
    t.setAlpha(alpha);
}

在上述代码中,AlphaAnimation 类定义了透明度动画的起始和结束透明度。applyTransformation 方法根据插值时间计算当前的透明度值,并设置到变换中。

3.3 视图动画的执行流程

视图动画的执行流程主要包括以下几个步骤:

  1. 创建动画对象 :根据需要创建具体的动画对象,如 TranslateAnimationScaleAnimation 等。
  2. 设置动画属性:设置动画的持续时间、开始延迟时间、重复模式等属性。
  3. 启动动画 :调用 ViewstartAnimation 方法启动动画。
  4. 动画循环 :在动画的持续时间内,系统会不断调用 AnimationapplyTransformation 方法,计算动画在不同时间点的变换矩阵,并将其应用到 View 上。
  5. 动画结束:当动画的持续时间结束时,动画停止。

以下是一个简单的视图动画示例代码:

java 复制代码
// 创建平移动画对象,从 (0, 0) 平移到 (200, 0)
TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 0);
// 设置动画的持续时间为 1000 毫秒
translateAnimation.setDuration(1000);
// 获取要应用动画的 View
View view = findViewById(R.id.my_view);
// 启动动画
view.startAnimation(translateAnimation);

在上述代码中,首先创建了一个平移动画对象,然后设置了动画的持续时间,最后将动画应用到一个 View 上并启动动画。

3.4 视图动画的局限性

视图动画虽然使用简单,但也存在一些局限性:

  • 不改变 View 的实际属性 :视图动画只是对 View 的内容进行视觉上的变换,并没有真正改变 View 的属性。例如,一个平移动画结束后,View 的实际位置并没有改变,仍然在原来的位置上。
  • 只能对 View 进行操作 :视图动画只能对 View 进行操作,不能对非 View 对象进行动画操作。
  • 动画效果有限:视图动画只能实现平移、缩放、旋转和透明度变化等基本的动画效果,对于一些复杂的动画效果,如 3D 变换、路径动画等,无法实现。

四、属性动画原理

4.1 属性动画的基本概念

属性动画是 Android 3.0(API 级别 11)引入的一种动画机制,它可以直接改变对象的属性值,实现更加灵活和复杂的动画效果。属性动画的核心思想是在一段时间内,根据一定的算法计算出对象属性在不同时刻的值,并将这些值应用到对象的属性上,从而实现动画效果。

4.2 属性动画的关键类和方法

4.2.1 ValueAnimator

ValueAnimator 是属性动画的基类,它可以在一段时间内生成一系列的值。以下是 ValueAnimator 类的部分关键属性和方法的源码分析:

java 复制代码
// 动画的起始值
private float mFloatValues[];
// 动画的结束值
private float mFloatValues[];
// 动画的持续时间,单位为毫秒
private long mDuration = 300;
// 动画的插值器
private TimeInterpolator mInterpolator = new AccelerateDecelerateInterpolator();

// 创建一个 ValueAnimator 对象,指定动画的起始值和结束值
public static ValueAnimator ofFloat(float... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setFloatValues(values);
    return anim;
}

// 设置动画的持续时间
public ValueAnimator setDuration(long duration) {
    mDuration = duration;
    return this;
}

// 设置动画的插值器
public void setInterpolator(TimeInterpolator value) {
    if (value != null) {
        mInterpolator = value;
    } else {
        mInterpolator = new LinearInterpolator();
    }
}

// 启动动画
public void start() {
    mPlayingState = PLAYING;
    mRunning = true;
    mReversing = false;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    if (mStartDelay > 0) {
        mStartTime += mStartDelay;
    }
    mStartTimeCommitted = false;
    mSeekFraction = -1;
    mOverallFraction = 0f;
    mPlayingFraction = 0f;
    mLastFrameTime = -1;
    mCurrentIteration = 0;
    mCurrentPlayTime = 0;
    AnimationHandler.getInstance().addAnimationFrameCallback(this, mStartTime);
}

// 计算动画在指定时间点的值
@Override
public void doAnimationFrame(long frameTime) {
    if (mStartTime < 0) {
        mStartTime = frameTime;
    }
    long currentTime = frameTime;
    long totalTime = currentTime - mStartTime;
    float fraction = totalTime / (float) mDuration;
    if (fraction > 1f) {
        fraction = 1f;
    }
    float interpolatedFraction = mInterpolator.getInterpolation(fraction);
    float value = mFloatValues[0] + (mFloatValues[1] - mFloatValues[0]) * interpolatedFraction;
    // 触发动画更新监听器
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            ValueAnimator.AnimatorUpdateListener listener = mUpdateListeners.get(i);
            listener.onAnimationUpdate(this);
        }
    }
}

在上述代码中,ValueAnimator 类定义了动画的起始值、结束值、持续时间和插值器等属性。ofFloat 方法用于创建一个 ValueAnimator 对象,指定动画的起始值和结束值。setDuration 方法用于设置动画的持续时间,setInterpolator 方法用于设置动画的插值器。start 方法用于启动动画,doAnimationFrame 方法用于计算动画在指定时间点的值,并触发动画更新监听器。

4.2.2 ObjectAnimator

ObjectAnimatorValueAnimator 的子类,它可以直接对对象的属性进行动画操作。以下是 ObjectAnimator 类的部分关键属性和方法的源码分析:

java 复制代码
// 要进行动画操作的对象
private Object mTarget;
// 要进行动画操作的属性名
private String mPropertyName;
// 属性的 setter 方法
private Method mSetter;

// 创建一个 ObjectAnimator 对象,指定要进行动画操作的对象、属性名和属性值
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    ObjectAnimator anim = new ObjectAnimator();
    anim.mTarget = target;
    anim.mPropertyName = propertyName;
    anim.setFloatValues(values);
    return anim;
}

// 启动动画
@Override
public void start() {
    if (mTarget != null) {
        // 获取属性的 setter 方法
        mSetter = getSetter(mTarget.getClass(), mPropertyName);
        if (mSetter != null) {
            super.start();
        }
    }
}

// 计算动画在指定时间点的值,并应用到对象的属性上
@Override
public void doAnimationFrame(long frameTime) {
    super.doAnimationFrame(frameTime);
    if (mSetter != null) {
        try {
            float value = getAnimatedValue();
            // 调用属性的 setter 方法,设置属性值
            mSetter.invoke(mTarget, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 获取属性的 setter 方法
private Method getSetter(Class<?> targetClass, String propertyName) {
    String methodName = "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
    try {
        return targetClass.getMethod(methodName, float.class);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
        return null;
    }
}

在上述代码中,ObjectAnimator 类定义了要进行动画操作的对象、属性名和属性的 setter 方法。ofFloat 方法用于创建一个 ObjectAnimator 对象,指定要进行动画操作的对象、属性名和属性值。start 方法用于启动动画,在启动动画之前会获取属性的 setter 方法。doAnimationFrame 方法用于计算动画在指定时间点的值,并调用属性的 setter 方法将值应用到对象的属性上。

4.2.3 AnimatorSet

AnimatorSet 类用于组合多个动画,实现多个动画的同时播放或顺序播放。以下是 AnimatorSet 类的部分关键方法的源码分析:

java 复制代码
// 动画集合
private ArrayList<Animator> mAnimators = new ArrayList<Animator>();
// 动画的播放顺序
private int mPlayOrder = PLAY_TOGETHER;

// 设置动画的播放顺序为同时播放
public void playTogether(Animator... items) {
    mPlayOrder = PLAY_TOGETHER;
    mAnimators.clear();
    for (Animator anim : items) {
        mAnimators.add(anim);
    }
}

// 设置动画的播放顺序为顺序播放
public void playSequentially(Animator... items) {
    mPlayOrder = PLAY_SEQUENTIALLY;
    mAnimators.clear();
    for (Animator anim : items) {
        mAnimators.add(anim);
    }
}

// 启动动画集合
public void start() {
    if (mPlayOrder == PLAY_TOGETHER) {
        for (Animator anim : mAnimators) {
            anim.start();
        }
    } else if (mPlayOrder == PLAY_SEQUENTIALLY) {
        for (int i = 0; i < mAnimators.size(); i++) {
            final Animator anim = mAnimators.get(i);
            if (i < mAnimators.size() - 1) {
                final Animator nextAnim = mAnimators.get(i + 1);
                anim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        nextAnim.start();
                    }
                });
            }
        }
        mAnimators.get(0).start();
    }
}

在上述代码中,AnimatorSet 类定义了动画集合和动画的播放顺序。playTogether 方法用于设置动画的播放顺序为同时播放,playSequentially 方法用于设置动画的播放顺序为顺序播放。start 方法用于启动动画集合,根据播放顺序依次启动动画。

4.3 属性动画的执行流程

属性动画的执行流程主要包括以下几个步骤:

  1. 创建动画对象 :根据需要创建具体的动画对象,如 ValueAnimatorObjectAnimator 等。
  2. 设置动画属性:设置动画的起始值、结束值、持续时间、插值器等属性。
  3. 启动动画 :调用动画对象的 start 方法启动动画。
  4. 动画循环 :在动画的持续时间内,系统会不断调用动画对象的 doAnimationFrame 方法,计算动画在不同时间点的值,并将其应用到对象的属性上。
  5. 动画结束:当动画的持续时间结束时,动画停止。

以下是一个简单的属性动画示例代码:

java 复制代码
// 创建一个 ObjectAnimator 对象,对 View 的 translationX 属性进行动画操作,从 0 到 200
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0, 200);
// 设置动画的持续时间为 1000 毫秒
animator.setDuration(1000);
// 启动动画
animator.start();

在上述代码中,首先创建了一个 ObjectAnimator 对象,然后设置了动画的持续时间,最后启动动画,对 ViewtranslationX 属性进行动画操作。

4.4 属性动画的优势

与视图动画相比,属性动画具有以下优势:

  • 改变对象的实际属性 :属性动画可以直接改变对象的属性值,动画结束后,对象的属性值会真正改变。例如,一个平移动画结束后,View 的实际位置会改变。
  • 可以对任何对象进行操作 :属性动画可以对任何对象的任何属性进行动画操作,不仅仅局限于 View 对象。
  • 动画效果丰富:属性动画可以实现更加复杂和灵活的动画效果,如 3D 变换、路径动画等。

五、帧动画原理

5.1 帧动画的基本概念

帧动画是通过依次显示一系列的图片来实现动画效果,类似于传统的动画片。在 Android 中,帧动画主要通过 AnimationDrawable 类来实现。

5.2 帧动画的关键类和方法

5.2.1 AnimationDrawable

AnimationDrawable 类是帧动画的核心类,它继承自 Drawable 类。以下是 AnimationDrawable 类的部分关键属性和方法的源码分析:

java 复制代码
// 帧动画的帧列表
private ArrayList<Frame> mFrames = new ArrayList<Frame>();
// 帧动画的当前帧索引
private int mCurFrame = 0;
// 帧动画是否正在运行
private boolean mRunning = false;
// 帧动画的回调接口
private Runnable mAnimationCallback;

// 添加一帧到帧动画中
public void addFrame(Drawable frame, int duration) {
    mFrames.add(new Frame(frame, duration));
}

// 开始帧动画
public void start() {
    if (!mRunning) {
        mRunning = true;
        mCurFrame = 0;
        scheduleNextFrame();
    }
}

// 停止帧动画
public void stop() {
    if (mRunning) {
        mRunning = false;
        unscheduleSelf(mAnimationCallback);
    }
}

// 调度下一帧
private void scheduleNextFrame() {
    if (mRunning) {
        Frame frame = mFrames.get(mCurFrame);
        int duration = frame.duration;
        if (mAnimationCallback == null) {
            mAnimationCallback = new Runnable() {
                @Override
                public void run() {
                    nextFrame();
                }
            };
        }
        scheduleSelf(mAnimationCallback, SystemClock.uptimeMillis() + duration);
    }
}

// 显示下一帧
private void nextFrame() {
    if (mRunning) {
        mCurFrame++;
        if (mCurFrame >= mFrames.size()) {
            mCurFrame = 0;
        }
        invalidateSelf();
        scheduleNextFrame();
    }
}

// 内部类,用于表示一帧
private static class Frame {
    Drawable frame;
    int duration;

    Frame(Drawable frame, int duration) {
        this.frame = frame;
        this.duration = duration;
    }
}

在上述代码中,AnimationDrawable 类定义了帧动画的帧列表、当前帧索引和是否正在运行等属性。addFrame 方法用于添加一帧到帧动画中,start 方法用于开始帧动画,stop 方法用于停止帧动画。scheduleNextFrame 方法用于调度下一帧,nextFrame 方法用于显示下一帧。

5.3 帧动画的执行流程

帧动画的执行流程主要包括以下几个步骤:

  1. 创建 AnimationDrawable 对象 :创建一个 AnimationDrawable 对象,用于表示帧动画。
  2. 添加帧 :通过 addFrame 方法将一系列的图片添加到 AnimationDrawable 对象中,并指定每帧的显示时间。
  3. 设置 AnimationDrawableView 的背景 :将 AnimationDrawable 对象设置为一个 View 的背景。
  4. 开始帧动画 :调用 AnimationDrawable 对象的 start 方法开始帧动画。
  5. 帧循环:系统会按照指定的时间间隔依次显示每一帧,直到动画停止。
  6. 停止帧动画 :调用 AnimationDrawable 对象的 stop 方法停止帧动画。

以下是一个简单的帧动画示例代码:

java 复制代码
// 创建一个 AnimationDrawable 对象
AnimationDrawable animationDrawable = new AnimationDrawable();
// 添加帧
animationDrawable.addFrame(getResources().getDrawable(R.drawable.frame1), 100);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.frame2), 100);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.frame3), 100);
// 设置为 View 的背景
View view = findViewById(R.id.my_view);
view.setBackground(animationDrawable);
// 开始帧动画
animationDrawable.start();

在上述代码中,首先创建了一个 AnimationDrawable 对象,然后添加了三帧图片,并设置了每帧的显示时间。接着将 AnimationDrawable 对象设置为一个 View 的背景,最后启动帧动画。

5.4 帧动画的局限性

帧动画虽然实现简单,但也存在一些局限性:

  • 内存占用大:帧动画需要将一系列的图片加载到内存中,如果图片数量较多或图片尺寸较大,会占用大量的内存。
  • 动画效果有限:帧动画只能通过依次显示图片来实现动画效果,对于一些复杂的动画效果,如渐变、变形等,无法实现。

六、动画插值器和估值器

6.1 插值器(Interpolator)

插值器用于控制动画的变化速率,它定义了动画在不同时间点的进度。Android 提供了多种内置的插值器,如 AccelerateDecelerateInterpolator(加速减速插值器)、LinearInterpolator(线性插值器)、AccelerateInterpolator(加速插值器)等。

6.1.1 插值器的接口定义

插值器的核心接口是 TimeInterpolator,它定义了一个 getInterpolation 方法,用于计算动画在指定时间点的进度。以下是 TimeInterpolator 接口的源码分析:

java 复制代码
public interface TimeInterpolator {
    // 根据输入的时间因子(0 到 1 之间)计算插值后的时间因子
    float getInterpolation(float input);
}

在上述代码中,getInterpolation 方法接受一个输入的时间因子(范围从 0 到 1),表示动画的当前时间进度,返回一个插值后的时间因子,用于控制动画的变化速率。

6.1.2 内置插值器的实现

以下是一些常见内置插值器的实现源码分析:

6.1.2.1 LinearInterpolator(线性插值器)
java 复制代码
public class LinearInterpolator implements TimeInterpolator {
    // 线性插值,直接返回输入的时间因子
    @Override
    public float getInterpolation(float input) {
        return input;
    }
}

在上述代码中,LinearInterpolator 实现了 TimeInterpolator 接口,getInterpolation 方法直接返回输入的时间因子,因此动画的变化速率是线性的。

6.1.2.2 AccelerateDecelerateInterpolator(加速减速插值器)
java 复制代码
public class AccelerateDecelerateInterpolator implements TimeInterpolator {
    // 加速减速插值,使用余弦函数计算插值后的时间因子
    @Override
    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }
}

在上述代码中,AccelerateDecelerateInterpolator 实现了 TimeInterpolator 接口,getInterpolation 方法使用余弦函数计算插值后的时间因子,动画在开始和结束时较慢,中间较快。

6.1.3 自定义插值器

开发者可以通过实现 TimeInterpolator 接口来创建自定义的插值器。以下是一个自定义插值器的示例代码:

java 复制代码
public class CustomInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float input) {
        // 自定义插值逻辑,例如使用二次函数计算插值后的时间因子
        return input * input;
    }
}

在上述代码中,自定义了一个插值器 CustomInterpolatorgetInterpolation 方法使用二次函数计算插值后的时间因子,动画的变化速率会随着时间的增加而加快。

6.2 估值器(TypeEvaluator)

估值器用于根据插值器计算出的进度,计算出对象属性在该进度下的值。Android 提供了多种内置的估值器,如 IntEvaluator(整数估值器)、FloatEvaluator(浮点数估值器)等。

6.2.1 估值器的接口定义

估值器的核心接口是 TypeEvaluator,它定义了一个 evaluate 方法,用于根据插值后的时间因子计算对象属性的值。以下是 TypeEvaluator 接口的源码分析:

java 复制代码
public interface TypeEvaluator<T> {
    // 根据插值后的时间因子、起始值和结束值计算对象属性的值
    T evaluate(float fraction, T startValue, T endValue);
}

在上述代码中,evaluate 方法接受一个插值后的时间因子(范围从 0 到 1)、起始值和结束值,返回对象属性在该进度下的值。

6.2.2 内置估值器的实现

以下是一些常见内置估值器的实现源码分析:

6.2.2.1 IntEvaluator(整数估值器)
java 复制代码
public class IntEvaluator implements TypeEvaluator<Integer> {
    // 根据插值后的时间因子、起始值和结束值计算整数属性的值
    @Override
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

在上述代码中,IntEvaluator 实现了 TypeEvaluator 接口,evaluate 方法根据插值后的时间因子、起始值和结束值计算整数属性的值。

6.2.2.2 FloatEvaluator(浮点数估值器)
java 复制代码
public class FloatEvaluator implements TypeEvaluator<Float> {
    // 根据插值后的时间因子、起始值和结束值计算浮点数属性的值
    @Override
    public Float evaluate(float fraction, Float startValue, Float endValue) {
        float startFloat = startValue;
        return startFloat + fraction * (endValue - startFloat);
    }
}

在上述代码中,FloatEvaluator 实现了 TypeEvaluator 接口,evaluate 方法根据插值后的时间因子、起始值和结束值计算浮点数属性的值。

6.2.3 自定义估值器

开发者可以通过实现 TypeEvaluator 接口来创建自定义的估值器。下面以自定义一个颜色估值器为例,实现颜色在动画过程中的平滑过渡。

java 复制代码
import android.animation.TypeEvaluator;
import android.graphics.Color;

// 自定义颜色估值器
public class ColorEvaluator implements TypeEvaluator<Integer> {

    // 该方法根据 fraction 计算出在 startValue 和 endValue 之间过渡的颜色值
    @Override
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        // 提取起始颜色的红、绿、蓝分量
        int startRed = Color.red(startValue);
        int startGreen = Color.green(startValue);
        int startBlue = Color.blue(startValue);

        // 提取结束颜色的红、绿、蓝分量
        int endRed = Color.red(endValue);
        int endGreen = Color.green(endValue);
        int endBlue = Color.blue(endValue);

        // 根据 fraction 计算过渡颜色的红、绿、蓝分量
        int red = (int) (startRed + fraction * (endRed - startRed));
        int green = (int) (startGreen + fraction * (endGreen - startGreen));
        int blue = (int) (startBlue + fraction * (endBlue - startBlue));

        // 组合红、绿、蓝分量成一个颜色值
        return Color.rgb(red, green, blue);
    }
}

在上述代码中,ColorEvaluator 实现了 TypeEvaluator<Integer> 接口,evaluate 方法接收三个参数:fraction 表示当前动画的进度(取值范围是 0 到 1),startValue 是起始颜色,endValue 是结束颜色。通过分别计算红、绿、蓝三个分量在当前进度下的值,最后组合成一个新的颜色值返回。

下面是使用自定义颜色估值器的示例代码:

java 复制代码
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.textView);

        // 创建一个 ObjectAnimator 对象,对 textView 的 backgroundColor 属性进行动画操作
        ObjectAnimator animator = ObjectAnimator.ofObject(textView, "backgroundColor", new ColorEvaluator(), Color.RED, Color.BLUE);
        animator.setDuration(2000); // 设置动画持续时间为 2 秒
        animator.start(); // 启动动画
    }
}

在这个示例中,我们创建了一个 ObjectAnimator 对象,使用自定义的 ColorEvaluatorTextView 的背景颜色进行从红色到蓝色的动画过渡。

6.3 插值器和估值器在动画中的协同工作

插值器和估值器在 Android 动画中协同工作,共同实现动画的各种效果。插值器负责控制动画的变化速率,而估值器负责根据插值器计算出的进度,计算出对象属性在该进度下的具体值。

下面是一个简单的示例,展示了插值器和估值器在 ValueAnimator 中的协同工作:

java 复制代码
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class InterpolatorEvaluatorExample extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_interpolator_evaluator);

        textView = findViewById(R.id.textView);

        // 创建一个 ValueAnimator 对象,指定动画的起始值和结束值
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
        animator.setDuration(2000); // 设置动画持续时间为 2 秒

        // 设置插值器,使用加速减速插值器
        animator.setInterpolator(new android.view.animation.AccelerateDecelerateInterpolator());

        // 设置动画更新监听器
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 获取当前动画的值
                float value = (float) animation.getAnimatedValue();
                // 将值设置到 TextView 上显示
                textView.setText(String.valueOf(value));
            }
        });

        animator.start(); // 启动动画
    }
}

在上述代码中,ValueAnimator 负责生成从 0 到 100 的一系列值,AccelerateDecelerateInterpolator 作为插值器控制动画的变化速率,动画在开始和结束时较慢,中间较快。在动画更新监听器中,我们通过 getAnimatedValue 方法获取当前动画的值,并将其显示在 TextView 上。

七、动画的监听和回调

7.1 动画监听器接口

Android 提供了多个动画监听器接口,用于监听动画的不同阶段,开发者可以通过实现这些接口来处理动画的开始、结束、取消和重复等事件。

7.1.1 Animator.AnimatorListener 接口

Animator.AnimatorListener 接口定义了四个方法,分别用于监听动画的开始、结束、取消和重复事件。以下是该接口的源码分析:

java 复制代码
public static interface AnimatorListener {
    // 当动画开始时调用
    void onAnimationStart(Animator animation);
    // 当动画结束时调用
    void onAnimationEnd(Animator animation);
    // 当动画被取消时调用
    void onAnimationCancel(Animator animation);
    // 当动画重复时调用
    void onAnimationRepeat(Animator animation);
}

开发者可以通过实现 Animator.AnimatorListener 接口来监听动画的不同阶段,并在相应的方法中添加自定义的逻辑。

7.1.2 Animator.AnimatorPauseListener 接口

Animator.AnimatorPauseListener 接口定义了两个方法,分别用于监听动画的暂停和恢复事件。以下是该接口的源码分析:

java 复制代码
public static interface AnimatorPauseListener {
    // 当动画暂停时调用
    void onAnimationPause(Animator animation);
    // 当动画恢复时调用
    void onAnimationResume(Animator animation);
}

开发者可以通过实现 Animator.AnimatorPauseListener 接口来监听动画的暂停和恢复事件,并在相应的方法中添加自定义的逻辑。

7.2 动画监听器的使用示例

以下是一个使用 Animator.AnimatorListener 接口的示例代码:

java 复制代码
import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class AnimationListenerExample extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation_listener);

        textView = findViewById(R.id.textView);

        // 创建一个 ObjectAnimator 对象,对 textView 的 translationX 属性进行动画操作
        ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "translationX", 0f, 200f);
        animator.setDuration(2000); // 设置动画持续时间为 2 秒

        // 添加动画监听器
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                // 动画开始时,在日志中输出信息
                android.util.Log.d("Animation", "Animation started");
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                // 动画结束时,在日志中输出信息
                android.util.Log.d("Animation", "Animation ended");
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                // 动画取消时,在日志中输出信息
                android.util.Log.d("Animation", "Animation cancelled");
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                // 动画重复时,在日志中输出信息
                android.util.Log.d("Animation", "Animation repeated");
            }
        });

        animator.start(); // 启动动画
    }
}

在上述代码中,我们创建了一个 ObjectAnimator 对象,对 TextViewtranslationX 属性进行动画操作。通过 addListener 方法添加了一个 Animator.AnimatorListener 监听器,在动画的开始、结束、取消和重复事件发生时,分别在日志中输出相应的信息。

7.3 动画更新监听器

除了上述的动画监听器接口,Android 还提供了 ValueAnimator.AnimatorUpdateListener 接口,用于监听动画的更新事件。该接口定义了一个 onAnimationUpdate 方法,在动画的每一帧更新时都会调用。以下是该接口的源码分析:

java 复制代码
public static interface AnimatorUpdateListener {
    // 当动画更新时调用
    void onAnimationUpdate(ValueAnimator animation);
}

以下是一个使用 ValueAnimator.AnimatorUpdateListener 接口的示例代码:

java 复制代码
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class AnimationUpdateListenerExample extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation_update_listener);

        textView = findViewById(R.id.textView);

        // 创建一个 ValueAnimator 对象,指定动画的起始值和结束值
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
        animator.setDuration(2000); // 设置动画持续时间为 2 秒

        // 添加动画更新监听器
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 获取当前动画的值
                float value = (float) animation.getAnimatedValue();
                // 将值设置到 TextView 上显示
                textView.setText(String.valueOf(value));
            }
        });

        animator.start(); // 启动动画
    }
}

在上述代码中,我们创建了一个 ValueAnimator 对象,对一个浮点数进行从 0 到 100 的动画操作。通过 addUpdateListener 方法添加了一个 ValueAnimator.AnimatorUpdateListener 监听器,在动画的每一帧更新时,获取当前动画的值并显示在 TextView 上。

八、动画的性能优化

8.1 避免频繁创建动画对象

在 Android 开发中,频繁创建动画对象会消耗大量的内存和 CPU 资源,影响应用的性能。因此,建议在需要使用动画的地方,尽量复用已经创建好的动画对象。

以下是一个示例代码,展示了如何复用动画对象:

java 复制代码
import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class AnimationReuseExample extends AppCompatActivity {

    private TextView textView;
    private ObjectAnimator animator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation_reuse);

        textView = findViewById(R.id.textView);
        Button startButton = findViewById(R.id.startButton);

        // 创建一个 ObjectAnimator 对象,对 textView 的 translationX 属性进行动画操作
        animator = ObjectAnimator.ofFloat(textView, "translationX", 0f, 200f);
        animator.setDuration(2000); // 设置动画持续时间为 2 秒

        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 复用已经创建好的动画对象
                animator.start();
            }
        });
    }
}

在上述代码中,我们在 onCreate 方法中创建了一个 ObjectAnimator 对象,在按钮的点击事件中复用该动画对象,避免了频繁创建动画对象。

8.2 合理设置动画的帧率

Android 系统的默认帧率是 60fps,即每秒更新 60 帧。如果动画的帧率过高,会消耗大量的 CPU 资源,导致应用卡顿;如果帧率过低,动画会显得不流畅。因此,需要根据实际情况合理设置动画的帧率。

在属性动画中,可以通过 ValueAnimator.setFrameDelay 方法设置动画的帧延迟时间,从而间接控制动画的帧率。以下是一个示例代码:

java 复制代码
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class AnimationFrameRateExample extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation_frame_rate);

        textView = findViewById(R.id.textView);

        // 创建一个 ValueAnimator 对象,指定动画的起始值和结束值
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
        animator.setDuration(2000); // 设置动画持续时间为 2 秒

        // 设置动画的帧延迟时间为 20 毫秒,即帧率约为 50fps
        animator.setFrameDelay(20);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 获取当前动画的值
                float value = (float) animation.getAnimatedValue();
                // 将值设置到 TextView 上显示
                textView.setText(String.valueOf(value));
            }
        });

        animator.start(); // 启动动画
    }
}

在上述代码中,我们通过 setFrameDelay 方法将动画的帧延迟时间设置为 20 毫秒,即帧率约为 50fps。

8.3 避免在动画中进行大量的计算

在动画的更新过程中,避免进行大量的计算,以免影响动画的流畅性。如果需要进行复杂的计算,可以提前计算好结果,在动画更新时直接使用。

以下是一个示例代码,展示了如何避免在动画中进行大量的计算:

java 复制代码
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class AnimationCalculationExample extends AppCompatActivity {

    private TextView textView;
    private float[] preCalculatedValues;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_animation_calculation);

        textView = findViewById(R.id.textView);

        // 提前计算好一系列的值
        preCalculatedValues = new float[100];
        for (int i = 0; i < 100; i++) {
            preCalculatedValues[i] = i * 2;
        }

        // 创建一个 ValueAnimator 对象,指定动画的起始值和结束值
        ValueAnimator animator = ValueAnimator.ofInt(0, 99);
        animator.setDuration(2000); // 设置动画持续时间为 2 秒

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 获取当前动画的索引
                int index = (int) animation.getAnimatedValue();
                // 直接使用提前计算好的值
                float value = preCalculatedValues[index];
                // 将值设置到 TextView 上显示
                textView.setText(String.valueOf(value));
            }
        });

        animator.start(); // 启动动画
    }
}

在上述代码中,我们在 onCreate 方法中提前计算好了一系列的值,存储在 preCalculatedValues 数组中。在动画更新时,直接根据当前动画的索引从数组中获取值,避免了在动画更新过程中进行大量的计算。

8.4 使用硬件加速

Android 系统提供了硬件加速功能,可以利用 GPU 来加速动画的渲染,提高动画的性能。可以通过在 AndroidManifest.xml 文件中为应用或特定的 Activity 设置 android:hardwareAccelerated="true" 来开启硬件加速。

以下是一个示例 AndroidManifest.xml 文件:

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.animationexample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:hardwareAccelerated="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

在上述代码中,我们在 <application> 标签中设置了 android:hardwareAccelerated="true",表示为整个应用开启硬件加速。

九、总结与展望

9.1 总结

本文从源码级别深入分析了 Android View 动画的原理,涵盖了视图动画、属性动画和帧动画三种主要的动画类型。

视图动画通过对 View 的内容进行平移、缩放、旋转和透明度变化等操作来实现动画效果,其核心是 Animation 类及其子类,通过重写 applyTransformation 方法来计算动画在不同时间点的变换矩阵。视图动画使用简单,但存在不改变 View 实际属性、只能对 View 进行操作和动画效果有限等局限性。

属性动画是 Android 3.0 引入的一种更强大的动画机制,它可以直接改变对象的属性值,实现更加灵活和复杂的动画效果。属性动画的核心类是 ValueAnimatorObjectAnimator,通过插值器和估值器来控制动画的变化速率和属性值的计算。属性动画可以对任何对象的任何属性进行动画操作,并且会真正改变对象的属性值。

帧动画通过依次显示一系列的图片来实现动画效果,主要通过 AnimationDrawable 类来实现。帧动画实现简单,但存在内存占用大、动画效果有限等局限性。

此外,我们还介绍了动画插值器和估值器的原理和使用方法,以及动画的监听和回调机制。最后,提出了一些动画性能优化的建议,如避免频繁创建动画对象、合理设置动画的帧率、避免在动画中进行大量的计算和使用硬件加速等。

9.2 展望

随着 Android 技术的不断发展,动画技术也将不断进步和完善。未来,Android 动画可能会朝着以下几个方向发展:

9.2.1 更加智能和自动化的动画

未来的 Android 动画可能会引入更多的智能算法和机器学习技术,能够根据用户的操作习惯和场景自动生成合适的动画效果。例如,当用户进行某种操作时,系统可以自动判断并生成与之匹配的动画过渡效果,提高用户体验。

9.2.2 跨平台动画框架的融合

随着移动开发的多元化,跨平台开发框架越来越受到开发者的青睐。未来,Android 动画可能会与跨平台动画框架进行更深入的融合,实现一套代码在多个平台上都能运行的动画效果,提高开发效率。

9.2.3 与虚拟现实(VR)和增强现实(AR)技术的结合

VR 和 AR 技术在移动应用中的应用越来越广泛,未来的 Android 动画可能会与这些技术紧密结合,为用户带来更加沉浸式的体验。例如,在 VR 场景中实现更加逼真的动画效果,或者在 AR 应用中实现与现实场景交互的动画效果。

9.2.4 动画性能的进一步提升

随着硬件技术的不断发展,Android 系统的性能也会不断提升。未来的 Android 动画可能会进一步优化性能,减少内存占用和 CPU 消耗,实现更加流畅和复杂的动画效果。

总之,深入理解 Android View 动画的原理对于开发者来说至关重要。通过不断学习和实践,开发者能够更好地运用动画技术,为用户带来更加优质、流畅和富有创意的应用体验。同时,关注动画技术的发展趋势,不断探索新的应用场景和技术方法,将有助于开发者在未来的移动开发领域中保持竞争力。

相关推荐
RichardLai884 分钟前
[Flutter 基础] - Flutter基础组件 - Image
android·flutter
一杯凉白开13 分钟前
虽然我私生活很混乱,但是我码德很好-多线程竞态条件bug寻找之旅
android
小小年纪不学好19 分钟前
【60.组合总和】
java·算法·面试
科昂21 分钟前
Dart 异步编程:轻松掌握 Future 的核心用法
android·flutter·dart
揭开画皮21 分钟前
8.Android(通过Manifest配置文件传递数据(meta-data))
android
LiuShangYuan22 分钟前
Moshi原理分析
android
前行的小黑炭27 分钟前
Android 消息队列之MQTT的使用:物联网通讯,HTTP太重了,使用MQTT;订阅、发送数据和接受数据、会话+消息过期机制,实现双向通讯。
android
我是哪吒30 分钟前
分布式微服务系统架构第122集:NestJS是一个用于构建高效、可扩展的服务器端应用程序的开发框架
前端·后端·面试
一个热爱生活的普通人36 分钟前
如何用go语言实现类似AOP的功能
后端·面试·go
lqstyle36 分钟前
Redis地理相亲事务所:Geo/Bitmap/HLL底层脱单算法全解
后端·面试