转载请注明出处:http://blog.csdn.net/farmer_cc/article/details/18259117

Android 动画animation 深入分析

前言:本文试图通过分析动画流程,来理解android动画系统的设计与实现,学习动画的基本原则,最终希望能够指导动画的设计。

0 本文中用到的一些类图

1 view animation

调用方法:view.startAnimation(animation);

  1. public void startAnimation(Animation animation) {
  2. animation.setStartTime(Animation.START_ON_FIRST_FRAME);
  3. setAnimation(animation);
  4. invalidateParentCaches();
  5. invalidate(true);
  6. }

在invalidate(ture);中

  1. if (p != null && ai != null) {
  2. final Rect r = ai.mTmpInvalRect;
  3. r.set(0, 0, mRight - mLeft, mBottom - mTop);
  4. // Don't call invalidate -- we don't want to internally scroll
  5. // our own bounds
  6. p.invalidateChild(this, r);
  7. }

即调用parent的invalidateChild,

假定父控件即为ViewRootImpl;

public final class ViewRootImpl implements ViewParent;

  1. @Override
  2. public void invalidateChild(View child, Rect dirty) {
  3. invalidateChildInParent(null, dirty);
  4. }
  5. public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
  6. //...省略一堆判断条件,最终调用
  7. if (!mWillDrawSoon && (intersected || mIsAnimating)) {
  8. scheduleTraversals();
  9. }
  10. return null;
  11. }
  1. void scheduleTraversals() {
  2. if (!mTraversalScheduled) {
  3. mTraversalScheduled = true;
  4. mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
  5. mChoreographer.postCallback(
  6. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  7. scheduleConsumeBatchedInput();
  8. }
  9. }

其中mTraversalBarrier = mHandler.getLooper().postSyncBarrier();是设置同步障碍(syncBarrier),当looper中的消息队列执行到barrier 后,会暂停执行,只有当barrier 被释放mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); 后消息队列才能继续执行。

Choreographer mChoreographer; 是动画系统中的核心组织者, 负责统一调度。后面详细说。

  1. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  2. final class TraversalRunnable implements Runnable {
  3. @Override
  4. public void run() {
  5. doTraversal();
  6. }
  7. }
  1. void doTraversal() {
  2. performTraversals();
  3. }

perform 待补充

  1. final class ConsumeBatchedInputRunnable implements Runnable {
  2. @Override
  3. public void run() {
  4. doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
  5. }
  6. }
  7. final ConsumeBatchedInputRunnable mConsumedBatchedInputRunnable =
  8. new ConsumeBatchedInputRunnable();

doConsume 待补充

2 属性动画aninmator

valueAnimator.start();

  1. private void start(boolean playBackwards) {
  2. if (Looper.myLooper() == null) {
  3. throw new AndroidRuntimeException("Animators may only be run on Looper threads");
  4. }
  5. AnimationHandler animationHandler = getOrCreateAnimationHandler();
  6. animationHandler.mPendingAnimations.add(this);
  7. if (mStartDelay == 0) {
  8. // This sets the initial value of the animation, prior to actually starting it running
  9. setCurrentPlayTime(0);
  10. mPlayingState = STOPPED;
  11. mRunning = true;
  12. notifyStartListeners();
  13. }
  14. animationHandler.start();
  15. }

这里会检查调用线程必须是Looper线程,如果是view相关的属性动画,还必须是UI 线程。

