自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

補(bǔ)間動(dòng)畫源碼中分析機(jī)制原理

移動(dòng)開發(fā) Android
補(bǔ)間動(dòng)畫移動(dòng)后,點(diǎn)擊事件的響應(yīng)為什么還在原來的位置?那今天我們就來從源碼解析原理,我們可以通過平移、旋轉(zhuǎn)、縮放、透明度等API進(jìn)行具體的操作.

[[438831]]

前言

補(bǔ)間動(dòng)畫移動(dòng)后,點(diǎn)擊事件的響應(yīng)為什么還在原來的位置?

那今天我們就來從源碼解析原理

一、補(bǔ)間動(dòng)畫

補(bǔ)間動(dòng)畫可以在一個(gè)視圖容器內(nèi)執(zhí)行一系列簡(jiǎn)單變換(具體的變換步驟有:位置、大小、旋轉(zhuǎn)、透明度);

我們可以通過平移、旋轉(zhuǎn)、縮放、透明度等API進(jìn)行具體的操作;

補(bǔ)間動(dòng)畫的實(shí)現(xiàn)方式可以通過 XML或通過Android代碼兩種方式 去定義;

1、xml方式實(shí)現(xiàn)

文件名:animator_translate.xml

  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <translate 
  3.     xmlns:android="http://schemas.android.com/apk/res/android" 
  4.     android:fromXDelta="0" 
  5.     android:fromYDelta="0" 
  6.     android:toYDelta="0" 
  7.     android:toXDelta="200" 
  8.     android:duration="500" 
  9.     android:fillAfter="true"
  10. </translate> 

代碼加載xml文件獲取動(dòng)畫

  1. //加載動(dòng)畫 
  2. Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_translate); 
  3. //執(zhí)行動(dòng)畫 
  4. testBtn.startAnimation(animation); 

2、代碼方式實(shí)現(xiàn)

  1. TranslateAnimation translateAnimation = new TranslateAnimation(0,200,0,0); 
  2. translateAnimation.setDuration(500);//動(dòng)畫執(zhí)行時(shí)間 
  3. translateAnimation.setFillAfter(true);//動(dòng)畫執(zhí)行完成后保持狀態(tài) 
  4. //執(zhí)行動(dòng)畫 
  5. testBtn.startAnimation(translateAnimation); 

二、補(bǔ)間動(dòng)畫原理解

1、startAnimation

startAnimation(rotateAnimation)方法進(jìn)入源碼;

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

首先是通過setStartTime()設(shè)置了動(dòng)畫的開始時(shí)間;

  1. //View.java 
  2.  public void setStartTime(long startTimeMillis) { 
  3.      mStartTime = startTimeMillis; 
  4.      mStarted = mEnded = false
  5.      mCycleFlip = false
  6.      mRepeated = 0; 
  7.      mMore = true
  8.  } 

這里只是對(duì)一些變量進(jìn)行賦值,再來看看下一個(gè)方法;

設(shè)置動(dòng)畫setAnimation(animation):

  1. //View.java 
  2.   public void setAnimation(Animation animation) { 
  3.       mCurrentAnimation = animation; 
  4.       if (animation != null) { 
  5.                 if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF 
  6.                   && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) { 
  7.               animation.setStartTime(AnimationUtils.currentAnimationTimeMillis()); 
  8.           } 
  9.           animation.reset(); 
  10.       } 
  11.   } 

這里面也是將動(dòng)畫實(shí)例賦值給當(dāng)前的成員變量;

分析startAnimation()方法里的invalidateParentCaches();

  1. //View.java 
  2.  protected void invalidateParentCaches()  
  3.      if (mParent instanceof View) { 
  4.          ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED; 
  5.      } 
  6.  } 

可以看到這里僅僅是設(shè)置動(dòng)畫標(biāo)記,在視圖構(gòu)建或者屬性改變時(shí)是必要的;

再回到startAnimation()方法里面invalidate(true);

