首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Android UI 執行緒機制解析:for 迴圈中連續改變 View 位置卻只看到最後一幀

     編輯:程式設計疑難解答

在 `Button.onClick` 中寫 `for` 迴圈,每次迭代修改 `View.layout()` 並呼叫 `invalidate()`,Logcat 印出 10 次卻只看到最終位置。這是 Android UI 執行緒與繪製機制決定的經典現象。

現象還原

for (int i = 0; i < 10; i++) {
    view.layout(view.getLeft()+1, view.getTop()+1,
                view.getRight()-1, view.getBottom()-1);
    view.invalidate();
    log("*****");
    sleep(100); // 假設有延遲
}

核心原因:invalidate() 是非同步排程,非即時繪製

  1. invalidate() 只是向 `ViewRootImpl` 發送「重繪請求訊息」(`MSG_INVALIDATE`)到 UI 執行緒的 `MessageQueue`
  2. 迴圈在同一個同步執行緒(主執行緒)高速跑完,10 個重繪請求被合併或僅最後一個生效
  3. 主執行緒下一次 `doTraversal()` 時才真正執行 `performDraw()`,此時 View 已在最終位置
  4. 人眼感知頻率 ~60Hz,10 次在幾毫秒內完成,根本看不出中間過程

正確做法:使用 ValueAnimator / ObjectAnimator

// 位移動畫:從當前位置移動到目標位置,持續 500ms
view.animate()
    .translationXBy(10f)
    .translationYBy(10f)
    .setDuration(500)
    .setInterpolator(new LinearInterpolator())
    .start();

// 或 ValueAnimator 精確控制每一幀
ValueAnimator animator = ValueAnimator.ofInt(0, 10);
animator.addUpdateListener(animation -> {
    int val = (int) animation.getAnimatedValue();
    view.layout(view.getLeft()+1, view.getTop()+1,
                view.getRight()-1, view.getBottom()-1);
});
animator.setDuration(1000).start();

為什麼不能在迴圈裡 Thread.sleep()?

  • `Thread.sleep()` 會阻塞主執行緒,導致 ANR(Application Not Responding)
  • 主執行緒被阻塞時無法處理任何 UI 事件、重繪、輸入
  • 即使加上 `sleep`,`invalidate()` 仍是非同步,畫面依然不會逐幀更新

關鍵概念對照表

概念說明
UI Thread (Main Thread)唯一處理 View 繪製、事件分發的執行緒
invalidate()請求重繪,非同步,將訊息入隊
requestLayout()請求重新佈局(measure + layout),同樣非同步
ViewRootImpl.doTraversal()每幀執行一次:performMeasure → performLayout → performDraw
Choreographer接收 VSync 訊號,排程 doTraversal,約 16.6ms/幀

最佳實踐清單

  • ✅ 位移/縮放/透明度動畫 → `view.animate()` / `ObjectAnimator`
  • ✅ 複雜路徑動畫 → `ValueAnimator` + `addUpdateListener`
  • ✅ 需要逐幀回調 → 實作 `Animation.AnimationListener` 或 `Animator.AnimatorListener`
  • ❌ 禁止在主執行緒 `for/while` + `sleep` + `invalidate`
  • ❌ 禁止在主執行緒做耗時運算或阻塞呼叫

理解 Android 「單執行緒 + 非同步重繪 + VSync 驅動」的繪製管線,即可避開「迴圈改 UI 只見終態」這類新手坑,並寫出流暢 60fps 的動畫效果。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved