我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android深入分析属性动画源码

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android深入分析属性动画源码

1.先看一段动画的代码实现

ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);
alpha.setDuration(500);
alpha.start();

代码很简单,上面三行代码就可以开启一个透明度变化的动画。 那么android系统到底是如何实现的呢?进入源码分析。

1)看第一行代码:

ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);

创建了一个ObjectAnimator对象,并把values数组设置给了anim对象。

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
}

ObjectAnimator 构造函数中。将传过来的View对象和propertyName赋值给成员变量。

private ObjectAnimator(Object target, String propertyName) {
        //将传过来的View对象赋值给成员变量mTarget
        setTarget(target);
        //将propertyName赋值给成员变量mPropertyName
        setPropertyName(propertyName);
}

注意这个mTarget为什么要用一个软引用?

那是为了防止Activity发生内存泄漏。因为会有Activity已经退出,但是动画可能还未执行完,这个时候View得不到释放的话,会引发Activity内存泄漏。

private WeakReference<Object> mTarget;
public void setTarget(@Nullable Object target) {
    final Object oldTarget = getTarget();
    if (oldTarget != target) {
        if (isStarted()) {
            cancel();
        }
        //将传进来的View对象赋值给mTarget
        mTarget = target == null ? null : new WeakReference<Object>(target);
        mInitialized = false;
    }
}

再看第二行代码做了啥?anim.setFloatValues(values);

首次进来mValues==null,mProperty==null,所以会执行这行代码。 setValues(PropertyValuesHolder.ofFloat(mPropertyName, values))。

public void setFloatValues(float... values) {
    if (mValues == null || mValues.length == 0) {
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofFloat(mProperty, values));
        } else {
            setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
        }
    } else {
        super.setFloatValues(values);
    }
}

setValue将得到的 PropertyValuesHolder数组赋值给成员变量PropertyValuesHolder[] mValues;

再看PropertyValuesHolder.ofFloat(mPropertyName, values));

先调用super构造函数,将propertyName赋值给父类的mPropertyName,

 public FloatPropertyValuesHolder(String propertyName, float... values) {
    super(propertyName);
    setFloatValues(values);
}

然后再调用setFloatValues(values);

public void setFloatValues(float... values) {
    super.setFloatValues(values);
    //将mKeyframes强转为mFloatKeyframes
    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
//调用父类方法创建了KeyframeSet对象,赋值给了mKeyframes
public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
}

KeyframeSet.ofFloat(values);这行代码创建了一个关键帧的集合。

public static KeyframeSet ofFloat(float... values) {
    boolean badValue = false;
    int numKeyframes = values.length;
    //创建一个value长度的 FloatKeyFrame的数组
    FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
    //numKeyframes==1的话,其实是没有View是没有动画的。如果传过来的values的长度是1的话,会报错的。
    if (numKeyframes == 1) {
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
        keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
        if (Float.isNaN(values[0])) {
            badValue = true;
        }
    } else {
        //下面的代码才是关键的 Keyframe ofFloat(float fraction, float value)是创建关键帧。
        //fraction英文单词意思是部分,在这作为参数的意思是:从动画启示位置,到当前位置,所占的整个动画的百分比。
        //value就是某个部分对应的属性值。
        // 比如传进来的value值是1.0f 2.0f 3.0f 4.0f,5.0f。整个动画有5个值。因为1.0是初始值,要完成整个动画需要4步。
         //从1-2,2-3,3-4,4-5;4个部分。
         //第0个位置是起始位置,所以他所在的部分就是0。第一个位置就是四分之一,第二个就是四分之二....
         //第i个位置,所在整个动画的部分就是i/(i-1)。而这个位置对应的动画的属性值,就是value[i]
        //所以这个keyframes[]数组的目的就是保存,动画的关键位置所占的百分比和关键位置对应的属性值。
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
            if (Float.isNaN(values[i])) {
                badValue = true;
            }
        }
    }
    return new FloatKeyframeSet(keyframes);
}

到这为止,第一行代码执行完毕。

ObjectAnimator.ofFloat(view, "alpha", 1, 0,1)

将view赋值给ObjectAnimator成员变量。

将propertyName赋值给PropertyValuesHolder,会通过属性name来反射它的set方法,用来修改属性值。