得到AnimationHandle 并把自己加入到PendingAnimations  的list中.

  1. getOrCreateAnimationHandler();
  1. protected static ThreadLocal<AnimationHandler> sAnimationHandler =
  2. new ThreadLocal<AnimationHandler>()
  3. protected static class AnimationHandler implements Runnable {
  4. // The per-thread list of all active animations
  5. /** @hide */
  6. protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
  7. // Used in doAnimationFrame() to avoid concurrent modifications of mAnimations
  8. private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();
  9. // The per-thread set of animations to be started on the next animation frame
  10. /** @hide */
  11. protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
  12. /**
  13. * Internal per-thread collections used to avoid set collisions as animations start and end
  14. * while being processed.
  15. * @hide
  16. */
  17. protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
  18. private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
  19. private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
  20. private final Choreographer mChoreographer;
  21. private boolean mAnimationScheduled;

AnimationHandler 就是一个runnable, 注意成员变量中的多个animator 的list 以及重要的mChoreographer = Choreographer.getInstance();

mChoreographer 也是一个threadlocal的变量。

在animationHandler.start() 中

  1. public void start() {
  2. scheduleAnimation();
  3. }
  4. private void scheduleAnimation() {
  5. if (!mAnimationScheduled) {
  6. mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
  7. mAnimationScheduled = true;
  8. }
  9. }

this 是runnable 即把animationHandler自己添加添加到mChoreographer 的队列中。

  1. public void postCallback(int callbackType, Runnable action, Object token) {
  2. postCallbackDelayed(callbackType, action, token, 0);
  3. }
  4. public void postCallbackDelayed(int callbackType,
  5. Runnable action, Object token, long delayMillis) {
  6. postCallbackDelayedInternal(callbackType, action, token, delayMillis);
  7. }
  8. private void postCallbackDelayedInternal(int callbackType,
  9. Object action, Object token, long delayMillis) {
  10. synchronized (mLock) {
  11. final long now = SystemClock.uptimeMillis();
  12. final long dueTime = now + delayMillis;
  13. mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
  14. if (dueTime <= now) {
  15. scheduleFrameLocked(now);
  16. } else {
  17. Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
  18. msg.arg1 = callbackType;
  19. msg.setAsynchronous(true);
  20. mHandler.sendMessageAtTime(msg, dueTime);
  21. }
  22. }
  23. }

传入的delay为0, 即调用scheduleFrameLocked(now);

  1. private void scheduleFrameLocked(long now) {
  2. if (!mFrameScheduled) {
  3. mFrameScheduled = true;
  4. if (USE_VSYNC) {
  5. if (DEBUG) {
  6. Log.d(TAG, "Scheduling next frame on vsync.");
  7. }
  8. // If running on the Looper thread, then schedule the vsync immediately,
  9. // otherwise post a message to schedule the vsync from the UI thread
  10. // as soon as possible.
  11. if (isRunningOnLooperThreadLocked()) {
  12. scheduleVsyncLocked();
  13. } else {
  14. Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
  15. msg.setAsynchronous(true);
  16. mHandler.sendMessageAtFrontOfQueue(msg);
  17. }
  18. } else {
  19. final long nextFrameTime = Math.max(
  20. mLastFrameTimeNanos / NANOS_PER_MS + sFrameDelay, now);
  21. if (DEBUG) {
  22. Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
  23. }
  24. Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
  25. msg.setAsynchronous(true);
  26. mHandler.sendMessageAtTime(msg, nextFrameTime);
  27. }
  28. }
  29. }
  1. private static final boolean USE_VSYNC = SystemProperties.getBoolean(
  2. "debug.choreographer.vsync", true);

USE_VSYNC 默认是true;

  1. private boolean isRunningOnLooperThreadLocked() {
  2. return Looper.myLooper() == mLooper;
  3. }

检查当前looper和mChoreographer的looper是否一致。一般情况是一致的。就会调用scheduleVsyncLocked();

  1. private void scheduleVsyncLocked() {
  2. mDisplayEventReceiver.scheduleVsync();
  3. }
  1. public void scheduleVsync() {
  2. if (mReceiverPtr == 0) {
  3. Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
  4. + "receiver has already been disposed.");
  5. } else {
  6. nativeScheduleVsync(mReceiverPtr);
  7. }
  8. }

到了native 暂时先不涉及。

