97

I want to use the marquee effect on a TextView, but the text is only being scrolled when the TextView gets focus. That's a problem, because in my case, it can't.

I am using:

  android:ellipsize="marquee"
  android:marqueeRepeatLimit="marquee_forever"

Is there a way to have the TextView always scroll its text? I've seen this being done in the Android Market app, where the app name will scroll in the title bar, even if it doesn't receive focus, but I couldn't find this being mentioned in the API docs.

Cristian
  • 198,401
  • 62
  • 356
  • 264
mxk
  • 43,056
  • 28
  • 105
  • 132

8 Answers8

122

I finally came up against this problem today and so fired up hierarchyviewer on the Android Market application.

Looking at the title on an app's detail screen, they use a plain old TextView. Examining its properties showed that it wasn't focused, couldn't be focused and was generally very ordinary — except for the fact that it was marked as selected.

One line of code later and I had it working :)

textView.setSelected(true);

This makes sense, given what the Javadoc says:

A view can be selected or not. Note that selection is not the same as focus. Views are typically selected in the context of an AdapterView like ListView or GridView.

i.e. When you scroll over an item in a list view (like in the Market app), only then does the now-selected text start scrolling. And since this particular TextView isn't focusable or clickable, it will never lose its selection state.

Unfortunately, as far as I know there is no way to pre-set the selected state from the layout XML.
But the one-liner above works fine for me.

Christopher Orr
  • 110,418
  • 27
  • 198
  • 193
  • just wondering: would this also work for TextViews that are focusable, but should still scroll when not being focused? I.o.w., does the select state "stick" even when the focus state changes? – mxk Sep 14 '10 at 09:25
  • I guess so. I don't see why a focus change on a basic `TextView` (i.e. one not embedded in something "selectable" like a `ListView`) would change the selected state. – Christopher Orr Sep 14 '10 at 11:11
  • It works fine for me as well. Thank you for your answer. Is there possibility to change the way of repeatation?! – Tima Dec 01 '10 at 08:41
  • @Mur: Yes, read the `TextView` documentation and take a look at `android:marqueeRepeatLimit`. – Christopher Orr Dec 02 '10 at 11:26
  • @Christopher I haven't meant number of repeats, but the way of looping. As default, if the text would be shown it jumps to begin and it doesn't look very fluent – Tima Dec 02 '10 at 12:36
  • I thought there is no solution for this problem ! Thanks for the answer , was trying to request focus for the textview, which was not going with the design , forgot that setSelected gives focus ! – sat Mar 18 '11 at 09:30
  • Does not work if it contains a clickable url span, and move focus in and out some times, the marquee will stop. – virsir Jun 05 '11 at 13:09
  • visir: Of course not, but then why would you want something with a clickable link to always scroll? – Christopher Orr Jun 06 '11 at 19:38
  • This solution looks appropriate to my case but I can't figure out how to get the child View object in the first place. I tried accessing the ListView's children right after setting its ListAdapter but getChildCount return 0... How do you get a reference to your TextView? – smichak Jan 18 '12 at 19:25
  • @smichak I can't imagine you want all items of a ListView to scroll at the same time, but I imagine you'd have to do this in the getView method of your Adapter. – Christopher Orr Jan 19 '12 at 10:04
  • I have a ListView that shows filename. Sometimes the filenames are quite long. I could place them on a multi-line but that marquee effect seems better. – smichak Jan 19 '12 at 10:32
  • *sigh* and I was missing just one line for more than a week. – 0xC0DED00D Mar 22 '12 at 09:02
  • 1
    This method is better than the one with the focus as it doesn't mess with the views focus. If you have views that require their parents to take focus, that method will mess the focus up. This method is simple, efficient and doesn't rely on hacks for changing the focus everytime. Thank you! – Ionut Negru Jul 16 '14 at 09:00
76

Just put these parameters in your TextView. It works :)

    android:singleLine="true" 
    android:ellipsize="marquee"
    android:marqueeRepeatLimit ="marquee_forever"
    android:scrollHorizontally="true"
    android:focusable="true"
    android:focusableInTouchMode="true" 