创建KeyframeSet,关键帧集合。将value数组转换成对应的关键帧集合,通过动画执行的时间,来计算当前时间对应的属性值,然后再调用view的set属性方法,从而达到形成动画的目的。

这块的代码会再后面看到。

2).看动画的第二行代码alpha.start();

ObjectAnimator的父类是ValueAnimator。start()里面调用到的方法会在子类和父类里跳来跳去,这也增大了阅读的难度。

首先看ValueAnimator#start(boolean playBackwards)方法

addAnimationCallback:向Choreographer注册回调函数,我们知道Choreographer可以接受Vsync信号,16.66ms一次,也是屏幕刷新一次的时间。这样在屏幕刷新的时候,就可以通过向Choreographer注册回调函数进行动画的更新。

 private void start(boolean playBackwards) {
        //Animators 必须运行在一个Looper不能为空的线程中,因为动画需要涉及到Choreographer。
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mStartTime = -1;
        //这个是一个回调函数。这块是由Choreographer回调的,稍后分析。
        addAnimationCallback(0);
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            //开始动画。
            startAnimation();
        }
    }

先看startAnimation方法(),会在这个方法中调用initAnimation();

在这会先调用子类ObjectAnimator,然后在调用父类的ValueAnimator的initAnimation方法。

先看子类的initAnimation(),这个方法根据propertyName来反射view的set属性方法。

void initAnimation() {
     if (!mInitialized) {
        //先拿到target,也就是view对象。
         final Object target = getTarget();
         if (target != null) {
         // PropertyValuesHolder[] mValues;这个values就是PropertyValuesHolder的集合。
             final int numValues = mValues.length;
             for (int i = 0; i < numValues; ++i) {
                //在PropertyValuesHolder中传进了属性值,下面这行代码就是根据属性值,来反射view的set方法,
                //通过set方法,就可以动态的改变view的属性值的变化。
                 mValues[i].setupSetterAndGetter(target);
             }
         }
         //调用父类的initAnimation()方法
         super.initAnimation();
     }
}

再看父类ValueAnimator的initAnimation方法。调用了PropertyValuesHolder的init()方法。

在init方法中,向KeyframeSet关键帧集合设置了一个估值器,这个用来计算属性值的,后面会看到具体的计算方法。

void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
        //调用PropertyValuesHolder#init方法
            mValues[i].init();
        }
        mInitialized = true;
    }
}
 void init() {
    if (mEvaluator == null) {
        //得到一个估值器
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator : null;
    }
    if (mEvaluator != null) {
        //向KeyframeSet中设置一个估值器,这个估值器用来计算动画在某个时刻的属性值。
        mKeyframes.setEvaluator(mEvaluator);
    }
}
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
public class FloatEvaluator implements TypeEvaluator<Number> {
    //This function returns the result of linearly interpolating the start and end values
    这个方法返回一个在动画开始和结束之间的一个线性的结果。其实就是个一元一次方程,来计算动画当前的位置。
    //result = x0 + t * (v1 - v0)
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

至此,initAnimation的代码已经执行完毕。主要做的工作可以总结为两点:

1.调用PropertyValuesHolder的setupSetterAndGetter方法,通过反射拿到View的setter方法。

2.向KeyframeSet中设置一个估值器,用来计算动画某一时刻的属性值。

3)接下来看ValueAnimator#addAnimationCallback

这个方法是向Choreographer设置了一个会回调函数,每隔16.66ms回调一次,用来刷新动画。

还设置了一个回调集合,在Choreographer的回调函数中,回调集合里面的回调函数,来实现属性动画的刷新

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    //getAnimationHandler 就是上面创建的AnimationHandler。
    //将this作为 AnimationFrameCallback的回调,会回调doAnimationFrame(long frameTime)
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
//AnimationHandler#addAnimationFrameCallback
getProvider()拿到的是MyFrameCallbackProvider。
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        //向Choreographer加入一个回调函数mFrameCallback
        getProvider().postFrameCallback(mFrameCallback);
    }
    //将添加的回调函数加入一个回调的集合。
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
}

先看这个getProvider().postFrameCallback(mFrameCallback);这个就是向Choreographer注册一个回调。