回头来看animationHandler 的run()。 前面提到animationHandler把自己添加到mChoreographer,当被调用时,调用run方法。

  1. // Called by the Choreographer.
  2. @Override
  3. public void run() {
  4. mAnimationScheduled = false;
  5. doAnimationFrame(mChoreographer.getFrameTime());
  6. }
  1. public long getFrameTime() {
  2. return getFrameTimeNanos() / NANOS_PER_MS;
  3. }
  4. public long getFrameTimeNanos() {
  5. synchronized (mLock) {
  6. if (!mCallbacksRunning) {
  7. throw new IllegalStateException("This method must only be called as "
  8. + "part of a callback while a frame is in progress.");
  9. }
  10. return USE_FRAME_TIME ? mLastFrameTimeNanos : System.nanoTime();
  11. }
  12. }

doAnimationFrame()总结就是

1.遍历pending list动画,如果delay为0 则调用start,不为0,加入delay list;

2.遍历delay list, 根据frametime计算是继续delay还是ready可以播放,若是ready,则加入到ready list中;

3 遍历ready list,调用start ;

4,遍历所有animation,根据frametime计算动画是否要结束,如果可以结束,则加入到ending list中;

5,遍历ending list, 调用end;

6, 如果有列表中仍然有动画,则继续scheduleAnimation;

  1. private void doAnimationFrame(long frameTime) {
  2. // mPendingAnimations holds any animations that have requested to be started
  3. // We're going to clear mPendingAnimations, but starting animation may
  4. // cause more to be added to the pending list (for example, if one animation
  5. // starting triggers another starting). So we loop until mPendingAnimations
  6. // is empty.
  7. while (mPendingAnimations.size() > 0) {
  8. ArrayList<ValueAnimator> pendingCopy =
  9. (ArrayList<ValueAnimator>) mPendingAnimations.clone();
  10. mPendingAnimations.clear();
  11. int count = pendingCopy.size();
  12. for (int i = 0; i < count; ++i) {
  13. ValueAnimator anim = pendingCopy.get(i);
  14. // If the animation has a startDelay, place it on the delayed list
  15. if (anim.mStartDelay == 0) {
  16. anim.startAnimation(this);
  17. } else {
  18. mDelayedAnims.add(anim);
  19. }
  20. }
  21. }
  22. // Next, process animations currently sitting on the delayed queue, adding
  23. // them to the active animations if they are ready
  24. int numDelayedAnims = mDelayedAnims.size();
  25. for (int i = 0; i < numDelayedAnims; ++i) {
  26. ValueAnimator anim = mDelayedAnims.get(i);
  27. if (anim.delayedAnimationFrame(frameTime)) {
  28. mReadyAnims.add(anim);
  29. }
  30. }
  31. int numReadyAnims = mReadyAnims.size();
  32. if (numReadyAnims > 0) {
  33. for (int i = 0; i < numReadyAnims; ++i) {
  34. ValueAnimator anim = mReadyAnims.get(i);
  35. anim.startAnimation(this);
  36. anim.mRunning = true;
  37. mDelayedAnims.remove(anim);
  38. }
  39. mReadyAnims.clear();
  40. }
  41. // Now process all active animations. The return value from animationFrame()
  42. // tells the handler whether it should now be ended
  43. int numAnims = mAnimations.size();
  44. for (int i = 0; i < numAnims; ++i) {
  45. mTmpAnimations.add(mAnimations.get(i));
  46. }
  47. for (int i = 0; i < numAnims; ++i) {
  48. ValueAnimator anim = mTmpAnimations.get(i);
  49. if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
  50. mEndingAnims.add(anim);
  51. }
  52. }
  53. mTmpAnimations.clear();
  54. if (mEndingAnims.size() > 0) {
  55. for (int i = 0; i < mEndingAnims.size(); ++i) {
  56. mEndingAnims.get(i).endAnimation(this);
  57. }
  58. mEndingAnims.clear();
  59. }
  60. // If there are still active or delayed animations, schedule a future call to
  61. // onAnimate to process the next frame of the animations.
  62. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
  63. scheduleAnimation();
  64. }
  65. }