2、invalidate

  1. //View.java 
  2.   public void invalidate(boolean invalidateCache) { 
  3.       invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); 
  4.   } 
  5.   void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, 
  6.           boolean fullInvalidate) { 
  7.       if (mGhostView != null) { 
  8.           mGhostView.invalidate(true); 
  9.           return
  10.       } 
  11.         ................. 
  12.           // Propagate the damage rectangle to the parent view
  13.           final AttachInfo ai = mAttachInfo; 
  14.           final ViewParent p = mParent; 
  15.           if (p != null && ai != null && l < r && t < b) { 
  16.               final Rect damage = ai.mTmpInvalRect; 
  17.               damage.set(l, t, r, b); 
  18.               p.invalidateChild(this, damage); 
  19.           } 
  20.       } 
  21.    } 

這里著重看p.invalidateChild(this, damage);

  1. //ViewGroup.java 
  2.   @Deprecated 
  3.   @Override 
  4.   public final void invalidateChild(View child, final Rect dirty) { 
  5.       final AttachInfo attachInfo = mAttachInfo; 
  6.       if (attachInfo != null && attachInfo.mHardwareAccelerated) { 
  7.           // HW accelerated fast path 
  8.           onDescendantInvalidated(child, child); 
  9.           return
  10.       } 
  11.       ViewParent parent = this; 
  12.         ......... 
  13.           do { 
  14.               View view = null
  15.               if (parent instanceof View) { 
  16.                   view = (View) parent; 
  17.               } 
  18.          ......... 
  19.               parent = parent.invalidateChildInParent(location, dirty); 
  20.           } while (parent != null); 
  21.       } 
  22.   } 
  • 因?yàn)閂iewParent p = mParent,this是View的子類ViewGroup;
  • 所以p.invalidateChild(this, damage)里面其實(shí)是調(diào)用了ViewGroup的invalidateChild();
  • 這里有一個(gè)do{}while()循環(huán),第一次的時(shí)候parent = this即ViewGroup,然后調(diào)用parent.invalidateChildInParent(location, dirty)方法,當(dāng)parent == null的時(shí)候結(jié)束循環(huán);
  • invalidateChildInParent方法中,只要條件成立就會(huì)返回mParent;
  1. //ViewGroup.java 
  2.   @Deprecated 
  3.   @Override 
  4.   public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { 
  5.       if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) { 
  6.          ....... 
  7.           return mParent; 
  8.       } 
  9.       return null
  10.   } 
  • ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0)是保持成立的,所以會(huì)一直返回mParent,那么說明View的mParent是ViewGroup;
  • ViewGroup的mParent也是ViewGroup,而do{}while()循環(huán)一直找mParent,而一個(gè)View最頂端的mParent是ViewRootImpl,所以最后走到ViewRootImpl的invalidateChildInParent()里面;
  • 在onCreate()方法里面通過setContentView()將布局添加到以DecorView為根布局的一個(gè)ViewGroup里面,因?yàn)樵趏nResume()執(zhí)行完成后,WindowManager會(huì)執(zhí)行addView()方法,然后會(huì)創(chuàng)建一個(gè)ViewRootImpl對(duì)象,與DecorView綁定起來,DecorView的mParent設(shè)置成ViewRootImpl,ViewRootImpl實(shí)現(xiàn)了ViewParent接口,所以ViewRootImpl雖然沒有繼承View或者ViewGroup;
  • ViewRootImpl的invalidateChildInParent()方法中;
  1. //ViewRootImpl.java 
  2.    @Override 
  3.    public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 
  4.        checkThread(); 
  5.        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); 
  6.        if (dirty == null) { 
  7.            invalidate(); 
  8.            return null
  9.        } else if (dirty.isEmpty() && !mIsAnimating) { 
  10.            return null
  11.        } 
  12.         ....... 
  13.        invalidateRectOnScreen(dirty); 
  14.        return null
  15.    } 