final Choreographer mChoreographer = Choreographer.getInstance();
    //这行代码是向编舞者Choreographer添加了一个回调函数。
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        mChoreographer.postFrameCallback(callback);
}
Choreographer中
public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);
}

下面这行代码就是向Choreographer添加CallBackType为CALLBACK_ANIMATION,Token为FRAME_CALLBACK_TOKEN的回调函数。 callback 就是传进来的mFrameCallback。

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

省略中间的调用过程。。。这块的代码在Choreographer源码分析过。

MyFrameCallbackProvider#postFrameCallback就是向Choreographer添加一个回调函数。 我们知道,Choreographer在接收到Vsync信号后调用这些回调函数。

 void doFrame(long frameTimeNanos, int frame) {
 doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
 }
最终会调到这里,根据上面传过来的token,转换成不同的回调函数,调用不同的方法。
//在将View绘制时,调用的是else分支的回调
//在动画这里,传进来的是mFrameCallback,Choreographer.FrameCallback的实例,会调用到doFrame方法
 public void run(long frameTimeNanos) {
    if (token == FRAME_CALLBACK_TOKEN) {
        ((FrameCallback)action).doFrame(frameTimeNanos);
    } else {
        ((Runnable)action).run();
    }
}
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            //再次向Choreographer注册回调,等到下一次Vsync信号来的时候调用,
            //针对于60Hz的屏幕,刷新时间间隔是16.66ms,也就是Vsync回调的时间间隔
            //也就是说属性动画16.66毫秒会改变一次
            getProvider().postFrameCallback(this);
        }
    }
};

Choreographer中每个16.6ms会回调doFrame方法(),在doAnimationFrame方法中,就会回调注册的回调集合。

private void doAnimationFrame(long frameTime) {
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
        if (callback == null) {
            continue;
        }
        //遍历mAnimationCallbacks,调用callBack回调函数,
        //这个回调函数是ValueAnimator的doAnimationFrame
        if (isCallbackDue(callback, currentTime)) {
            callback.doAnimationFrame(frameTime);
        }
    }
}

doAnimationFrame是AnimationFrameCallback的回调函数,由ValueAnimator实现。

 public final boolean doAnimationFrame(long frameTime) {
       //frameTime 这个时间是从Choreographer传过来的时间,
       //记录为上一次动画刷新的时间
        mLastFrameTime = frameTime;
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        return finished;
 }
 public final boolean doAnimationFrame(long frameTime) {
       //frameTime 这个时间是从Choreographer传过来的时间,
       //记录为上一次动画刷新的时间
        mLastFrameTime = frameTime;
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        return finished;
 }
boolean animateBasedOnTime(long currentTime) {
     boolean done = false;
     if (mRunning) {
        //拿到总时间
         final long scaledDuration = getScaledDuration();
         //通过计算得到动画当前执行占比多少。(currentTime - mStartTime)动画执行的时间
         //除以scaledDuration总时间,得到就是已经执行的部分,如果是一个重复的动画,这个值可能会大于1.
         final float fraction = scaledDuration > 0 ?
                 (float)(currentTime - mStartTime) / scaledDuration : 1f;
        //下面通过计算对fraction进行修正,减去重复执行的部分,得到真正的在一次动画中要执行到哪一部分
         mOverallFraction = clampFraction(fraction);
         float currentIterationFraction = getCurrentIterationFraction(
                 mOverallFraction, mReversing);
         animateValue(currentIterationFraction);
     }
     return done;
 }

注意animateValue,这个方法在父类ValueAnimator和子类ObjectAnimator都有实现。

所以这里先调用子类ObjectAnimator的方法。

//这个方法是调用的子类的方法
void animateValue(float fraction) {
     final Object target = getTarget();
     if (mTarget != null && target == null) {
         cancel();
         return;
     }
     //先调用父类的方法
     super.animateValue(fraction);
     //再回到子类
     int numValues = mValues.length;
     for (int i = 0; i < numValues; ++i) {
        //给View设置改变后的属性值     
         mValues[i].setAnimatedValue(target);
     }
 }

