Analyzing and Fixing TextView Marquee Reset Issue on Android 6.0+
The article shows how to diagnose the Android 6.0+ TextView marquee jump caused by a button’s setText triggering requestLayout and onMeasure, and fixes it by giving the button a fixed size instead of wrap_content, illustrating a systematic source‑code analysis and debugging workflow for UI issues.
This article explains how to diagnose and solve the problem where a TextView marquee animation resets (jumps) after clicking the "Add to Cart" button on Android 6.0 and above.
Background : The author notes that developers often lack time to read source code and may get stuck when a UI issue appears. The article uses a concrete problem to demonstrate a systematic source‑code analysis approach.
Problem description : On Android 6.0+ the TextView with android:ellipsize="marquee" and android:singleLine="true" exhibits a jump when the button is pressed. The animation restarts from the beginning each time.
Preparation : The author prepared the Android source code, created an Android Studio project, and added a demo that reproduces the issue.
Sample Java code (MainActivity) :
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.show_tv).setSelected(true);
final TextView changeTv = findViewById(R.id.change_tv);
changeTv.setText(getString(R.string.shopping_count, mNum));
findViewById(R.id.click_tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNum++;
changeTv.setText(getString(R.string.shopping_count, mNum));
}
});
}Sample XML layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.workshop.textview.MyTextView
android:id="@+id/show_tv"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentTop="true"
android:layout_marginTop="30dp"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:padding="5dp"
android:scrollHorizontally="true"
android:textColor="@android:color/holo_blue_bright"
android:singleLine="true"
android:text="!!!广告!!!vivo S7手机将不惧距离与光线的限制,带来全场景化自拍体验,刷新了5G时代的自拍旗舰标准"
android:textSize="24sp" />
<TextView
android:id="@+id/change_tv"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/shopping_count"
android:textColor="@android:color/holo_orange_dark"
android:textSize="28sp" />
<TextView
android:id="@+id/click_tv"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:background="@android:color/darker_gray"
android:padding="5dp"
android:singleLine="true"
android:text="添加购物车"
android:textColor="@android:color/background_dark"
android:textSize="24sp"
android:textStyle="bold" />
</RelativeLayout>Analysis steps :
Search for the marquee implementation in Android source (e.g., startMarquee() ).
Identify that onDraw() calls startMarquee() when singleLine=true and ellipsize=marquee .
Trace the Marquee inner class: initialization, start() , setting of scroll variables, and invalidate() calls.
Understand the role of Choreographer – it provides per‑frame callbacks ( postFrameCallback ) that drive the marquee animation.
Examine the Marquee Tick method: calculates mPixelsPerMs , computes deltaMs , updates mScroll , caps it at mMaxScroll , and calls invalidate() .
Observe that the animation resets when mScroll is set back to zero, which happens when TextView.onMeasure() is triggered.
Root cause : Clicking the "Add to Cart" button calls setText() on the numeric TextView. setText() internally calls requestLayout() , which forces a new measurement pass. During this pass onMeasure() resets the marquee’s scroll position, causing the animation to restart.
Solution : Change the layout parameter of the button (or any view that triggers requestLayout() ) from wrap_content to a fixed size (e.g., match_parent or a specific dp value). This prevents requestLayout() from being invoked on each click, stopping the marquee reset.
After modifying the button’s width/height, the marquee runs continuously without jumping.
Conclusion : The article demonstrates a systematic debugging workflow for Android UI issues: (1) locate relevant source code, (2) draw flow diagrams, (3) hypothesize causes, (4) verify with breakpoints, and (5) apply a minimal layout change to fix the problem. It also highlights the importance of understanding Android’s view lifecycle, Marquee implementation, and Choreographer timing.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.