在animationFrame() 中根据当前状态,并且计算fraction,调用animateValue();

  1. boolean animationFrame(long currentTime) {
  2. boolean done = false;
  3. switch (mPlayingState) {
  4. case RUNNING:
  5. case SEEKED:
  6. //省略计算fraction的代码
  7. animateValue(fraction);
  8. break;
  9. }
  10. return done;
  11. }

通过mInterpolator.getInterpolation计算fraction;@Interpolator

根据fraction计算内部所有value,如果有updateListener,调用之。

  1. void animateValue(float fraction) {
  2. fraction = mInterpolator.getInterpolation(fraction);
  3. mCurrentFraction = fraction;
  4. int numValues = mValues.length;
  5. for (int i = 0; i < numValues; ++i) {
  6. mValues[i].calculateValue(fraction);
  7. }
  8. if (mUpdateListeners != null) {
  9. int numListeners = mUpdateListeners.size();
  10. for (int i = 0; i < numListeners; ++i) {
  11. mUpdateListeners.get(i).onAnimationUpdate(this);
  12. }
  13. }
  14. }

3. 插值器

从上面的介绍可以看到,Interpolator的关键是getInterpolation();

在ValueAnimator.animationFrame()中可以看到, 传递给Interpolator 的fraction是在[0,1] 值域范围。

  1. float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
  2. if (fraction >= 1f) {
  3. if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
  4. // Time to repeat
  5. if (mListeners != null) {
  6. int numListeners = mListeners.size();
  7. for (int i = 0; i < numListeners; ++i) {
  8. mListeners.get(i).onAnimationRepeat(this);
  9. }
  10. }
  11. if (mRepeatMode == REVERSE) {
  12. mPlayingBackwards = !mPlayingBackwards;
  13. }
  14. mCurrentIteration += (int)fraction;
  15. fraction = fraction % 1f;
  16. mStartTime += mDuration;
  17. } else {
  18. done = true;
  19. fraction = Math.min(fraction, 1.0f);
  20. }
  21. }
  22. if (mPlayingBackwards) {
  23. fraction = 1f - fraction;
  24. }

所以设计Interpolator 就是设计一个输入[0,1] 的函数。

先参观一下系统的几个Interpolator。

3.1 AccelerateDecelerateInterpolator

cos(t+1)Pi /2 +0.5f

从图可以看到,先加速后减速,病最终到达结束位置。

  1. public class AccelerateDecelerateInterpolator implements Interpolator {
  2. public float getInterpolation(float input) {
  3. return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
  4. }
  5. }

3.2 AccelerateInterpolator

如果factor=1 则函数为x^2

否则函数为x^a (a 是参数)

默认函数式x^2

如图示,逐渐加速到结束位置。

  1. public class AccelerateInterpolator implements Interpolator {
  2. private final float mFactor;
  3. private final double mDoubleFactor;
  4. public AccelerateInterpolator() {
  5. mFactor = 1.0f;
  6. mDoubleFactor = 2.0;
  7. }
  8. /**
  9. * Constructor
  10. *
  11. * @param factor Degree to which the animation should be eased. Seting
  12. *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
  13. *        1.0f  exaggerates the ease-in effect (i.e., it starts even
  14. *        slower and ends evens faster)
  15. */
  16. public AccelerateInterpolator(float factor) {
  17. mFactor = factor;
  18. mDoubleFactor = 2 * mFactor;
  19. }
  20. public float getInterpolation(float input) {
  21. if (mFactor == 1.0f) {
  22. return input * input;
  23. } else {
  24. return (float)Math.pow(input, mDoubleFactor);
  25. }
  26. }
  27. }

3.3 LinearInterpolator

线性的就是Y=X 没啥说的。

  1. public class LinearInterpolator implements Interpolator {
  2. public float getInterpolation(float input) {
  3. return input;
  4. }
  5. }