先看super.animateValue方法,这个方法就是去计算动画变动后的属性值。

 void animateValue(float fraction) {
     //通过插值器,来修改。如果没有设置插值器,那么fraction的变化就是匀速的。
     //经过插值器的计算,fraction的变化就会呈现出加速、减速变化的效果。
     fraction = mInterpolator.getInterpolation(fraction);
     mCurrentFraction = fraction;
     int numValues = mValues.length;
     for (int i = 0; i < numValues; ++i) {
         //PropertyValuesHolder[] mValues,因为一个View可以有多个属性动画,所以这用一个数组来存储。
         mValues[i].calculateValue(fraction);
     }
 }
AccelerateDecelerateInterpolator 插值器
public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
void calculateValue(float fraction) {
    //mKeyframes 就是前面创建的关键帧集合KeyframeSet
    Object value = mKeyframes.getValue(fraction);
   // 将得到的值,赋值给mAnimatedValue
    mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}

下面这个方法是真正去计算改变后的属性值。通过估值器mEvaluator去计算的。

public Object getValue(float fraction) {
    //第一关键帧记做前一关键帧
    Keyframe prevKeyframe = mFirstKeyframe;
    for (int i = 1; i < mNumKeyframes; ++i) {
        //得到下一关键帧
        Keyframe nextKeyframe = mKeyframes.get(i);
        if (fraction < nextKeyframe.getFraction()) {
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            //得到前一关键帧,对应的部分
            final float prevFraction = prevKeyframe.getFraction();
            //fraction - prevFraction 当前要执行的部分距离前一关键帧是多少。
            //nextKeyframe.getFraction() - prevFraction,这一帧有多少
            //两者相除,得到的就是当前部分在这一帧的占比
            float intervalFraction = (fraction - prevFraction) /
                (nextKeyframe.getFraction() - prevFraction);
            if (interpolator != null) {
                //通过插值器来修改,这一部分的大小
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            //通过估值器,来计算属性值要变化到多少
            //这个估值器就是上面赋值的FloatEvaluator或IntEvaluator
            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                    nextKeyframe.getValue());
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't reach here
    //不应该执行到这里,在上面的for循环就应该返回当前动画,属性变化的大小。
    return mLastKeyframe.getValue();
}

通过估值器计算view的属性值。

public Float evaluate(float fraction, Number startValue, Number endValue) {
    float startFloat = startValue.floatValue();
    //通过一个一元一次方程,来计算得到当前的属性值。
    return startFloat + fraction * (endValue.floatValue() - startFloat);
}

至此,动画要变动后的属性值,已经计算出来了,

通过 mValues[i].setAnimatedValue(target);用来修改View的属性值大小。

 void setAnimatedValue(Object target) {
       //前面已经通过反射拿到了View的setter方法
      if (mSetter != null) {
          try {
               //拿到属性值大小,          
              mTmpValueArray[0] = getAnimatedValue();
             //通过反射,修改view属性值的大小
              mSetter.invoke(target, mTmpValueArray);
          } catch (InvocationTargetException e) {
              Log.e("PropertyValuesHolder", e.toString());
          } catch (IllegalAccessException e) {
              Log.e("PropertyValuesHolder", e.toString());
          }
      }
  }
Object getAnimatedValue() {
    return mAnimatedValue;
}

至此,android属性动画的整个执行流程已经分析完毕。

可以总结以下几点:

1.ValueAnimator是父类,ObjectAnimator是子类,这里面封装了一个target,也就是view对象。

2.PropertyValuesHolder,有属性名,属性值,通过属名来反射view的setter方法,来动态修改属性值。

3.KeyframeSet,是一个关键帧集合,封装了定义动画是value数组的值,每一个值都被记录为一个关键帧FloatKeyframe。

4.通过插值器,可以改变属性变化的快慢,通过估值器计算属性值的大小。

5.给Choreographer注册了一个回调,每隔16.66ms回调一次,每一次回调都会去改变view属性值的大小。改变是通过fraction计算的,进而通过计算得到改变后的属性值大小。

这样动态的改变view属性值的大小,就连贯的形成一幅动画。

到此这篇关于Android深入分析属性动画源码的文章就介绍到这了,更多相关Android属性动画内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android深入分析属性动画源码

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android源码解析之属性动画详解

前言 大家在日常开发中离不开动画,属性动画更为强大,我们不仅要知道如何使用,更要知道他的原理。这样,才能得心应手。那么,今天,就从最简单的来说,了解下属性动画的原理。ObjectAnimator.ofInt(mView,"width",10
2022-06-06

Android动画(四)动画框架源码分析

本篇难度较大,慎入 也许可以先去看总结在来一起分析 从我们写的开始进入:fun click(view: View) {val textView = findViewById(R.id.tv)val animator = ObjectAnim
2022-06-06

【Android】CalledFromWrongThreadException 深入源码分析

先上结论 出现此问题的原因是:在非 UI 线程中创建了 Dialog,而在 UI 线程中调用了 show() 方法 问题还原 在使用 dialog 的时候,因为线程问题,在调用 dismiss() 方法的时候,出现如下常见的 crash–O
2022-06-06

Android转场动画深入分析探究

对于一个动画而言,它是由多个分镜头组成的,而转场就是分镜之间衔接方式。转场的主要目的,就是为了让镜头与镜头之间过渡的更加自然,让动画更加连贯,例如两个页面切换之间的衔接动画
2022-11-13

Android context源码详解及深入分析

Android context详解 前言: Context都没弄明白,还怎么做Android开发? Activity mActivity =new Activity()作为Android开发者,不知道你有没有思考过这个问题,Activity
2022-06-06

SpringAOP源码深入分析

这篇文章主要介绍了SpringAOP源码,AOP(AspectOrientProgramming),直译过来就是面向切面编程,AOP是一种编程思想,是面向对象编程(OOP)的一种补充
2023-01-03

ReactFiber源码深入分析

Fiber可以理解为一个执行单元,每次执行完一个执行单元,ReactFiber就会检查还剩多少时间,如果没有时间则将控制权让出去,然后由浏览器执行渲染操作,这篇文章主要介绍了ReactFiber架构原理剖析,需要的朋友可以参考下
2022-11-13

JavaLinkedHashMap深入分析源码

大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰,所以LinkedHashMap就闪亮登场了,这篇文章通过源码解析带你了解LinkedHashMap
2022-11-13

Android源码系列之深入理解ImageView的ScaleType属性

做Android开发的童靴们肯定对系统自带的控件使用的都非常熟悉,比如Button、TextView、ImageView等。如果你问我具体使用,我会给说:拿ImageView来说吧,首先创建一个新的项目,在项目布局文件中应用ImageVie
2022-06-06

React深入分析useEffect源码

useEffect是react v16.8新引入的特性。我们可以把useEffect hook看作是componentDidMount、componentDidUpdate、componentWillUnmounrt三个函数的组合
2022-11-13

Node.js深入分析Koa源码

本文主要从源码的角度来讲述Koa,尤其是其中间件系统是如何实现的。跟Express相比,Koa的源码异常简洁,Express因为把路由相关的代码嵌入到了主要逻辑中,因此读Express的源码可能长时间不得要领,而直接读Koa的源码几乎没有什么障碍
2022-11-13

JavaArrayList深入源码层分析

Java中容器对象主要用来存储其他对象,根据实现原理不同,主要有3类常用的容器对象:ArrayList使用数组结构存储容器中的元素、LinkedList使用链表结构存储容器中的元素
2023-01-17

JavaRetrofit源码层深入分析

这篇文章主要介绍了JavaRetrofit源码层分析,Retrofit是一个RESTful的HTTP网络请求框架的封装,网络请求的工作本质上是OkHttp完成,而Retrofit仅负责网络请求接口的封装
2023-01-13

Android RecyclerView的Item自定义动画及DefaultItemAnimator源码分析

这是关于RecyclerView的第二篇,说的是如何自定义Item动画,但是请注意,本文不包含动画的具体实现方法,只是告诉大家如何去自定义动画,如何去参考源代码。 我们知道,RecyclerView默认会使用DefaultItemAnima
2022-06-06

Android动画 实现开关按钮动画(属性动画之平移动画)实例代码

Android动画 实现开关按钮动画(属性动画之平移动画),最近做项目,根据项目需求,有一个这样的功能,实现类似开关的动画效果,经过自己琢磨及上网查找资料,终于解决了,这里就记录下:在Android里面,一些炫酷的动画确实是很吸引人的地方,
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录