Android属性动画(Animator)是如何实现的

2024-03-13 14:58

本文主要是介绍Android属性动画(Animator)是如何实现的,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        与补间动画不同,属性动画是对对象的属性进行修改的,主要涉及到ValueAnimator和ObjectAnimator,其中ObjectAnimator又是继承自ValueAnimator,所以这里从ValueAnimator入手,首先来看一下ValueAnimator的简单用法:

 

private void testValueAnimator(){ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,100);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float animatedFraction = animation.getAnimatedFraction();float value = (float) animation.getAnimatedValue();Log.d(TAG, "onAnimationUpdate: = "+animatedFraction+"    value = "+value);}});valueAnimator.start();
}

再来看下输出内容:

 

06-07 11:37:33.377 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.0    value = 0.0
06-07 11:37:33.402 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.0    value = 0.0
06-07 11:37:33.444 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.031359017    value = 3.1359017
06-07 11:37:33.526 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.33063102    value = 33.063103
06-07 11:37:33.535 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.42178285    value = 42.178284
06-07 11:37:33.551 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.5052359    value = 50.52359
06-07 11:37:33.568 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.5936906    value = 59.369057
06-07 11:37:33.585 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.67918396    value = 67.918396
06-07 11:37:33.602 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.7590136    value = 75.90136
06-07 11:37:33.618 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.82671034    value = 82.671036
06-07 11:37:33.635 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.88857293    value = 88.85729
06-07 11:37:33.652 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.93815327    value = 93.81532
06-07 11:37:33.668 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.9738842    value = 97.38842
06-07 11:37:33.685 29237-29237/? D/MainActivity: onAnimationUpdate: = 0.99384415    value = 99.384415
06-07 11:37:33.702 29237-29237/? D/MainActivity: onAnimationUpdate: = 1.0    value = 100.0

有没有发现,后面的值就是前面的值乘以100,这个100就是我们前面设置的。那这个内部是怎么实现的呢?那这里我们就从start()这个方法开始入手:

 

@Override
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");}......//看意思应该是动画的回调addAnimationCallback(0);......if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {// If there's no start delay, init the animation and notify start listeners right away// to be consistent with the previous behavior. Otherwise, postpone this until the first// frame after the start delay.startAnimation();if (mSeekFraction == -1) {// No seek, start at play time 0. Note that the reason we are not using fraction 0// is because for animations with 0 duration, we want to be consistent with pre-N// behavior: skip to the final value immediately.setCurrentPlayTime(0);} else {setCurrentFraction(mSeekFraction);}}
}

这里先执行了一个动画的回调addAnimatorCallback(0),这个回调里具体做了些什么呢?进去看看:

 

private void addAnimationCallback(long delay) {if (!mSelfPulse) {return;}getAnimationHandler().addAnimationFrameCallback(this, delay);
}
public AnimationHandler getAnimationHandler() {return AnimationHandler.getInstance();
}

这里是先获取到了一个单列对象AnimationHandler,看字面上的意思应该是处理动画的,而addAnimationFrameCallback()从方法名上猜测应该是每一次帧动画执行的时候回调用到,这里回调的接口是AnimationHandler.AnimationFrameCallback,ValueAnimation本身是实现了这个接口的,所以这里传进去的是ValuesAnimator本身,我们先来看看这个接口的两个方法:

 

interface AnimationFrameCallback {//这里是动画的执行逻辑实现boolean doAnimationFrame(long frameTime);void commitAnimationFrame(long frameTime);
}

这里有一个概念需要先了解,Android的屏幕刷新是每隔16ms,当这个时间到来时,可以看做是动画执行了一帧,不过这个信号并不是每次都会传递到应用层,而是要我们去系统层注册,每注册一次,下一次的屏幕刷新就会传递到这个应用。现在再来说说这个接口的作用,doAnimationFrame()主要就是计算一帧动画所需要的参数,当屏幕刷新信号到来时可以执行一帧动画。

    弄清这个接口后,还是跟着主线继续往下看,现在该是到AnimationHandle的addAnimationFrameCallback():

 

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {// 这里是一个单列对象,mAnimationCallBacks为0意味着没有动画在执行的时候,这时候就需要我们去// 系统层注册,下次屏幕刷新时就会调用到mFrameCallbakif (mAnimationCallbacks.size() == 0) {getProvider().postFrameCallback(mFrameCallback);}// 这里是判断集合中是否含有这个回调,如果含有就不会再次添加进去,也就是说这个动画还在执行就// 不会添加进去,不过如果在这个动画还在执行的时候再次开启这个动画,那么这个动画会从初始状态// 再次开始执行if (!mAnimationCallbacks.contains(callback)) {mAnimationCallbacks.add(callback);}if (delay > 0) {mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));}
}