3.4 anticipateInterpolator

函数是:x^2((a+1)x-a) 默认参数a=2 默认函数为x^2(3x-1)

如图示, 会先反方向执行一段,然后正向一直加速至结束位置。

  1. public class AnticipateInterpolator implements Interpolator {
  2. private final float mTension;
  3. public AnticipateInterpolator() {
  4. mTension = 2.0f;
  5. }
  6. /**
  7. * @param tension Amount of anticipation. When tension equals 0.0f, there is
  8. *                no anticipation and the interpolator becomes a simple
  9. *                acceleration interpolator.
  10. */
  11. public AnticipateInterpolator(float tension) {
  12. mTension = tension;
  13. }
  14. public float getInterpolation(float t) {
  15. // a(t) = t * t * ((tension + 1) * t - tension)
  16. return t * t * ((mTension + 1) * t - mTension);
  17. }
  18. }

3.5 aniticipateOvershoot

是一个分段函数,默认参数a=3

2x*x[(2x*(a+1)-a)]     0<=x<=0.5

2(x-1)(x-1)[(2x-1)(a+1)+a]    0.5<x<=1

通过下图可以看到,动画会先反方向执行,然后向正方向逐渐加速,在快结束时逐渐减速,并超过预设的值,最后回到结束位置。

2x*x[(2x*(a+1)-a)]     0<=x<=0.5 的函数图

2(x-1)(x-1)[(2x-1)(a+1)+a]    0.5<x<=1的函数图

  1. public class AnticipateOvershootInterpolator implements Interpolator {
  2. private final float mTension;
  3. public AnticipateOvershootInterpolator() {
  4. mTension = 2.0f * 1.5f;
  5. }
  6. /**
  7. * @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
  8. *                there is no anticipation/overshoot and the interpolator becomes
  9. *                a simple acceleration/deceleration interpolator.
  10. */
  11. public AnticipateOvershootInterpolator(float tension) {
  12. mTension = tension * 1.5f;
  13. }
  14. /**
  15. * @param tension Amount of anticipation/overshoot. When tension equals 0.0f,
  16. *                there is no anticipation/overshoot and the interpolator becomes
  17. *                a simple acceleration/deceleration interpolator.
  18. * @param extraTension Amount by which to multiply the tension. For instance,
  19. *                     to get the same overshoot as an OvershootInterpolator with
  20. *                     a tension of 2.0f, you would use an extraTension of 1.5f.
  21. */
  22. public AnticipateOvershootInterpolator(float tension, float extraTension) {
  23. mTension = tension * extraTension;
  24. }
  25. private static float a(float t, float s) {
  26. return t * t * ((s + 1) * t - s);
  27. }
  28. private static float o(float t, float s) {
  29. return t * t * ((s + 1) * t + s);
  30. }
  31. public float getInterpolation(float t) {
  32. // a(t, s) = t * t * ((s + 1) * t - s)
  33. // o(t, s) = t * t * ((s + 1) * t + s)
  34. // f(t) = 0.5 * a(t * 2, tension * extraTension), when t < 0.5
  35. // f(t) = 0.5 * (o(t * 2 - 2, tension * extraTension) + 2), when t <= 1.0
  36. if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
  37. else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
  38. }
  39. }

4. 指导设计动画。

从第3节中可以看到,想要让动画按照我们预期的行为来执行,需要做的就是找到合适的函数。

画图使用http://www.fooplot.com/在线工具

