在onTouchListener中调用TextView的setText()导致UI卡顿
Hey there, let's break down why your touch event handling is causing UI stutter when updating a TextView, and walk through practical fixes to smooth things out.
The Root Cause
Your onTouchEvent runs on the main UI thread, and setText() isn't just a simple string swap—it triggers a chain of expensive UI work: measuring the text's size, relayouting the TextView, and scheduling a full view redraw. When you call this in ACTION_MOVE (which fires dozens of times per second as the user drags), you're flooding the UI thread with redundant tasks. This blocks critical operations like processing touch input or rendering frames, leading to that annoying jank.
Practical Fixes
1. Throttle Updates (Cut Down on Unnecessary Calls)
Instead of updating the TextView every single ACTION_MOVE event, only refresh when the value changes significantly or after a small time interval. This reduces UI workload without making the update feel laggy.
Here's how to adjust your code:
// Add these class-level variables private float mLastProgress = 0; private long mLastUpdateTime = 0; private static final long MIN_UPDATE_INTERVAL = 60; // ~16fps, matches typical screen refresh @Override public boolean onTouchEvent(MotionEvent e) { float center = mScrubberX + mScrubberWidth / 2; float handleLeft = center - HANDLE_WIDTH; float handleRight = center + HANDLE_WIDTH; switch (e.getAction()) { case MotionEvent.ACTION_DOWN: if (e.getX() > handleLeft && e.getX() < handleRight) { mActionDown = true; mActionDownX = e.getX() - mScrubberX; } break; case MotionEvent.ACTION_MOVE: if (mActionDown) { float newLeft = e.getX() - mActionDownX; // Calculate your progress value based on the new position (adjust to your logic) float currentProgress = calculateProgressFromPosition(newLeft); long now = System.currentTimeMillis(); // Only update if progress changed enough OR enough time has passed if (Math.abs(currentProgress - mLastProgress) > 1 || now - mLastUpdateTime > MIN_UPDATE_INTERVAL) { mYourTextView.setText(String.valueOf((int) currentProgress)); mLastProgress = currentProgress; mLastUpdateTime = now; } } break; // Don't forget to reset state when touch ends case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mActionDown = false; break; } return true; } // Helper method to map position to your desired progress value private float calculateProgressFromPosition(float leftPosition) { // Example: convert position to 0-100 progress range float totalRange = getWidth() - mScrubberWidth; return (leftPosition / totalRange) * 100; }
2. Use Choreographer for Frame-Aligned Updates
If you need ultra-smooth updates, use Choreographer to schedule the text update for the next available screen frame. This ensures the UI update doesn't block touch event processing itself.
// Add these class-level variables private Choreographer mChoreographer; private float mPendingProgress; private boolean mIsUpdateScheduled = false; // Initialize in your constructor/onCreate public YourView(Context context) { super(context); mChoreographer = Choreographer.getInstance(); } @Override public boolean onTouchEvent(MotionEvent e) { // ... (keep your ACTION_DOWN logic) case MotionEvent.ACTION_MOVE: if (mActionDown) { float newLeft = e.getX() - mActionDownX; mPendingProgress = calculateProgressFromPosition(newLeft); // Only schedule one update at a time to avoid duplicates if (!mIsUpdateScheduled) { mIsUpdateScheduled = true; mChoreographer.postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { mYourTextView.setText(String.valueOf((int) mPendingProgress)); mIsUpdateScheduled = false; } }); } } break; // ... (keep ACTION_UP/CANCEL logic) }
Why These Work
- Throttling reduces the number of
setText()calls, so the UI thread isn't overwhelmed with repeated layout and redraw tasks. - Choreographer aligns the update with the screen's refresh cycle, ensuring the text update happens at the optimal time without blocking touch input.
内容的提问来源于stack exchange,提问作者Siddharth Yadav