這里所有的返回值都變?yōu)閚ull了,之前執(zhí)行的do{}while()循壞也會(huì)停止。

3、scheduleTraversals()

  • 接著分析invalidateRectOnScreen(dirty)方法;
  • 進(jìn)入 scheduleTraversals()方法;
  1.  //ViewRootImpl.java 
  2.   private void invalidateRectOnScreen(Rect dirty) { 
  3.       ...... 
  4.       if (!mWillDrawSoon && (intersected || mIsAnimating)) { 
  5.           scheduleTraversals(); 
  6.       } 
  7.   } 
  8. //ViewRootImpl.java 
  9. void scheduleTraversals() { 
  10.       if (!mTraversalScheduled) { 
  11.           mTraversalScheduled = true
  12.           mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
  13.           mChoreographer.postCallback( 
  14.                   Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
  15.           if (!mUnbufferedInputDispatch) { 
  16.               scheduleConsumeBatchedInput(); 
  17.           } 
  18.           notifyRendererOfFramePending(); 
  19.           pokeDrawLockIfNeeded(); 
  20.       } 
  21.   } 

主要看mTraversalRunnable,我們找到mTraversalRunnable這個(gè)類;

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

4、doTraversal()

doTraversal()方法;

  1. //ViewRootImpl.java 
  2. void doTraversal() { 
  3.          ....... 
  4.           performTraversals(); 
  5.          ....... 
  6.       } 
  7.   } 
  • scheduleTraversals()是將 performTraversals()放到一個(gè)Runnable里面;
  • 在Choreographer的帶執(zhí)行對(duì)列里面,這些待執(zhí)行的Runable會(huì)在最近的一個(gè)16.6ms屏幕刷新信號(hào)到來的時(shí)候執(zhí)行;
  • 而performTraversals()是View的三大操作:測(cè)量、布局、繪制的發(fā)起者;
  • 在Android屏幕刷新機(jī)制里,View樹里面不管哪個(gè)View發(fā)起的繪制請(qǐng)求或者布局請(qǐng)求都會(huì)走到ViewRootImpl的scheduleTraversals()里面,然后在最新的一個(gè)屏幕刷新信號(hào)來到時(shí),再通過ViewRootImpl的performTraversals()從根布局DecorView依次遍歷View樹去執(zhí)行測(cè)量、布局、繪制三大操作
  • 每一次的刷新都會(huì)走到ViewRootImpl里面,然后在層層遍歷到發(fā)生改變的VIew里去執(zhí)行相應(yīng)的布局和繪制操作;
  • 所以在調(diào)用View.startAnimation(rotateAnimation)后,并沒有立即執(zhí)行動(dòng)畫,而是做了一下變量初始化操作,將View和Animation綁定起來,調(diào)用重繪操作,內(nèi)部層層尋找mPartent,最終在ViewRootImpl的scheduleTraversals()發(fā)起一個(gè)遍歷View樹的請(qǐng)求,在最近一個(gè)屏幕信號(hào)刷新到來時(shí)執(zhí)行這個(gè)請(qǐng)求,調(diào)用performTraversals()從根布局去遍歷View樹。