Paresh Mayani
  • 127,700
  • 71
  • 241
  • 295
Vinayak
  • 785
  • 5
  • 2
  • 1
    if this answer thread isn't already dead, this should be the accepted answer, in my opinion. I added this to my XML definition for the text view, and it worked on the first shot. I've tried other suggestions, and this is the simplest. -- Also, adding "android:marqueeRepeatLimit="marquee_forever" makes it scroll indefinitely – brack Nov 22 '10 at 05:42
  • 19
    Using this breaks the focus selection of a ListView Row if the TextView is inside it. – Tivie Dec 09 '10 at 10:57
  • Works perfectly. Simple and elegant solution. Thanks! – Rabi Jan 20 '12 at 17:41
  • How to start and stop marquee of TextView – Ashish Dwivedi May 24 '12 at 11:19
  • This didn't work for me until I tried the other answer - textView.setSelected(true); – Justin Jun 19 '14 at 18:10
65

I have been facing the problem and the shortest solution I have come up with is to create a new class derived from TextView. The class should override three methods onFocusChanged, onWindowFocusChanged and isFocused to make the TextView all focused.

@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
    if(focused)
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
}

@Override
public void onWindowFocusChanged(boolean focused) {
    if(focused)
        super.onWindowFocusChanged(focused);
}


@Override
public boolean isFocused() {
    return true;
}
Andrew Wyld
  • 7,133
  • 7
  • 54
  • 96
hnviet
  • 1,747
  • 1
  • 18
  • 22
  • Perfect - this is just the solution I was looking for. – Jason May 18 '10 at 02:45
  • 7
    You should also override onWindowFocusChanged method if you want the marquee would not stop when menus pop up. – virsir Jun 08 '10 at 07:34
  • 7
    By the way, we actually had problems with this solution, since it messes up stateful drawables: if you change drawables with focus-in/focus-out, then this will break. Took us almost an hour of debugging until we realized this hack was causing it. – mxk Dec 03 '10 at 14:23
  • Can you provide a more detailed description of the issue? I use this feature in many apps. I would like to take a look at this problem too. – hnviet Dec 04 '10 at 07:57
  • For the initial question I posted this works just fine. The problem occurred when we were using this on a button which scrolls its text. Since it reports the view as being focused all times, even when it isn't, and if you want to change the button drawable based on whether the view is focused or not, then this will obviously fail. – mxk Dec 09 '10 at 11:19
  • Ok, this solution is only a nasty hack. I only use it in that simple scenario. I hope in later versions of Android, this marquee feature will be enhanced, so this hack wasn't required anymore. – hnviet Dec 10 '10 at 00:08
  • 1
    This hack works great and is needed only when multiple TextViews are on screen at the same time (like when used in ListView) - because usually only one of them can be focused, but this solution 'tricks' the system by telling it all of them are :) Otherwise for single TextView simpler anwer like http://stackoverflow.com/a/3510891/258848 should be used. – dimsuz Sep 01 '13 at 09:54
  • Is it possible to use it with mTitleTextView from toolbar? – Makalele Oct 13 '16 at 13:15
13

TranslateAnimation works by "pulling" the View in one direction by a specified amount. You can set where to start this "pulling" and where to end.

TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);

fromXDelta set the offset of the starting position of the movement in the X axis.

fromXDelta = 0 //no offset. 
fromXDelta = 300 //the movement starts at 300px to the right.
fromXDelta = -300 //the movement starts at 300px to the left

toXDelta defines the offset ending position of the movement in the X axis.

toXDelta = 0 //no offset. 
toXDelta = 300 //the movement ends at 300px to the right.
toXDelta = -300 //the movement ends at 300px to the left.

If the width of your text is greater that the module of the difference between fromXDelta and toXDelta, the text won't be able to totaly and compeltely move within the screen.


Example

