SoFunction
Updated on 2025-05-06

Sample code for Android to implement text scrolling effect

1. Project introduction

1. Background and meaning

In many information, news and enterprise display Android applications,Scroll text playback(also known as marquee effect, bulletin board effect) is a very common UI interaction method, used to continuously display announcements, news titles, prompt information, etc. In scenes such as film and television recommendation apps, subway and bus inquiries, stock market conditions, text scrolling can not only save screen space, but also attract users' attention and make information transmission more intense. Through native Android technology, this project implements a set of high-performance, highly customizable text scrolling playback controls that support multiple scrolling directions and animation curves from scratch to meet various complex needs.

2. Functional Requirements

  1. Text content settings: One or more texts can be dynamically set;

  2. Scroll mode:supportlevelverticalTwo rolling directions;

  3. Scrolling method:supportcyclePlay andSingle timePlay, supportRound tripandSeamless connection

  4. Speed ​​and interval: Customizable scroll speed and the stay interval between two scrolls;

  5. Animation curves:built-inLinearaccelerateslow downEqual interpolation;

  6. Touch interaction: Supports user touch sliding pause and manual drag;

  7. Resource release: Activity/Fragment correctly releases animation and Handler when destroyed to prevent memory leakage;

  8. Customizable styles: Text size, color, font, background, etc. can be dynamically configured through XML attributes or code;

  9. high performance: Maintain a smooth 60FPS in long list and multi-instance scenarios.

3. Technical selection

  • language:Java

  • Minimum SDK:API 21(Android 5.0)

  • Core Components

    • TextViewOr customView

    • Attribute animation (ObjectAnimator

    • ValueAnimator + ()(Advanced Program)

    • Handler + Runnable(Basic Plan)

    • Scroller / OverScroller(Smooth scrolling)

  • Layout container: Usually usedFrameLayoutRelativeLayoutConstraintLayoutHosting custom controls

  • Development Tools: Android Studio latest stable version

2. Detailed explanation of relevant knowledge

1. Android Custom View Basics

  • onMeasure(): Measure the width and height of the control;

  • onSizeChanged(): Size change callback, initialize the drawing area;

  • onDraw(Canvas): Draw text and background;

  • Custom properties:passres/values/Definition, can be used in XML;

  • Hardware acceleration: Ensure that the animation is smooth and turn off hardware acceleration for text shadow drawing if necessary.

2. Attribute animation and interpolation

  • (view, "translationX", start, end)

  • (start, end),existaddUpdateListenerUpdate location in  ;

  • Commonly used interpolation:LinearInterpolatorAccelerateInterpolatorDecelerateInterpolatorAccelerateDecelerateInterpolator

  • Custom interpolation: ImplementationTimeInterpolator

3. Handler and Runnable

  • Suitable for circular light scheduling;

  • postDelayed()Control the scroll interval;

  • Activity / Fragment When destroyingremoveCallbacks()Prevent memory leaks.

4. Scroller / OverScroller

  • Achieve smooth physical scrolling effect;

  • ()orfling()

  • existcomputeScroll(), call()andscrollTo(x, y)

  • Suitable for scenes where gesture drag and inertial scrolling are required.

5. TextView and ()

  • For simple scenarios, you can move directlyTextView

  • For higher performance and custom effects, you can()middle(), and pass()Implement scrolling.

Ideas for project implementation

  1. Determine the implementation plan

    • Plan 1 (Basic): Use a single in the layoutTextView,passObjectAnimatororTranslateAnimationmoveTextViewoftranslationX/Y

    • Scheme 2 (custom View):InheritanceView,existonDraw()Draw text in and control the offset of text drawing position to achieve more flexible animation and style control.

  2. Basic Process

    • initialization: Read XML attributes or get text content, font, color, speed and other configurations through setters;

    • Measurement and layout:existonMeasure()Calculate the text width/height and determine the View size;

    • Start the animation:existonAttachedToWindow()orstartScroll(), start the scrolling animation;

    • Scroll control:useValueAnimatororObjectAnimatorContinuously update the offset of text;

    • Loop and interval: The monitoring animation ends (AnimatorListener), in the callbackpostDelayed()Start again to achieve interval playback;

    • Resource release:existonDetachedFromWindow()Cancel all animations with Handler calls.

  3. Multi-directional and multi-mode

    • Roll horizontally: The initial offset isviewWidth, the end point is-textWidth

    • Vertical scrolling: The initial offset isviewHeight, the end point is-textHeight

    • Round trip mode:set uprepeatMode =

    • Seamless connection: Use two lines of text to alternately scroll, one line rolls out, and one line follows.

  4. Touch Pause and Drag

    • Rewrite in a custom viewonTouchEvent(),existACTION_DOWNhourpause()Animation,ACTION_MOVEAdjust the offset whenACTION_UPhourresume()orfling()

4. Complete integrated version code

4.1

<!-- res/values/ -->
<resources>
    <declare-styleable name="MarqueeTextView">
        <attr name="mtv_text" format="string" />
        <attr name="mtv_textColor" format="color" />
        <attr name="mtv_textSize" format="dimension" />
        <attr name="mtv_speed" format="float" />
        <attr name="mtv_direction">
            <flag name="horizontal" value="0" />
            <flag name="vertical" value="1" />
        </attr>
        <attr name="mtv_repeatDelay" format="integer" />
        <attr name="mtv_repeatMode">
            <enum name="restart" value="1" />
            <enum name="reverse" value="2" />
        </attr>
        <attr name="mtvInterpolator" format="reference" />
        <attr name="mtv_loop" format="boolean" />
    </declare-styleable>
</resources>

4.2 Layout Files

&lt;!-- res/layout/activity_main.xml --&gt;
&lt;FrameLayout xmlns:andro
    xmlns:app="/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"&gt;
 
    &lt;
        android:
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:mtv_text="Welcome to Android text scrolling control"
        app:mtv_textColor="#FF5722"
        app:mtv_textSize="18sp"
        app:mtv_speed="100"
        app:mtv_direction="horizontal"
        app:mtv_repeatDelay="500"
        app:mtv_repeatMode="restart"
        app:mtvInterpolator="@android:anim/linear_interpolator"
        app:mtv_loop="true"/&gt;
&lt;/FrameLayout&gt;

4.3 Custom controls:

package ;
 
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
 
public class MarqueeTextView extends View {
 
    // =========== Configurable properties ============    private String text;
    private int textColor;
    private float textSize;
    private float speed;               // px/s
    private int direction;             // 0: horizontal, 1: vertical
    private long repeatDelay;          // ms
    private int repeatMode;            //  or REVERSE
    private boolean loop;              // Whether to loop    private TimeInterpolator interpolator;
 
    // ============ Draw related =============    private Paint paint;
    private float textWidth, textHeight;
    private float offset;              // Current scroll offset 
    // ============ Animation ==============    private ObjectAnimator animator;
 
    public MarqueeTextView(Context context) {
        this(context, null);
    }
 
    public MarqueeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initAttributes(context, attrs);
        initPaint();
    }
 
    private void initAttributes(Context context, AttributeSet attrs) {
        TypedArray a = (attrs, );
        text = (.MarqueeTextView_mtv_text);
        textColor = (.MarqueeTextView_mtv_textColor, 0xFF000000);
        textSize = (.MarqueeTextView_mtv_textSize, 16 * getResources().getDisplayMetrics().scaledDensity);
        speed = (.MarqueeTextView_mtv_speed, 50f);
        direction = (.MarqueeTextView_mtv_direction, 0);
        repeatDelay = (.MarqueeTextView_mtv_repeatDelay, 500);
        repeatMode = (.MarqueeTextView_mtv_repeatMode, );
        loop = (.MarqueeTextView_mtv_loop, true);
        int interpRes = (.MarqueeTextView_mtvInterpolator, );
        interpolator = (context, interpRes);
        ();
 
        if ((text)) text = "";
    }
 
    private void initPaint() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        (textColor);
        (textSize);
        ();
 
        // Calculate text size        textWidth = (text);
         fm = ();
        textHeight =  - ;
    }
 
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int desiredW = (int) (direction == 0 ? getSuggestedMinimumWidth() : textWidth + getPaddingLeft() + getPaddingRight());
        int desiredH = (int) (direction == 1 ? getSuggestedMinimumHeight() : textHeight + getPaddingTop() + getPaddingBottom());
 
        int width = resolveSize(desiredW, widthMeasureSpec);
        int height = resolveSize(desiredH, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }
 
    @Override
    protected void onAttachedToWindow() {
        ();
        startScroll();
    }
 
    @Override
    protected void onDetachedFromWindow() {
        ();
        if (animator != null) ();
    }
 
    private void startScroll() {
        if (animator != null &amp;&amp; ()) return;
 
        float start, end, distance;
        if (direction == 0) {
            // Horizontal scrolling: start from the outside on the right and end from the outside on the left            start = getWidth();
            end = -textWidth;
            distance = start - end;
        } else {
            // Vertical scrolling: start from outside the bottom and end from outside the top            start = getHeight();
            end = -textHeight;
            distance = start - end;
        }
        long duration = (long) (distance / speed * 1000);
 
        animator = (this, "offset", start, end);
        (interpolator);
        (duration);
        (loop ?  : 0);
        (repeatMode);
        (repeatDelay);
        (new () {
            @Override public void onAnimationStart(Animator animation) { }
            @Override public void onAnimationEnd(Animator animation) { }
            @Override public void onAnimationCancel(Animator animation) { }
            @Override public void onAnimationRepeat(Animator animation) { }
        });
        ();
    }
 
    public void setOffset(float value) {
         = value;
        invalidate();
    }
 
    public float getOffset() { return offset; }
 
    @Override
    protected void onDraw(Canvas canvas) {
        (canvas);
        if (direction == 0) {
            // level            float y = getPaddingTop() - ().top;
            (text, offset, y, paint);
        } else {
            // Vertical            float x = getPaddingLeft();
            (text, x, offset - ().top, paint);
        }
    }
 
    // ==== More APIs can be added: pause(), resume(), setText(), setSpeed(), etc. ====}