5、draw

  • 我們繼續(xù)分析draw方法是怎樣將動(dòng)畫繪制的且動(dòng)畫是怎樣動(dòng)起來的呢?
  • invalidate最終會(huì)調(diào)用到ViewRootImpl 注冊(cè)Choreographer的回調(diào);
  • 在下一次VSYN信號(hào)到來時(shí) 會(huì)調(diào)用ViewRootImpl performTraversals 最終會(huì)調(diào)用到View的draw方法。
  1. boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { 
  2. ... 
  3. //清除上次動(dòng)畫保存的Transformation 
  4. if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) { 
  5.     parent.getChildTransformation().clear(); 
  6.     parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION; 
  7. ...... 
  8. final Animation a = getAnimation(); 
  9. if (a != null) { 
  10.     //根據(jù)當(dāng)前時(shí)間計(jì)算當(dāng)前幀的動(dòng)畫,more表示是否需要執(zhí)行更多幀的動(dòng)畫 
  11.     more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); 
  12.     concatMatrix = a.willChangeTransformationMatrix(); 
  13.     if (concatMatrix) { 
  14.         mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; 
  15.     } 
  16.    //拿到當(dāng)前幀需要的變換 ,這個(gè)值會(huì)在applyLegacyAnimation中進(jìn)行設(shè)置 
  17.     transformToApply = parent.getChildTransformation(); 
  18. .... 
  19. if (transformToApply != null) { 
  20.     if (concatMatrix) { 
  21.         if (drawingWithRenderNode) { 
  22.             renderNode.setAnimationMatrix(transformToApply.getMatrix()); 
  23.         } else { 
  24.             // Undo the scroll translation, apply the transformation matrix, 
  25.             // then redo the scroll translate to get the correct result. 
  26.             canvas.translate(-transX, -transY); 
  27.             canvas.concat(transformToApply.getMatrix());//在這里調(diào)用canvas的concat方法,實(shí)現(xiàn)最終的平移效果 (做矩陣相乘) 
  28.             canvas.translate(transX, transY); 
  29.         } 
  30.        //標(biāo)記需要清除Tranformation 
  31.         parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; 
  32.     } 
  33.     float transformAlpha = transformToApply.getAlpha(); 
  34.     if (transformAlpha < 1) { 
  35.         alpha *= transformAlpha; 
  36.         parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; 
  37.     } 
  38. ... 
  • 調(diào)用applyLegacyAnimation根據(jù)當(dāng)前時(shí)間來計(jì)算當(dāng)前幀的動(dòng)畫同時(shí)返回值表示動(dòng)畫是否還沒播放完成;
  • 拿到當(dāng)前幀需要的變換transformToApply;
  • 調(diào)用canvas.concat 方法 實(shí)現(xiàn)最終的平移效果 (做矩陣相乘) 這樣我們就將最終的平移想過畫到canvas上面了解決了如何完成繪制的問題;
  • 接下來繼續(xù)分析applyLegacyAnimation是如何計(jì)算當(dāng)前的幀的動(dòng)畫的。

6、applyLegacyAnimation

  1. private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, 
  2.         Animation a, boolean scalingRequired) { 
  3.     ... 
  4.     //獲取Transformation 每個(gè)ViewGroup中的子View共同使用一個(gè)Transformation 為了多個(gè)View有動(dòng)畫時(shí)頻繁創(chuàng)建多個(gè)Transformation 
  5.     //這個(gè)和在draw方法中取出的transformToApply是一個(gè)對(duì)象 就是最終應(yīng)用到Canvas上的Transform 
  6.     final Transformation t = parent.getChildTransformation(); 
  7.     //調(diào)用Animation的getTransformation方法來根據(jù)當(dāng)前時(shí)間計(jì)算Transformation 這個(gè)對(duì)象的值最終會(huì)由getTransformation方法中進(jìn)行賦值 
  8.     boolean more = a.getTransformation(drawingTime, t, 1f); 
  9.     invalidationTransform = t; 
  10.     ... 
  11.     //如果動(dòng)畫還沒有播放完成 需要讓動(dòng)畫循環(huán)起來 實(shí)際上是繼續(xù)調(diào)用invalidate 
  12.     if (more) { 
  13.      if (parent.mInvalidateRegion == null) { 
  14.                     parent.mInvalidateRegion = new RectF(); 
  15.                 } 
  16.                 final RectF region = parent.mInvalidateRegion; 
  17.                //調(diào)用Animation 的getInvalidateRegion來根據(jù)invalidationTransform計(jì)算 parent的invalidateRegion 
  18.                 a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region, 
  19.                         invalidationTransform); 
  20.                 // The child need to draw an animation, potentially offscreen, so 
  21.                 // make sure we do not cancel invalidate requests 
  22.                 parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; 
  23.                 final int left = mLeft + (int) region.left
  24.                 final int top = mTop + (int) region.top
  25.                 //調(diào)用invalidate執(zhí)行下一次繪制請(qǐng)求,這樣動(dòng)畫就動(dòng)起來了 
  26.                 parent.invalidate(lefttopleft + (int) (region.width() + .5f), 
  27.                         top + (int) (region.height() + .5f)); 
  28.     } 
  • 在applyLegacyAnimation方法中會(huì)再次調(diào)用parent.invalidate注冊(cè)一個(gè)Choreographer回調(diào),下一次VSYN后又會(huì)調(diào)用draw方法.這樣就循環(huán)起來了;
  • 只有當(dāng)more為false時(shí) 表示動(dòng)畫播放完成了 這時(shí)候就不會(huì)invalidate了;
  • 繼續(xù)看getTransformation是如何計(jì)算Transformation的。
  1. //Animation.java 
  2. //返回值表示動(dòng)畫是否沒有播放完成 并且需要計(jì)算outTransformation 也就是動(dòng)畫需要做的變化 
  3. public boolean getTransformation(long currentTime, Transformation outTransformation) { 
  4.     if (mStartTime == -1) { 
  5.       mStartTime = currentTime;//記錄第一幀的時(shí)間 
  6.     } 
  7.      if (duration != 0) { 
  8.         normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / //計(jì)算運(yùn)行的進(jìn)度(0-1) (當(dāng)前時(shí)間-開始時(shí)間+偏移量)/動(dòng)畫總時(shí)長(zhǎng) 
  9.             (float) duration; 
  10.         } 
  11.       final boolean expired = normalizedTime >= 1.0f || isCanceled(); //判斷動(dòng)畫是否播放完成 或者被取消 
  12.       mMore = !expired; 
  13.       if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); //處理最大值 
  14.       final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);//根據(jù)插值器計(jì)算的當(dāng)前動(dòng)畫運(yùn)行進(jìn)度 
  15.       applyTransformation(interpolatedTime, outTransformation);//根據(jù)動(dòng)畫進(jìn)度  計(jì)算最終的outTransformation 
  16.       return mMore; 
  • 記錄動(dòng)畫第一幀的時(shí)間;
  • 根據(jù)當(dāng)前時(shí)間到動(dòng)畫第一幀的時(shí)間這之間的時(shí)長(zhǎng)和動(dòng)畫應(yīng)持續(xù)的時(shí)長(zhǎng)來計(jì)算動(dòng)畫的進(jìn)度;
  • 把動(dòng)畫進(jìn)度控制在 0-1 之間,超過 1 的表示動(dòng)畫已經(jīng)結(jié)束,重新賦值為 1 即可;
  • 根據(jù)插值器來計(jì)算動(dòng)畫的實(shí)際進(jìn)度;
  • 調(diào)用 applyTransformation() 應(yīng)用動(dòng)畫效果。
  1. //applyTransformation每種類型的動(dòng)畫都有自己的實(shí)現(xiàn) 這里以位移動(dòng)畫為例 
  2. //TranslateAnimation.java 
  3. @Override 
  4.     protected void applyTransformation(float interpolatedTime, Transformation t) { 
  5.         //Transformation可以理解成 存儲(chǔ)View的一些變換信息,將變化信息保存到成員變量matrix中 
  6.         float dx = mFromXDelta; 
  7.         float dy = mFromYDelta; 
  8.         if (mFromXDelta != mToXDelta) { 
  9.             dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);//計(jì)算X方向需要移動(dòng)的距離 
  10.         } 
  11.         if (mFromYDelta != mToYDelta) { 
  12.             dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);//計(jì)算Y方向需要移動(dòng)的距離 
  13.         } 
  14.         t.getMatrix().setTranslate(dx, dy); //將最終的結(jié)果設(shè)置到Matrix上面去 
  15.     } 
  • 至此計(jì)算完最終的變化然后應(yīng)用到了Transformation的Matix上,會(huì)在draw方法中拿到該Transformation并應(yīng)用到Canvas上,調(diào)用canvas.concat,進(jìn)行平移動(dòng)畫;
  • 當(dāng)動(dòng)畫如果還沒執(zhí)行完,就會(huì)再調(diào)用 invalidate() 方法,層層通知到 ViewRootImpl 再次發(fā)起一次遍歷請(qǐng)求,當(dāng)下一幀屏幕刷新信號(hào)來的時(shí)候;
  • 再通過 performTraversals() 遍歷 View 樹繪制時(shí),該 View 的 draw 收到通知被調(diào)用時(shí);
  • 會(huì)再次去調(diào)用 applyLegacyAnimation() 方法去執(zhí)行動(dòng)畫相關(guān)操作,包括調(diào)用 getTransformation() 計(jì)算動(dòng)畫進(jìn)度,調(diào)用 applyTransformation() 應(yīng)用動(dòng)畫。

