19

I Am using a marquee to show the text in one of my Activitys. My question is it possible to speed up the rate of the marquee so it scrolls along the screen faster. Below is my XML and Java.

TextView et2 = (TextView) findViewById(R.id.noneednum);
    et2.setEllipsize(TruncateAt.MARQUEE);    
    et2.setText("");
    if (num.size() > 0) {
        for (String str : num) {
            et2.append(str + "    ");
        }
    }
    et2.setSelected(true);
}

And XML:

<TextView
    android:id="@+id/noneednum"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:ellipsize="marquee"
    android:fadingEdge="horizontal"
    android:gravity="center_vertical|center_horizontal"
    android:lines="1"
    android:marqueeRepeatLimit="marquee_forever"
    android:scrollHorizontally="true"
    android:singleLine="true"
    android:text="Large Text"
    android:textColor="#fff"
    android:textSize="140dp" />
Matt
  • 1,747
  • 8
  • 33
  • 58
  • http://code.google.com/p/android/issues/detail?id=6567 and http://stackoverflow.com/questions/5014578/android-and-a-textviews-horizontal-marquee-scroll-rate – Sergey Benner Jan 23 '12 at 11:33
  • @SergeyBenner The first link has `TextView.MARQUEE_SPEED_FAST cannot be resolved`and the second option looks way to complex must be an easier way? – Matt Jan 23 '12 at 11:38
  • The first link is the issue - for an enhancement to set the marque speed :) The second one is the link to a solution. I guess there's no other way but somebody might have solved in some other way... – Sergey Benner Jan 23 '12 at 11:48
  • Are you want to increase the scrolling speed – Ramakrishna Jan 23 '12 at 12:39
  • Just a note for others. The marquee doesn't animate when the android:maxLines="1" (I had to use android:singleLine even it is depreciated). – chaco Mar 05 '14 at 13:53

5 Answers5

55

You have to create a custom class for scrolling the text:

ScrollTextView.java

public class ScrollTextView extends TextView {

     // scrolling feature
     private Scroller mSlr;

     // milliseconds for a round of scrolling
     private int mRndDuration = 10000;

     // the X offset when paused
     private int mXPaused = 0;

     // whether it's being paused
     private boolean mPaused = true;

     /*
     * constructor
     */
     public ScrollTextView(Context context) {
         this(context, null);
         // customize the TextView
         setSingleLine();
         setEllipsize(null);
         setVisibility(INVISIBLE);
     }

     /*
     * constructor
     */
     public ScrollTextView(Context context, AttributeSet attrs) {
         this(context, attrs, android.R.attr.textViewStyle);
         // customize the TextView
         setSingleLine();
         setEllipsize(null);
         setVisibility(INVISIBLE);
     }