Let's assume our screen size is 320x240 pxs. We have a TextView with a text that has 700px width and we wish to create an animation that "pulls" the text so that we can see the end of the phrase.

                                       (screen)
                             +---------------------------+
                             |<----------320px---------->|
                             |                           |
                             |+---------------------------<<<< X px >>>>
               movement<-----|| some TextView with text that goes out...
                             |+---------------------------
                             |  unconstrained size 700px |
                             |                           |
                             |                           |
                             +---------------------------+


                             +---------------------------+
                             |                           |
                             |                           |
               <<<< X px >>>>---------------------------+|
movement<----- some TextView with text that goes out... ||
                             ---------------------------+|
                             |                           |
                             |                           |
                             |                           |
                             +---------------------------+

First we set fromXDelta = 0 so that the movement doesn't have a starting offset. Now we need to figure the toXDelta value. To achieve the desired effect we need to "pull" the text the exact same px that it spans out of the screen. (in the scheme is represented by <<<< X px >>>>) Since our text has 700 width, and the visible area is 320px (screen width) we set:

tXDelta = 700 - 320 = 380

And how do we figure the Screen Width and the text Width?


Code

Taking the Zarah Snippet as a starting point:

    /**
     * @param view The Textview or any other view we wish to apply the movement
     * @param margin A margin to take into the calculation (since the view
     *               might have any siblings in the same "row")
     *
     **/
public static Animation scrollingText(View view, float margin){

    Context context = view.getContext(); //gets the context of the view

            // measures the unconstrained size of the view
            // before it is drawn in the layout
    view.measure(View.MeasureSpec.UNSPECIFIED, 
                         View.MeasureSpec.UNSPECIFIED); 

            // takes the unconstrained wisth of the view
    float width = view.getMeasuredWidth();

            // gets the screen width
    float screenWidth = ((Activity) context).getWindowManager().getDefaultDisplay().getWidth();


            // perfrms the calculation
    float toXDelta = width - (screenWidth - margin);

            // sets toXDelta to 0 if the text width is smaller that the screen size
    if (toXDelta < 0) {toXDelta = 0; } else { toXDelta = 0 - toXDelta;}

            // Animation parameters
    Animation mAnimation = new TranslateAnimation(0, toXDelta, 0, 0);
    mAnimation.setDuration(15000); 
    mAnimation.setRepeatMode(Animation.RESTART);
    mAnimation.setRepeatCount(Animation.INFINITE);

    return mAnimation;
}

There might be easier ways to perform this, but this works for every view you can think of and is reusable. It is specially usefull if you want to animate a TextView in a ListView without breaking the enabled/onFocus abilities of the textView. It also scrolls continuously even if the View is not focused.