7、動(dòng)畫總結(jié)

  • 當(dāng)調(diào)用View.startAnimation(Animation)時(shí),并沒有立即執(zhí)行動(dòng)畫,而是通過invalidate()層層通過到ViewRootImpl發(fā)起一次遍歷View樹的請(qǐng)求,在接收到下一個(gè)(16ms)屏幕信號(hào)刷新時(shí)才發(fā)起遍歷View樹的繪制操作,從DecorView開始遍歷,繪制時(shí)會(huì)調(diào)用draw()方法,如果View有綁定動(dòng)畫則執(zhí)行applyLegacyAnimation()方法處理相關(guān)動(dòng)畫邏輯;
  • 在applyLegacyAnimation()里面,先執(zhí)行初始化initialize(),再通知?jiǎng)赢嬮_始o(jì)nAnimationStart(),然后通過getTransformation()計(jì)算動(dòng)畫進(jìn)度,并且它的返回值和動(dòng)畫是否結(jié)束決定是否繼續(xù)通知ViewRootImpl發(fā)起遍歷請(qǐng)求,view樹繪制,如此重復(fù)這個(gè)步驟,并且調(diào)用applyTransformation()方法執(zhí)行動(dòng)畫的邏輯,直到動(dòng)畫結(jié)束。

總結(jié)

