Android Property Animation 介绍(三)

这篇文章想要从源码的角度来介绍下Property Animation。先来看下相关类的继承结构

134203_dKqV_134491.jpg

Animator是一个抽象类,他提供了基础方法 start、cancel、end、pause、resume等,还定义了2个监听器,AnimatorListener和AnimatorPauseListener。Animator类没有具体动画逻辑的实现,具体实现由子类来完成

AnimatorSet定义了一组动画的实现逻辑,这个在之后的文章中再详细介绍。下面主要介绍下ValueAnimator类。

通过类的名称就可以知道这个类是显示对某个值的动画变化。其实现动画的原理有以下几个步骤:

1、动画的对象初始化

2、启动动画后,会获取一个AnimationHandler,不断的轮询来计算value在这一时刻的值

3、计算value值由2个步骤,先通过interpolator算出一个interpolated fraction。

4、在通过TypeEvaluator来计算出真实的属性值

5、真实的值计算出来后再调用监听器updateListeners通知更新属性。

下面来具体介绍下每个步骤

1、动画对象的初始化

ValueAnimator对象提供了很多种的方法来初始化动画的相关数据:ofInt、ofArgb、ofFalot、ofPropertyValueHolder、ofObject。

初始化主要是设置了动画关键的帧对应的value值。以ofInt方法为例:

  public static ValueAnimator ofInt(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        return anim;
    }
 public void setIntValues(int... values) {
        if (values == null || values.length == 0) {
            return;
        }
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofInt("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setIntValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

要设置的value值实际上是要存储在PropertyValuesHolder中。在来看看PropertyValueHolder中的实现:

 public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframes = KeyframeSet.ofInt(values);
    }
public static KeyframeSet ofInt(int... values) {
        int numKeyframes = values.length;
        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
        } else {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] =
                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new IntKeyframeSet(keyframes);
    }

可以看到PropertyValueHolder存储了value的类型valueType为int.class和KeyframeSet,value值真正是存在keyframeSet中的,也就是一个帧对象的集合。先来了解下帧对象keyframe

public abstract class Keyframe implements Cloneable {
   
    boolean mHasValue;
    boolean mValueWasSetOnStart;
    float mFraction;
    Class mValueType;
    private TimeInterpolator mInterpolator = null;

Keyframe是一个抽象类,定义了几个参数

mHasValue:是否有值

mValueWasSetOnStart:onstart时是否设置了值

mFraction:该帧对应时间片段(0-1的一个数)

mValueType:值的类型

mInterpolation:插值器

他的子类有IntKeyframe、FloatKeyframe、ObjectKetframe。子类并没有什么而外的实现,只是确定了具体值的类型。

再来看下KeyframeSet中的ofInt方法。帧集合最少2帧,每一帧的对应的时间片段是0-1等比例分的一个值。

这样初始化的时候就把关键的一些帧对应的值给设置的,通常是设置第一帧和最后一帧。

ofArgb和ofObject方法除了设置value值外还设置了TypeEvaluator。

2、启动动画

public void start() {
        start(false);
    }
 private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        mPaused = false;
        updateScaledDuration(); // in case the scale factor has changed since creation time
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            setCurrentPlayTime(0);
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

通过以上代码可以看到启动动画的时候除了做相关属性的初始化,还有2个关键点获取AnimationHandler和AnimationHandler的启动

1、AnimationHandler的获取

private static AnimationHandler getOrCreateAnimationHandler() {
        AnimationHandler handler = sAnimationHandler.get();
        if (handler == null) {
            handler = new AnimationHandler();
            sAnimationHandler.set(handler);
        }
        return handler;
    }

    可以看到是通过sAnimationHandler获取的AnimationHandler,sAnimationHandler是一个ThreadLocal<AnimationHandler>对象,意味着同一个线程中共享一个animationHandler对象。

2、AnimationHandler的启动

 public void start() {
            scheduleAnimation();
        }
private void scheduleAnimation() {
            if (!mAnimationScheduled) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            }
        }

可以看到是通过choreographer的回掉来执行AnimationHandler的run方法,关于choreographer这个类比较复杂了,是android用于绘制界面的消息处理器,想了解的可以看下这篇文章

http://blog.youkuaiyun.com/farmer_cc/article/details/18619429

3、动画绘制

在AnimationHandler中定义了多种动画列表我们先来了解下

mPendingAnimations:下一帧要启动的动画,这类的动画在调用启动方法时加入

private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mPlayingBackwards = playBackwards;
        mCurrentIteration = 0;
        mPlayingState = STOPPED;
        mStarted = true;
        mStartedDelay = false;
        mPaused = false;
        updateScaledDuration(); // in case the scale factor has changed since creation time
        AnimationHandler animationHandler = getOrCreateAnimationHandler();
        animationHandler.mPendingAnimations.add(this);
        if (mStartDelay == 0) {
            // This sets the initial value of the animation, prior to actually starting it running
            setCurrentPlayTime(0);
            mPlayingState = STOPPED;
            mRunning = true;
            notifyStartListeners();
        }
        animationHandler.start();
    }

mAnimations:正在执行的动画,在动画真正开始执行时加入

private void startAnimation(AnimationHandler handler) {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                    System.identityHashCode(this));
        }
        initAnimation();
        handler.mAnimations.add(this);
        if (mStartDelay > 0 && mListeners != null) {
            // Listeners were already notified in start() if startDelay is 0; this is
            // just for delayed animations
            notifyStartListeners();
        }
    }