这里就不去看屏幕刷新时是怎么回调到这里的,接下来要看的是屏幕刷新时回调到的这个对象mFrameCallback:

 

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {@Overridepublic void doFrame(long frameTimeNanos) {doAnimationFrame(getProvider().getFrameTime());// mAnimationCallbacks不为0的时候说明还有动画没执行完,就会再去底层注册请求下次屏幕刷新时// 回调到这里,直到不再有动画执行if (mAnimationCallbacks.size() > 0) {getProvider().postFrameCallback(this);}}
};

doAnimationFrame()是执行一帧动画,如果动画没执行完成,那就再去注册屏幕刷新信号,所以这里主要看的就是doAnimationFrame():

 

private void doAnimationFrame(long frameTime) {long currentTime = SystemClock.uptimeMillis();final int size = mAnimationCallbacks.size();for (int i = 0; i < size; i++) {//当动画执行完之后,会将对应的动画回调接口置为nullfinal AnimationFrameCallback callback = mAnimationCallbacks.get(i);if (callback == null) {continue;}//这里就是判断当前时间是否可以开始执行动画了,有些动画的执行是有延迟时间的if (isCallbackDue(callback, currentTime)) {//这里的这个callback就是从ValueAnimator中传进来的callback.doAnimationFrame(frameTime);......}}//这里就是清除执行完的动画的回调cleanUpList();
}

在我们进入到AnimationHandler的时候传进来一个回调接口,这里就用到了这个传进来的回调接口,所以这里callback.doAnimationFrame()实际就又回到了ValueAnimator,到这,就可以总结一下AnimationHandler这个类的作用了:

    1、会收集应用层所有注册动画的回调;

    2、有去底层注册屏幕刷新信号的功能,每注册一次,下一次屏幕刷新就会回调到这里;

    3、当屏幕信号到来时,会执行所收集的动画回调,如果动画没执行完,就会再次去注册屏幕刷新信号。

接下来就该看看回调里到底做了些什么,看ValueAnimator的doAnimationFrame()方法:

 

public final boolean doAnimationFrame(long frameTime) {......// 主要就是根据是否需要延迟执行动画还有一些其他的条件来确定mRunning的值,我这里的理解就是// 是否执行了startAnimation()这个方法,执行了mRunning就是true,没有执行就是falseif (!mRunning) {if (mStartTime > frameTime && mSeekFraction == -1) {return false;} else {mRunning = true;startAnimation();}}......final long currentTime = Math.max(frameTime, mStartTime);//这里就是根据当前时间来计算动画执行所需要的相关值以及确定动画是否执行完成boolean finished = animateBasedOnTime(currentTime);if (finished) {//1、动画执行完了就会执行到这里,在这个方法里主要是对一些变量复位//2、动画执行完后的回调//3、置null在AnimationHandler中添加的回调endAnimation();}return finished;
}

这里分三步来看:

    1、动画是否初始化了,startAnimation()就是对动画进行初始化;

    2、计算动画执行的相关数据;

    3、动画是否执行完了

      这里先看下startAnimation(),这个方法在一开始调用start()时,里面就有判断这个方法是否需要执行,正好这里就一起看下:

 

private void startAnimation() {......mAnimationEndRequested = false;//初始化动画相关数据initAnimation();//这里就对mRunning赋值为truemRunning = true;......
}
void initAnimation() {if (!mInitialized) {int numValues = mValues.length;for (int i = 0; i < numValues; ++i) {//创建ValueAnimator的时候传进来的数值就是在这里初始化mValues[i].init();}mInitialized = true;}
}

主要是初始化动画相关的数据,而且看到这里对mRunning赋值为true。

    动画相关的计算比较赋值,这里就放到最后来看,先来看下动画结束时执行的逻辑endAnimation():

 

private void endAnimation() {if (mAnimationEndRequested) {return;}//这里就是将添加到AnimationHandler中的回调置nullremoveAnimationCallback();......if (notify && mListeners != null) {ArrayList<AnimatorListener> tmpListeners =(ArrayList<AnimatorListener>) mListeners.clone();int numListeners = tmpListeners.size();for (int i = 0; i < numListeners; ++i) {//如果添加了动画执行完成的回调,那在这里会执行tmpListeners.get(i).onAnimationEnd(this, mReversing);}}......
}
private void removeAnimationCallback() {if (!mSelfPulse) {return;}getAnimationHandler().removeCallback(this);
}
public void removeCallback(AnimationFrameCallback callback) {mCommitCallbacks.remove(callback);mDelayedCallbackStartTime.remove(callback);int id = mAnimationCallbacks.indexOf(callback);if (id >= 0) {mAnimationCallbacks.set(id, null);mListDirty = true;}
}

这里贴出的主要是动画执行完后,AnimationHandler中添加的回调是如何置null的,整个流程还是比较简单的,还记得在AnimationHandler中doAnimationFrame()个方法中就有对回调事件的判null,如果为null就会在集合中清掉。

    接下来就是动画执行数据的分析了,这里就来到了animateBaseOnTime()这个方法了:

 

boolean animateBasedOnTime(long currentTime) {boolean done = false;//动画初始化后这里mRunning为trueif (mRunning) {......// 前面省略的代码主要就是在计算currentIterationFraction这个值// 这个值是根据时间来计算的,很重要,后面具体值的计算就是根据// 这个来计算的,这个值的取值范围是在0和1之间animateValue(currentIterationFraction);}//动画执行完这里放回true,没执行完返回falsereturn done;
}
void animateValue(float fraction) {//mInterpolation是插值器,如果没有设置就会使用默认的fraction = mInterpolator.getInterpolation(fraction);mCurrentFraction = fraction;int numValues = mValues.length;for (int i = 0; i < numValues; ++i) {//mValues中存储的就是创建ValueAnimator时传进来的数值,mValues[i].calculateValue(fraction);}if (mUpdateListeners != null) {int numListeners = mUpdateListeners.size();for (int i = 0; i < numListeners; ++i) {//这里就是数据更新的回调mUpdateListeners.get(i).onAnimationUpdate(this);}}
}

看到这里,一开始给出的使用中打印出的结果就可以找到答案了。

这里在看下打印结果的两个方法:

public float getAnimatedFraction() {return mCurrentFraction;
}
public Object getAnimatedValue() {if (mValues != null && mValues.length > 0) {return mValues[0].getAnimatedValue();}return null;
}

对于mValues是什么,这里下看下定义它的地方:

 

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;}//首次进来mValues为nullif (mValues == null || mValues.length == 0) {//这里就是对mValues进行赋值setValues(PropertyValuesHolder.ofInt("", values));} else {PropertyValuesHolder valuesHolder = mValues[0];valuesHolder.setIntValues(values);}// New property/values/target should cause re-initialization prior to startingmInitialized = false;
}
public void setValues(PropertyValuesHolder... values) {int numValues = values.length;mValues = values;......
}

从这里可以看到mValues中存的对象是PropertyValuesHolder.ofInt()返回的对象:

 

public static PropertyValuesHolder ofInt(String propertyName, int... values) {return new IntPropertyValuesHolder(propertyName, values);
}

这里返回的是IntPropertyValuesHolder对象,这里就是对数据进行相关操作的工具类,这里去看下它的构造方法和用到的几个相关方法:

 

public IntPropertyValuesHolder(String propertyName, int... values) {super(propertyName);setIntValues(values);
}
@Override
public void setIntValues(int... values) {//这里去执行父类的方法,里面就是对mKeyFrames进行的赋值super.setIntValues(values);mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
}
@Override
void calculateValue(float fraction) {mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
}
@Override
Object getAnimatedValue() {return mIntAnimatedValue;
}

可以看到计算和获取值的方法都在这里,不过这个计算的过程是通过mIntAnimateValue来获取的,这里对于mKeyFrames的赋值是在欺父类的setIntValues()中实现的:

 

public void setIntValues(int... values) {mValueType = int.class;mKeyframes = KeyframeSet.ofInt(values);
}
public static KeyframeSet ofInt(int... values) {......return new IntKeyframeSet(keyframes);
}

终于快到头了,看到IntKeyFrameSet这个类,计算操作就是在这里实现的,上面看到calculateValue()方法,这里实际调用的是IntKeyFrameSet中的getIntValue(),这里就去看看:

 

@Override
public int getIntValue(float fraction) {if (fraction <= 0f) {......return mEvaluator == null ?prevValue + (int)(intervalFraction * (nextValue - prevValue)) :((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();} else if (fraction >= 1f) {......return mEvaluator == null ?prevValue + (int)(intervalFraction * (nextValue - prevValue)) :((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();}IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);for (int i = 1; i < mNumKeyframes; ++i) {......return mEvaluator == null ?prevValue + (int)(intervalFraction * (nextValue - prevValue)) :((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();}prevKeyframe = nextKeyframe;}// shouldn't get herereturn ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}

这里的计算逻辑就略去了,主要是看下它的返回,这里有对mEvaluator的一个判断,如果不为null就交给它来实现返回值,这里的prevValue和nextValue就是我么一开始设置的起始和结束值,这个mEvaluator是一个TypeEvaluator类型,是一个接口,所以需要自己去实现,所以到这里重点就是去看看mEvaluator这个对象是如何进行赋值的,在ValueAnimator中有一个setEvaluator()方法:

 

public void setEvaluator(TypeEvaluator value) {if (value != null && mValues != null && mValues.length > 0) {mValues[0].setEvaluator(value);}
}

所有如果你想对返回的结果进行修改,那就可以在这里进行动动手了,还是拿一开始的例子,只是这次多调用了setEvaluator()这个方法对返回结果进行修改:

 

private void testValueAnimator(){ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,100);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float animatedFraction = animation.getAnimatedFraction();float value = (float) animation.getAnimatedValue();Log.d(TAG, "onAnimationUpdate: = "+animatedFraction+"    value = "+value);}});valueAnimator.setEvaluator(new TypeEvaluator() {@Overridepublic Object evaluate(float fraction, Object startValue, Object endValue) {return 66;}});valueAnimator.start();
}

返回结果:

 

06-08 10:58:57.587 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.0    value = 66.0
06-08 10:58:57.967 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.0    value = 66.0
06-08 10:58:58.020 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.010926217    value = 66.0
06-08 10:58:58.081 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.17727113    value = 66.0
06-08 10:58:58.102 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.3159377    value = 66.0
06-08 10:58:58.122 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.42178285    value = 66.0
06-08 10:58:58.141 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.5209378    value = 66.0
06-08 10:58:58.157 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.60395575    value = 66.0
06-08 10:58:58.174 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.6889204    value = 66.0
06-08 10:58:58.191 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.76791346    value = 66.0
06-08 10:58:58.207 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.8345653    value = 66.0
06-08 10:58:58.224 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.89507747    value = 66.0
06-08 10:58:58.241 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.9431018    value = 66.0
06-08 10:58:58.258 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.97712016    value = 66.0
06-08 10:58:58.275 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.9960574    value = 66.0
06-08 10:58:58.292 4705-4705/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 1.0    value = 66.0

对于这个方法的使用拓张性很大,若是使用ValueAnimator的ofObject(),这样就可以对对象中的属性进行修改,下面看个简单的demo:

 

private void testValueAnimator(){ValueAnimator valueAnimator = ValueAnimator.ofObject(new CusTypeEvaluator(),new PointXY(0,0),new PointXY(100,100));valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float animatedFraction = animation.getAnimatedFraction();PointXY point = (PointXY) animation.getAnimatedValue();Log.d(TAG, "onAnimationUpdate: = "+animatedFraction+"    point = "+point.x+"   "+point.y);}});valueAnimator.start();
}
public class PointXY {public int x;public int y;public PointXY(int x, int y) {this.x = x;this.y = y;}
}
public class CusTypeEvaluator implements TypeEvaluator<PointXY>{@Overridepublic PointXY evaluate(float fraction, PointXY startValue, PointXY endValue) {int x = (int) (fraction*(endValue.x-startValue.x));int y = (int) (fraction*(endValue.y-startValue.y));PointXY pointXY = new PointXY(x,y);return pointXY;}
}