至于未來會(huì)怎樣,要走下去才知道,反正路還很長(zhǎng),天總會(huì)亮;

加油老鐵們!

本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」

責(zé)任編輯:姜華 來源: Android開發(fā)編程
相關(guān)推薦

2014-07-15 10:23:10

Android補(bǔ)間動(dòng)畫

2021-12-01 18:36:35

屬性

2021-11-11 17:40:08

WatchdogAndroid源碼分析

2012-12-03 16:57:37

HDFS

2015-09-11 09:15:32

RyuSDN

2021-11-26 17:17:43

Android廣播運(yùn)行原理源碼分析

2021-08-09 11:15:28

MybatisJavaSpring

2022-11-02 15:56:45

littlefscommit機(jī)制

2021-12-15 19:22:38

原理View動(dòng)畫

2021-09-09 06:55:43

AndroidViewDragHel原理

2012-05-31 02:54:07

HadoopJava

2011-06-23 14:05:32

Qt 事件機(jī)制

2021-08-12 16:28:10

AndroidHandleLooper

2021-09-01 06:48:16

AndroidGlide緩存

2024-08-30 10:40:12

2023-08-28 07:49:24

Redisson鎖機(jī)制源碼

2021-09-26 08:35:17

Android控件寬高

2021-12-08 06:53:28

Choreograph屏幕機(jī)制

2021-09-05 07:35:58

lifecycleAndroid組件原理

2009-12-17 11:00:47

Linux內(nèi)存管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)