5. Code interpretation

  1. Custom properties

    • existThe text content, color, size, speed, direction, interval, loop mode, interpolation and other attributes are defined;

    • Pass in the control constructorTypedArrayRead and initialize.

  2. Measurement logic

    • onMeasure()Determine the expected width and height of the control according to the scrolling direction;

    • For horizontal scrolling, the width is determined by the parent container and the height is determined by the text height plus the inner margin;

    • For vertical scrolling and vice versa.

  3. Drawing logic

    • onDraw(), according to the currentoffsetDraw text;

    • use()and()Calculate the width and height of the text and baseline.

  4. Animation logic

    • startScroll()In  , calculate the distance and duration from the start position to the end position;

    • useObjectAnimatorrightoffsetAttributes are used to animation;

    • Set the interpolation, number of cycles, cycle mode and delay;

    • existonDetachedFromWindow()Cancel the animation to prevent leakage.

  5. Scalability

    • ExposedsetText()setSpeed()pause()resume()etc.

    • Listen to user touch, supports sliding pause and manual drag;

    • Connect to RecyclerView and ListView to realize multiple marquee *s in the list.

6. Project Summary and Expansion

  1. Project gains

    • Deeply master the measurement, drawing and attribute animation of custom Views;

    • Learn to manage animation life cycle gracefully in custom controls;

    • The core algorithm for mastering the effect of marquee *s: offset calculation and duration conversion;

    • Learn how to achieve high configurability through XML properties.

  2. Performance optimization

    • Ensure that hardware is accelerated to avoid text drawing lags;

    • For extra long text or multiple columns, you can useStaticLayoutSegmented cache;

    • CombinedChoreographerAccurately control the frame rate;

  3. Advanced Development

    • Touch control: Drag and pause, manually fast forward and rewind;

    • Multi-line horse-drawn: Supports scrolling multiple lines of text at the same time, or background gradient;

    • Dynamic data sources: Combined with the network or database, update scrolling content in real time;

    • Jetpack Compose implementation:based onCanvasand()Compose plan;

The above is the detailed content of the sample code for Android to implement text scrolling effect. For more information about Android text scrolling, please follow my other related articles!