Is there a way to make ellipsize="marquee" always scroll?
Asked Answered
G

8

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.

Grassgreen answered 1/12, 2009 at 17:40 Comment(3)
To make marquee work TextView should be selected, not focused. Focus gives selection but not the reverse.Collectanea
Try alwaysMarqueeTextView https://mcmap.net/q/87907/-textview-marquee-not-working-duplicateCutwork
i would change the right answer for this one https://mcmap.net/q/88694/-is-there-a-way-to-make-ellipsize-quot-marquee-quot-always-scrollGerber
H
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;
}
Hemangioma answered 24/3, 2010 at 1:45 Comment(8)
Perfect - this is just the solution I was looking for.Forte
You should also override onWindowFocusChanged method if you want the marquee would not stop when menus pop up.Urology
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.Grassgreen
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.Hemangioma
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.Grassgreen
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.Hemangioma
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 https://mcmap.net/q/88694/-is-there-a-way-to-make-ellipsize-quot-marquee-quot-always-scroll should be used.Sanson
Is it possible to use it with mTitleTextView from toolbar?Typeset
M
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.

Malaya answered 13/9, 2010 at 13:6 Comment(13)
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?Grassgreen
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.Malaya
It works fine for me as well. Thank you for your answer. Is there possibility to change the way of repeatation?!Cottager
@Mur: Yes, read the TextView documentation and take a look at android:marqueeRepeatLimit.Malaya
@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 fluentCottager
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 !Clarino
Does not work if it contains a clickable url span, and move focus in and out some times, the marquee will stop.Urology
visir: Of course not, but then why would you want something with a clickable link to always scroll?Malaya
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?Mcgregor
@Mcgregor 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.Malaya
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.Mcgregor
sigh and I was missing just one line for more than a week.Winglet
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!Looker
C
75

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" 
Cardiac answered 18/8, 2010 at 9:58 Comment(5)
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 indefinitelyWoolsack
Using this breaks the focus selection of a ListView Row if the TextView is inside it.Langley
Works perfectly. Simple and elegant solution. Thanks!Fugitive
How to start and stop marquee of TextViewUrnfield
This didn't work for me until I tried the other answer - textView.setSelected(true);Williamwilliams
H
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;
}
Hemangioma answered 24/3, 2010 at 1:45 Comment(8)
Perfect - this is just the solution I was looking for.Forte
You should also override onWindowFocusChanged method if you want the marquee would not stop when menus pop up.Urology
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.Grassgreen
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.Hemangioma
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.Grassgreen
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.Hemangioma
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 https://mcmap.net/q/88694/-is-there-a-way-to-make-ellipsize-quot-marquee-quot-always-scroll should be used.Sanson
Is it possible to use it with mTitleTextView from toolbar?Typeset
L
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.

Langley answered 9/12, 2010 at 10:48 Comment(3)
Does my answer above (i.e. add one line of code: textView.setSelected(true);) not work in your situation?Malaya
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.Langley
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!) =PLangley
G
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).

Grecian answered 12/8, 2010 at 7:13 Comment(7)
That doesn't seem very simple. But I don't think he still needs the answer; see the accepted answer above.. :)Malaya
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. ;)Grecian
It works though well, but what should I do, to display long texts?! Now my text will be just cutedCottager
@Mur Votema: Have you tried setting your TextView's width to wrap_content?Grecian
@Grecian 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 layoutCottager
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.Rep
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!Rudd
M
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;

        }
    }
Mcgregor answered 19/1, 2012 at 10:9 Comment(0)
S
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);
            }
        }
    });
Shayn answered 29/12, 2016 at 19:16 Comment(0)
T
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);
Testicle answered 5/12, 2018 at 13:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.