Android 动画animation 深入分析的更多相关文章

  1. Android动画Animation之Tween用代码实现动画

    透明度动画.旋转动画.尺寸伸缩动画.移动动画 package com.javen.tween; import android.annotation.SuppressLint; import andro ...

  2. Android动画的深入分析

    一.AnimationDrawable的使用 详见:Drawable类及XMLDrawable的使用 补充:通过Animation的setAnimationListener()可以给View动画添加监 ...

  3. Android动画Animation简单示例

    Animation是Android给我们提供的一个可以实现动画效果的API,利用Animation我们可以实现一系列的动画效果,比如缩放动画,透明度动画,旋转动画,位移动画,布局动画,帧动画等等.An ...

  4. 《Android开发艺术探索》读书笔记 (7) 第7章 Android动画深入分析

    本节和<Android群英传>中的第七章Android动画机制与使用技巧有关系,建议先阅读该章的总结 第7章 Android动画深入分析 7.1 View动画 (1)android动画分为 ...

  5. 虾扯蛋:Android View动画 Animation不完全解析

    本文结合一些周知的概念和源码片段,对View动画的工作原理进行挖掘和分析.以下不是对源码一丝不苟的分析过程,只是以搞清楚Animation的执行过程.如何被周期性调用为目标粗略分析下相关方法的执行细节 ...

  6. Android动画效果之Property Animation进阶(属性动画)

    前言: 前面初步认识了Android的Property Animation(属性动画)Android动画效果之初识Property Animation(属性动画)(三),并且利用属性动画简单了补间动画 ...

  7. Android动画效果之初识Property Animation(属性动画)

    前言: 前面两篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画).Frame Animation(逐帧动画)Andr ...

  8. Android动画效果之Frame Animation(逐帧动画)

    前言: 上一篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画),今天来总结下Android的另外一种动画Frame ...

  9. Android动画效果之Tween Animation(补间动画)

    前言: 最近公司项目下个版本迭代里面设计了很多动画效果,在以往的项目中开发中也会经常用到动画,所以在公司下个版本迭代开始之前,抽空总结一下Android动画.今天主要总结Tween Animation ...

随机推荐

  1. 诞生于饭桌上的jcSQL语言

    相信每个Coder都有心在自己求学阶段可以写一门自己的语言,无论是毕业设计,还是课余爱好:不管是为了提升B格,还是想练手,抑或对其他语言不满,想自己撸一个,只要坚持下去了,都是不错的理由. 现在正值暑 ...

  2. 今天起改用mac的marsedit写博

    最近一直使用mac来工作,所以写博也相应改为marsedit. 初步感觉还是不错的,越来越发现mac其实也适合在工作中使用,生活上当然不在话下. 从高富帅的x220t变成屌丝的macbook小白(升级 ...

  3. 面向对象程序设计-C++_课时12访问限制

    private: 只有这个类(相同的类,不同的对象也可以)的成员函数可以访问这些成员变量或函数 public: 任何人都可以访问 protected: 只有这个类以及它的子子孙孙可以访问

  4. 魔兽世界---屠夫(Just a Hook)

    Just a Hook Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Tota ...

  5. How To Set Dark Theme in Visual Studio 2010

    Want to use the visual studio color theme editor to set the dark theme or other themes? Below shows ...

  6. Jade 报错

    今天写jade的时候遇到一个问题 Invalid indentation,you can use tabs or spaces but not both问题 经过查证原来是 在jade模板中 同时存在 ...

  7. AJAX背景技术介绍

    AJAX全称为“Asynchronous JavaScript and XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术. 主要包含了以下几种技术: Ajax(A ...

  8. Jar包下载地址

    Download Apache log4j 1.2.17下载: http://logging.apache.org/log4j/1.2/download.html jsoup http://jsoup ...

  9. vs 2005 在IE下断点不起作用

    vs2005 加断点调试,ie下不起作用. 1. 点击[开始]->[运行] 命令:regedit. 2. 定位到HKEY_LOCALMACHINE -> SOFTWARE -> Mi ...

  10. 读书笔记 - 设计模式(Head First)

    设计模式让你和其他开发人员之间有共享的词汇,设计模式可以把你的思考架构的层次提高到模式层面,而不是停留在琐碎的对象上. 设计原则: 封装变化:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需 ...