mDelayAnims、EndingAnims、mReadyAnims:分别是延迟的动画、结束的动画、准备中的动画

延迟动画是指还未到延迟时间的动画,准备中的动画是指已经超过延迟时间的动画,结束动画是指动画已结束的动画。

具体的类型的动画通过以下的代码来区分

private void doAnimationFrame(long frameTime) {
            // mPendingAnimations holds any animations that have requested to be started
            // We're going to clear mPendingAnimations, but starting animation may
            // cause more to be added to the pending list (for example, if one animation
            // starting triggers another starting). So we loop until mPendingAnimations
            // is empty.
            while (mPendingAnimations.size() > 0) {
                ArrayList<ValueAnimator> pendingCopy =
                        (ArrayList<ValueAnimator>) mPendingAnimations.clone();
                mPendingAnimations.clear();
                int count = pendingCopy.size();
                for (int i = 0; i < count; ++i) {
                    ValueAnimator anim = pendingCopy.get(i);
                    // If the animation has a startDelay, place it on the delayed list
                    if (anim.mStartDelay == 0) {
                        anim.startAnimation(this);
                    } else {
                        mDelayedAnims.add(anim);
                    }
                }
            }
            // Next, process animations currently sitting on the delayed queue, adding
            // them to the active animations if they are ready
            int numDelayedAnims = mDelayedAnims.size();
            for (int i = 0; i < numDelayedAnims; ++i) {
                ValueAnimator anim = mDelayedAnims.get(i);
                if (anim.delayedAnimationFrame(frameTime)) {
                    mReadyAnims.add(anim);
                }
            }
            int numReadyAnims = mReadyAnims.size();
            if (numReadyAnims > 0) {
                for (int i = 0; i < numReadyAnims; ++i) {
                    ValueAnimator anim = mReadyAnims.get(i);
                    anim.startAnimation(this);
                    anim.mRunning = true;
                    mDelayedAnims.remove(anim);
                }
                mReadyAnims.clear();
            }

            // Now process all active animations. The return value from animationFrame()
            // tells the handler whether it should now be ended
            int numAnims = mAnimations.size();
            for (int i = 0; i < numAnims; ++i) {
                mTmpAnimations.add(mAnimations.get(i));
            }
            for (int i = 0; i < numAnims; ++i) {
                ValueAnimator anim = mTmpAnimations.get(i);
                if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
                    mEndingAnims.add(anim);
                }
            }
            mTmpAnimations.clear();
            if (mEndingAnims.size() > 0) {
                for (int i = 0; i < mEndingAnims.size(); ++i) {
                    mEndingAnims.get(i).endAnimation(this);
                }
                mEndingAnims.clear();
            }

            // If there are still active or delayed animations, schedule a future call to
            // onAnimate to process the next frame of the animations.
            if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
                scheduleAnimation();
            }
        }

只有延迟时间为0的或是准备中的动画才会执行动画的startAnimation方法启动动画,如果还有未处理的动画还会执行scheduleAnimation()方法重新调用doAnimationFrame()方法来判断。

通过调用anim.doAnimationFrame(frameTime)方法来计算这个时刻的属性值

void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }
void calculateValue(float fraction) {
        Object value = mKeyframes.getValue(fraction);
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

先通过Interpolation获取相应的值,再mKeyframe的getValue方法获取通过TypeEvaluator计算出属性的值,

并通过updateListener通知动画更新。

转载于:https://my.oschina.net/u/134491/blog/340647

### 使用人工智能进行MIMO信道估计的方法研究 #### 深度学习在MIMO信道估计中的应用背景 为了评估模型在不同信道环境下的适应性,可以设计一系列信道模型,包括高斯信道、瑞利衰落信道、莱斯衰落信道等,并在每种信道模型下测试模型的性能。深度学习模型需要证明其在各种信道模型下都有良好的估计性能[^1]。 #### 基于深度学习的时频空联合信道外推方法 大规模MIMO系统的时频空联合信道外推方法被深入研究,分析了深度学习在此类问题上的优势。根据不同应用场景下的信道外推原理,探讨了各个场景下基于深度学习的信道外推问题及其共性问题。利用信道数据在时频空域的全局相关性,提出了一种基于卷积神经网络(CNN)的时频空域联合信道外推方法。该方法还特别关注多路并行上行到下行信道预测,在此背景下设计了专门用于多域联合信道外推的神经网络架构[^2]。 ```python import tensorflow as tf from tensorflow.keras import layers, models def create_cnn_model(input_shape): model = models.Sequential() model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=input_shape)) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Flatten()) model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(1)) return model ``` #### 实验验证比较 实验结果显示,无论是单一信道还是整体系统表现,实用估计器和神经网络估计器均超越传统线性插值技术。具体而言,通过MATLAB编写代码并执行在线CNN训练流程,最终得到各类信道估计的平均平方误差(MSE),并通过图表直观展示单个信道估计效果及实际信道响应情况[^3]。 #### 稀疏贝叶斯学习框架时变参数追踪 一种创新性的解决方案是采用稀疏贝叶斯学习(SBL)框架结合降维卡尔曼滤波算法来处理时变特性显著的大规模MIMO信道建模难题。这种方法不仅能够有效捕捉信道随时间的变化规律,而且提高了计算效率和精度[^4]。 #### CSI反馈机制优化 针对无线通信中至关重要的信道状态信息(CSI)反馈环节,有研究表明可以通过构建特定类型的深度学习模型——如CSiNet——来进行高效压缩感知编码,从而减少传输开销的同时保持较高的重建质量[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值