     /*
     * constructor
     */
     public ScrollTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         // customize the TextView
         setSingleLine();
         setEllipsize(null);
         setVisibility(INVISIBLE);
     }

     /**
     * begin to scroll the text from the original position
     */
     public void startScroll() {
         // begin from the very right side
         mXPaused = -1 * getWidth();
         // assume it's paused
         mPaused = true;
         resumeScroll();
     }

     /**
     * resume the scroll from the pausing point
     */
     public void resumeScroll() {

         if (!mPaused) return;

         // Do not know why it would not scroll sometimes
         // if setHorizontallyScrolling is called in constructor.
         setHorizontallyScrolling(true);

         // use LinearInterpolator for steady scrolling
         mSlr = new Scroller(this.getContext(), new LinearInterpolator());
         setScroller(mSlr);

         int scrollingLen = calculateScrollingLen();
         int distance = scrollingLen - (getWidth() + mXPaused);
         int duration = (new Double(mRndDuration * distance * 1.00000
         / scrollingLen)).intValue();

         setVisibility(VISIBLE);
         mSlr.startScroll(mXPaused, 0, distance, 0, duration);
         invalidate();
         mPaused = false;
     }

     /**
     * calculate the scrolling length of the text in pixel
     *
     * @return the scrolling length in pixels
     */
     private int calculateScrollingLen() {
         TextPaint tp = getPaint();
         Rect rect = new Rect();
         String strTxt = getText().toString();
         tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
         int scrollingLen = rect.width() + getWidth();
         rect = null;
         return scrollingLen;
     }

     /**
     * pause scrolling the text
     */
     public void pauseScroll() {
         if (null == mSlr) return;

         if (mPaused)
         return;

         mPaused = true;

         // abortAnimation sets the current X to be the final X,
         // and sets isFinished to be true
         // so current position shall be saved
         mXPaused = mSlr.getCurrX();

         mSlr.abortAnimation();
     }

     @Override
     /*
     * override the computeScroll to restart scrolling when finished so as that
     * the text is scrolled forever
     */
     public void computeScroll() {
         super.computeScroll();

         if (null == mSlr) return;

         if (mSlr.isFinished() && (!mPaused)) {
           this.startScroll();
         }
     }

     public int getRndDuration() {
       return mRndDuration;
     }

     public void setRndDuration(int duration) {
       this.mRndDuration = duration;
     }

     public boolean isPaused() {
       return mPaused;
     }
}

In your layout write like this:

<yourpackagename.ScrollTextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/scrolltext" />

In your activity write like this:

ScrollTextView scrolltext=(ScrollTextView) findViewById(R.id.scrolltext);
scrolltext.setText(yourscrollingtext);
scrolltext.setTextColor(Color.BLACK);
scrolltext.startScroll();

If you want to increase the scrolling speed then reduce the value of :

private int mRndDuration = 10000;//reduce the value of mRndDuration to increase scrolling speed
Mahesh Babariya
  • 4,560
  • 6
  • 39
  • 54
Ramakrishna
  • 4,066
  • 16
  • 48
  • 72
  • for multilanguage your custom marquee is not working ? do you know any other way? – Ghouse Feb 15 '13 at 05:26
  • @Ramakrishna awesome your code working fine. I have one more doubt how to scroll 1. top to bottom 2. bottom to top 3. right to left clarify my doubts – balaji Feb 21 '13 at 12:53
  • After putting my package name it crashes at that line. What am I doing wrong? – Laurynas G Mar 17 '13 at 18:14
  • @L.G. What is the error? Keep ScrollTextView.java Class in your package – Ramakrishna Mar 18 '13 at 08:56
  • 1
    speed looks to be quite random – Laurynas G Apr 05 '13 at 21:01
  • 2
    @Ramakrishna it worked fine for me. But here next scroll starts only after previous scroll ends. Example:Lets say I have a text "ABC". Its only after "C" scrolls away from the screen(to left),"A" of the next scroll comes to the screen(from right).As a result I am getting a blank screen after "C" of the previous scroll until it scrolls away.I need to start the next scroll ie "A" of the next scroll should come to screen just after "C" of previous scroll enters the screen,so that there is no blank space after between 2 scrolls. I hope I made myself clear. Now how to implement this? – Syamantak Basu May 29 '13 at 05:49
  • 1
    Great answer! Two things I changed though: |1. Currently speed depends on text's length. To fix it, make `int duration = (int) (1000f * distance / mScrollSpeed);`, where mScrollSpeed is around 100f. |2. To fix problems with speed changing after first run, start scroll after layout is calculated - use OnGlobalLayoutListener for that: `scrollTextView.getViewTreeObserver().addOnGlobalLayoutListener(new ... {scrollTextView.startScroll();//remove listener after that}});` – Koger Dec 04 '14 at 12:12
  • @Ramakrishna it's not work for me, I just added the class and use it in my layout xml but nothing even shown! why?! – Mahdi Jul 07 '15 at 20:39
  • Can you please explain the properties -> distance, duration ? – Jaydev Nov 03 '16 at 13:29
  • Why doesn't `android:gravity="center"` and the above ScrollTextView not work together ? If I give `android:gravity="center"`, then no scroll appears – Jaydev Nov 03 '16 at 21:26