Tivie
  • 18,864
  • 5
  • 58
  • 77
  • Does my answer above (i.e. add one line of code: `textView.setSelected(true);`) not work in your situation? – Christopher Orr Dec 09 '10 at 12:16
  • unfortunately no. I had a ListView populated with several TextViews in each row (hence the space crisis =P). each row of the textview is clickable and pops up a context menu. Setting setSelected to true seems to break the context menu. – Tivie Dec 09 '10 at 21:50
  • is it? Might be kind of an overshot for most cases. For me, for the reason described above, was the only solution. (it's reusable, btw!) =P – Tivie Oct 23 '11 at 14:05
12

I don't know if you still need the answer, but I found an easy way to do this.

Set up your animation like so:

Animation mAnimation = new TranslateAnimation(START_POS_X, END_POS_X, 
                START_POS_Y, END_POS_Y);
mAnimation.setDuration(TICKER_DURATION); 
mAnimation.setRepeatMode(Animation.RESTART);
mAnimation.setRepeatCount(Animation.INFINITE);

START_POS_X, END_POS_X, START_POS_Y and END_POS_Y are float values, while TICKER_DURATION is an int I declared with my other constants.

Then you can now apply this animation to your TextView:

TextView tickerText = (TextView) findViewById(R.id.ticker);
tickerText.setAnimation(mAnimation);

And that's it. :)

My animation starts on the right side off-screen (300f) and ends on the left side off-screen(-300f), with a duration of 15s (15000).

Zarah
  • 5,179
  • 2
  • 29
  • 29
  • 1
    That doesn't seem very simple. But I don't think he still needs the answer; see the accepted answer above.. :) – Christopher Orr Aug 12 '10 at 10:53
  • 2
    But I think this solution is simpler than creating a new class. :D As it is, you can also re-use the animation object for other views you want to animate. ;) – Zarah Aug 13 '10 at 03:09
  • It works though well, but what should I do, to display long texts?! Now my text will be just cuted – Tima Dec 01 '10 at 08:39
  • @Mur Votema: Have you tried setting your TextView's width to wrap_content? – Zarah Dec 02 '10 at 02:00
  • @Zarah Yes, I have :) without success. I thought, that's probably it didn't work in my case, because Ticker-Text came first from server, but it's not the reason. I've tried it now with a constant long text in layout – Tima Dec 02 '10 at 08:51
  • 3
    Not a great solution, as the size and duration parameters are extremely dependent on the text size. Further, will animate, even if the text length is less than the view width. setSelected(true) is really the easiest solution. – Anm May 09 '11 at 22:49
  • This is actually a perfect solution for me. I've tried using both TextView's marquee and HorizontalScrollView to try and move my text horizontally without success but with this animation with the right position and duration parameters I could make everything work as I wanted. Big thanks! – Ícaro Mar 24 '16 at 14:05
5

I wrote the following code for a ListView with marquee text items. It is based on the setSelected solution described above. Basically, I am extending the ArrayAdapter class and override the getView method to select the TextView before returning it:

    // Create an ArrayAdapter which selects its TextViews before returning      
    // them. This would enable marqueeing while still making the list item
    // clickable.
    class SelectingAdapter extends ArrayAdapter<LibraryItem>
    {
        public
        SelectingAdapter(
            Context context, 
            int resource, 
            int textViewResourceId, 
            LibraryItem[] objects
        )
        {
            super(context, resource, textViewResourceId, objects);
        }

        @Override
        public
        View getView(int position, View convertView, ViewGroup parent)
        {
            View view = super.getView(position, convertView, parent);
            TextView textview = (TextView) view.findViewById(
                R.id.textview_playlist_item_title
            );
            textview.setSelected(true);
            textview.setEnabled(true);
            textview.setFocusable(false);
            textview.setTextColor(0xffffffff);
            return view;

        }
    }
smichak
  • 4,716
  • 3
  • 35
  • 47
1

This is the answer that pops up at the top of my google search so I thought I could post a useful answer here since I struggle with remembering this fairly often. Anyway, this works for me and requires XML attributes and A an onFocusChangeListener.

//XML
        <TextView
            android:id="@+id/blank_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:layout_gravity="center_horizontal|center_vertical"
            android:background="#a4868585"
            android:textColor="#fff"
            android:textSize="15sp"
            android:singleLine="true"
            android:lines="1"
            android:ellipsize="marquee"
            android:marqueeRepeatLimit ="marquee_forever"
            android:scrollHorizontally="true"
            android:focusable="true"
            android:focusableInTouchMode="true"
            tools:ignore="Deprecated" />

//JAVA
    titleText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (!hasFocus) {
                titleText.setSelected(true);
            }
        }
    });
Thunderstick
  • 1,193
  • 13
  • 12
1

// xml

 <TextView
            android:id="@+id/tvMarque"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:layout_gravity="center_horizontal"
            android:fadingEdge="horizontal"
            android:marqueeRepeatLimit="marquee_forever"
            android:scrollHorizontally="true"
            android:padding="5dp"
            android:textSize="16sp"
            android:text=""
            android:textColor="@color/colorSyncText"
            android:visibility="visible" />

// In Java

        mtvMarque.setEllipsize(TextUtils.TruncateAt.MARQUEE);
        mtvMarque.setSelected(true);
        mtvMarque.setSingleLine(true);
Soni Kumar
  • 283
  • 1
  • 4
  • 16