看下它的输出:

 

06-08 11:19:53.318 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.0    point = 0   0
06-08 11:19:53.348 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.0    point = 0   0
06-08 11:19:53.390 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.035111755    point = 3   3
06-08 11:19:53.444 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.20610732    point = 20   20
06-08 11:19:53.462 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.30142605    point = 30   30
06-08 11:19:53.482 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.40117142    point = 40   40
06-08 11:19:53.499 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.4895288    point = 48   48
06-08 11:19:53.515 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.57304144    point = 57   57
06-08 11:19:53.532 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.6594797    point = 65   65
06-08 11:19:53.549 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.7362755    point = 73   73
06-08 11:19:53.565 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.81057394    point = 81   81
06-08 11:19:53.582 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.87505555    point = 87   87
06-08 11:19:53.599 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.9249463    point = 92   92
06-08 11:19:53.615 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.9648882    point = 96   96
06-08 11:19:53.632 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.9901356    point = 99   99
06-08 11:19:53.649 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 0.99975324    point = 99   99
06-08 11:19:53.665 6029-6029/com.example.ubt.proguardtest D/MainActivity: onAnimationUpdate: = 1.0    point = 100   100

对于如何去好的使用TypeEvaluator这个接口,就看你有什么样的需求,怎么样去实现了,这里只是说明了它是如何作用的。

如果有疑问,欢迎留言。

 

这篇关于Android属性动画(Animator)是如何实现的的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/805233

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10