6

Above code fails if the TextView is an instance of AppCompatTextView. Below code works is it is AppCompatTextView. Tested in Marshmallow.

public static void setMarqueeSpeed(TextView tv, float speed) {
    if (tv != null) {
        try {
            Field f = null;
            if (tv instanceof AppCompatTextView) {
                f = tv.getClass().getSuperclass().getDeclaredField("mMarquee");
            } else {
                f = tv.getClass().getDeclaredField("mMarquee");
            }
            if (f != null) {
                f.setAccessible(true);
                Object marquee = f.get(tv);
                if (marquee != null) {
                    String scrollSpeedFieldName = "mScrollUnit";
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        scrollSpeedFieldName = "mPixelsPerSecond";
                    }
                    Field mf = marquee.getClass().getDeclaredField(scrollSpeedFieldName);
                    mf.setAccessible(true);
                    mf.setFloat(marquee, speed);
                }
            } else {
                Logger.e("Marquee", "mMarquee object is null.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Hitesh Gupta
  • 1,091
  • 9
  • 14
  • This is beautiful. I love it and it worked. Thank you. If anyone else is having a problem, try using the `post` method with a runnable that has the marquee speed method callback in it. It is what got mine to work – Seth Feb 08 '20 at 21:32
  • 1
    It does not work with android 9 and newer. Because mPixelsPerSecond field was changed on android 9. I tried with "mPixelsPerMs" but it cannot find this field. – Huy Tran Jan 27 '21 at 04:56
0

This works for me. If f.get(tv) returns null, try calling mTextView.setSelected(true) before calling setMarqueeSpeed(). Original answer: Android and a TextView's horizontal marquee scroll rate

private void setMarqueeSpeed(TextView tv, float speed, boolean speedIsMultiplier) {

    try {
        Field f = tv.getClass().getDeclaredField("mMarquee");
        f.setAccessible(true);

        Object marquee = f.get(tv);
        if (marquee != null) {

            String scrollSpeedFieldName = "mScrollUnit";
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.L)
                scrollSpeedFieldName = "mPixelsPerSecond";

            Field mf = marquee.getClass().getDeclaredField(scrollSpeedFieldName);
            mf.setAccessible(true);

            float newSpeed = speed;
            if (speedIsMultiplier)
                newSpeed = mf.getFloat(marquee) * speed;

            mf.setFloat(marquee, newSpeed);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Community
  • 1
  • 1
Thomas Nielsen
  • 162
  • 1
  • 4
0

I resolved above scrolling problems on my device running Android 7.1 taking from multiple posts here and elsewhere

  1. Solved speed issue
  2. Scrolling only if needed / text is longer than width of TextView
  3. Works with extending TextView or AppCompatTextView

package com.myclass.classes;

import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.LinearInterpolator;
import android.widget.Scroller;
import android.widget.TextView;

public class ScrollTextView extends TextView {

// scrolling feature
private Scroller mSlr;

// the X offset when paused
private int mXPaused = 0;

// whether it's being paused
private boolean mPaused = true;

private float mScrollSpeed = 250f; //Added speed for same scrolling speed regardless of text

/*
 * constructor
 */
public ScrollTextView(Context context) {
    this(context, null);
    // customize the TextView
    setSingleLine();
    setEllipsize(null);
    setVisibility(VISIBLE);
    getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); //added listener check
}

/*
 * constructor
 */
public ScrollTextView(Context context, AttributeSet attrs) {
    this(context, attrs, android.R.attr.textViewStyle);
    // customize the TextView
    setSingleLine();
    setEllipsize(null);
    setVisibility(VISIBLE);
    getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); //added listener check
}

/*
 * constructor
 */
public ScrollTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    // customize the TextView
    setSingleLine();
    setEllipsize(null);
    setVisibility(VISIBLE);
    getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); //added listener check

}


@Override
protected void onDetachedFromWindow() {
    removeGlobalListener();
    super.onDetachedFromWindow();
}

/**
 * begin to scroll the text from the original position
 */
private void startScroll() {
    boolean needsScrolling = checkIfNeedsScrolling();
    // begin from the middle
    mXPaused = -1 * (getWidth() / 2);
    // assume it's paused
    mPaused = true;
    if (needsScrolling) {
        
        resumeScroll();
    } else {
        pauseScroll();
    }
    removeGlobalListener();
}

/**
 * Removing global listener
 **/
private synchronized void removeGlobalListener() {
    try {
        if (onGlobalLayoutListener != null)
            getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
        onGlobalLayoutListener = null;
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * Waiting for layout to initiate
 */
private OnGlobalLayoutListener onGlobalLayoutListener = () -> {
    startScroll();
};

/**
 * Checking if we need scrolling
 */
private boolean checkIfNeedsScrolling() {
    measure(0, 0);
    int textViewWidth = getWidth();
    if (textViewWidth == 0)
        return false;

    float textWidth = getTextLength();

    return textWidth > textViewWidth;
}

/**
 * resume the scroll from the pausing point
 */
public void resumeScroll() {

    if (!mPaused) return;

    // Do not know why it would not scroll sometimes
    // if setHorizontallyScrolling is called in constructor.
    setHorizontallyScrolling(true);

    // use LinearInterpolator for steady scrolling
    mSlr = new Scroller(this.getContext(), new LinearInterpolator());
    setScroller(mSlr);

    int scrollingLen = calculateScrollingLen();
    int distance = scrollingLen - (getWidth() + mXPaused);
    int duration = (int) (1000f * distance / mScrollSpeed);

    setVisibility(VISIBLE);
    mSlr.startScroll(mXPaused, 0, distance, 0, duration);
    invalidate();
    mPaused = false;
}

/**
 * calculate the scrolling length of the text in pixel
 *
 * @return the scrolling length in pixels
 */
private int calculateScrollingLen() {
    int length = getTextLength();
    return length + getWidth();
}

private int getTextLength() {
    TextPaint tp = getPaint();
    Rect rect = new Rect();
    String strTxt = getText().toString();
    tp.getTextBounds(strTxt, 0, strTxt.length(), rect);
    int length = rect.width();
    rect = null;
    return length;
}

/**
 * pause scrolling the text
 */
public void pauseScroll() {
    if (null == mSlr) return;

    if (mPaused)
        return;

    mPaused = true;

    // abortAnimation sets the current X to be the final X,
    // and sets isFinished to be true
    // so current position shall be saved
    mXPaused = mSlr.getCurrX();

    mSlr.abortAnimation();
}

@Override
/*
 * override the computeScroll to restart scrolling when finished so as that
 * the text is scrolled forever
 */
public void computeScroll() {
    super.computeScroll();

    if (null == mSlr) return;

    if (mSlr.isFinished() && (!mPaused)) {
        this.startScroll();
    }
}

public boolean isPaused() {
    return mPaused;
}
}
Tomek
  • 557
  • 2
  • 7
  • 24
0

With help of the accepted answer & some changes, you can now setSpeed() even at runtime using this library.

https://github.com/RohanPatil1/SpeedMarquee

Using XML

<com.rohan.speed_marquee.SpeedMarquee
        android:id="@+id/marqueeTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/dummy_text"
        android:maxLines="1"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:marquee_speed="180.0" />

Using Methods

 findViewById<Button>(R.id.speedButton).setOnClickListener {

            //Increment Text3's speed by 100.0
            marqueeTextView3.setSpeed(marqueeTextView3.getSpeed() + 100.0f)
        }
    }
Rohan Patil
  • 289
  • 2
  • 6