在 `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() 只是向 `ViewRootImpl` 發送「重繪請求訊息」(`MSG_INVALIDATE`)到 UI 執行緒的 `MessageQueue`// 位移動畫:從當前位置移動到目標位置,持續 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();
| 概念 | 說明 |
|---|---|
| UI Thread (Main Thread) | 唯一處理 View 繪製、事件分發的執行緒 |
| invalidate() | 請求重繪,非同步,將訊息入隊 |
| requestLayout() | 請求重新佈局(measure + layout),同樣非同步 |
| ViewRootImpl.doTraversal() | 每幀執行一次:performMeasure → performLayout → performDraw |
| Choreographer | 接收 VSync 訊號,排程 doTraversal,約 16.6ms/幀 |
理解 Android 「單執行緒 + 非同步重繪 + VSync 驅動」的繪製管線,即可避開「迴圈改 UI 只見終態」這類新手坑,並寫出流暢 60fps 的動畫效果。