有没有一种方法可以让ellipsize="marquee"始终滚动?

97

我想在TextView上使用跑马灯效果,但是当TextView获取焦点时,文本才会滚动。这是个问题,因为在我的情况下,它不能获取焦点。

我正在使用:

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

有没有一种方法可以使TextView始终滚动其文本?我在Android Market应用程序中看到过这样的操作,应用程序名称将在标题栏中滚动,即使它没有获得焦点,但我在API文档中找不到相关内容。


要使跑马灯正常工作,应该选择TextView而不是将其聚焦。 聚焦可以进行选择但反过来则不行。 - Denis Gladkiy
1
尝试使用alwaysMarqueeTextView:https://dev59.com/questions/_XA75IYBdhLWcg3wW3y8#28806003 - Zar E Ahmer
我会更改这个答案的正确性 https://dev59.com/UHI-5IYBdhLWcg3wfoa1#3700651 - user25
8个回答

122

今天我终于碰到了这个问题,于是在 Android 市场应用中打开 hierarchyviewer。查看应用详细信息屏幕上的标题,他们使用了一个普通的 TextView。检查其属性后发现它没有焦点,也不能聚焦,并且通常非常普通,除了它被标记为已选定之外。

只需一行代码,我就解决了它 :)

textView.setSelected(true);

根据Javadoc的说法,这是有道理的:

视图可以被选择或未选择。请注意,选择并不等同于焦点。视图通常在像ListView或GridView这样的AdapterView的上下文中被选择。

也就是说,当您在列表视图中滚动一个项目(例如在Market应用程序中),只有现在选定的文本才会开始滚动。并且由于此特定的TextView无法获得焦点或单击,因此它永远不会失去其选择状态。

不幸的是,据我所知,没有办法从布局XML中预设选择状态。
但是上面的一行代码对我来说很有效。


@Mur:是的,请阅读TextView文档,并查看android:marqueeRepeatLimit - Christopher Orr
@Christopher,我指的不是重复次数,而是循环方式。默认情况下,如果文本被显示,它会跳到开头,这看起来不太流畅。 - Tima
如果包含可点击的URL跨度,则无法正常工作,并且在多次移动焦点时,跑马灯将停止。 - virsir
visir:当然不会,但是你为什么希望一个带有可点击链接的东西总是滚动呢? - Christopher Orr
1
这种方法比那个带有焦点的方法更好,因为它不会干扰视图的焦点。如果您有需要其父级获取焦点的视图,则该方法会破坏焦点。 这种方法简单高效,不需要每次更改焦点时依赖黑科技。 谢谢! - Ionut Negru
显示剩余8条评论

76

只需将这些参数放入TextView中即可。它有效 :)

    android:singleLine="true" 
    android:ellipsize="marquee"
    android:marqueeRepeatLimit ="marquee_forever"
    android:scrollHorizontally="true"
    android:focusable="true"
    android:focusableInTouchMode="true" 

1
如果这个回答还没有被关闭,我认为这应该是被采纳的答案。我将其添加到我的XML定义中的文本视图中,并且第一次就成功了。我尝试过其他建议,但这是最简单的方法。此外,添加“android:marqueeRepeatLimit =”marquee_forever“使其无限滚动。 - brack
19
如果TextView位于ListView的行内,使用此功能会破坏对该行的焦点选择。 - Tivie
完美运作。简单而优雅的解决方案。谢谢! - Rabi
如何启动和停止TextView的跑马灯效果 - Ashish Dwivedi
在我尝试了另一个答案之前,这对我没有起作用 - textView.setSelected(true); - Justin

65

我一直遇到这个问题,最简单的解决方案是创建一个从TextView派生的新类。 这个类应该重写三个方法onFocusChangedonWindowFocusChangedisFocused,使TextView始终处于聚焦状态。

@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;
}

7
如果你希望跑马灯不在菜单弹出时停止滚动,你还需要重写onWindowFocusChanged方法。 - virsir
7
顺便说一下,我们实际上在使用这种解决方案时遇到了问题,因为它会破坏具有状态的可绘制对象:如果您使用聚焦/失焦来更改可绘制对象,则会导致其出现故障。直到我们意识到这个 hack 导致了问题,我们花了将近一个小时进行调试。 - mxk
你能提供更详细的问题描述吗?我在许多应用程序中使用这个功能。我也想看看这个问题。 - hnviet
好的,这个解决方案只是一个恶劣的黑客。我只在那种简单的情况下使用它。我希望在以后的Android版本中,这个跑马灯功能会得到加强,这样就不再需要这个黑客了。 - hnviet
1
这个技巧非常有效,只有在屏幕上同时存在多个TextView(例如在ListView中使用时)才需要使用它——因为通常只能有一个TextView获得焦点,但是这种解决方案通过告诉系统所有的TextView都获得了焦点来“欺骗”系统:) 否则,对于单个TextView,应该使用类似 https://dev59.com/UHI-5IYBdhLWcg3wfoa1#3510891 的更简单的答案。 - dimsuz
显示剩余3条评论

13
TranslateAnimation通过指定的量在一个方向上"拉动"视图。您可以设置从哪里开始这种"拉动"和结束在哪里。
TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);

fromXDelta设置X轴上移动的起始位置偏移量。

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 定义在 X 轴上移动的结束位置偏移量。

toXDelta = 0 //no offset. 
toXDelta = 300 //the movement ends at 300px to the right.
toXDelta = -300 //the movement ends at 300px to the left.
如果文本宽度大于 fromXDelta 和 toXDelta 之间差的绝对值,那么该文本将无法完全在屏幕内移动。

示例

假设我们的屏幕尺寸为320x240像素。我们有一个带有700像素宽度文本的TextView,并希望创建一个“拉”文本的动画,以便我们可以看到短语的结尾。

                                       (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... ||
                             ---------------------------+|
                             |                           |
                             |                           |
                             |                           |
                             +---------------------------+

首先,我们将fromXDelta = 0以确保动画没有起始偏移量。现在,我们需要确定toXDelta的值。为了实现所需的效果,我们需要将文本“拉出”与屏幕宽度相同的像素数(在该方案中由<<<< X px >>>>表示)。由于我们的文本宽度为700,而可见区域为320px(屏幕宽度),因此我们设置:

tXDelta = 700 - 320 = 380

我们该如何确定屏幕宽度和文本宽度呢?


代码

以Zarah代码段为起点:

    /**
     * @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;
}

可能有更简单的方法来实现这个功能,但这种方法适用于您能想到的每个视图,而且可以重复使用。如果您想在ListView中动画化TextView而不破坏textView的启用/焦点能力,则此方法尤其有用。即使该视图未获得焦点,它也会持续滚动。


我的上面的回答(即添加一行代码:textView.setSelected(true);)在你的情况下不起作用吗? - Christopher Orr
不幸的是,我有一个ListView,其中每行都填充了几个TextView(因此空间紧张=P)。每个TextView的每一行都可以点击并弹出上下文菜单。将setSelected设置为true似乎会破坏上下文菜单。 - Tivie
这可能对大多数情况来说有点过头了吧?但对于我来说,由于上述原因,这是唯一的解决方案。(顺便说一下,它是可重复使用的!)=P - Tivie

12

我不知道你是否仍需要答案,但是我找到了一个简单的方法来解决这个问题。

按以下方式设置你的动画:

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_XEND_POS_XSTART_POS_YEND_POS_Yfloat值,而TICKER_DURATION是我在其他常量中声明的一个int

现在,您可以将此动画应用于您的TextView:

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

就是这样啦. :)

我的动画从屏幕右侧(300f)开始,到屏幕左侧(-300f)结束,持续时间为15秒(15000)。


1
那似乎不是很简单。但我认为他已经不需要答案了;请参见上面的被接受的答案.. :) - Christopher Orr
2
但是我认为这个解决方案比创建一个新的类更简单。:D 因为它可以让你在其他想要进行动画操作的视图中重复使用动画对象。;) - Zarah
虽然它工作得很好,但我该怎么办才能显示长文本?!现在我的文本将被截断。 - Tima
3
这并不是一个很好的解决方案,因为尺寸和持续时间参数极度依赖于文本大小。此外,即使文本长度小于视图宽度,也会进行动画。选择setSelected(true)确实是最简单的解决方案。 - Anm
这对我来说实际上是一个完美的解决方案。我尝试过使用TextView的跑马灯和HorizontalScrollView来尝试水平移动我的文本,但没有成功,但是通过这个动画,使用正确的位置和持续时间参数,我可以使一切按照我想要的方式工作。非常感谢! - Ícaro
显示剩余2条评论

5
我为跑马灯文本项的ListView编写了以下代码。它基于上述所描述的setSelected解决方案。基本上,我扩展了ArrayAdapter类并覆盖了getView方法,在返回TextView之前先选择它:
    // 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;

        }
    }

1
这是我在谷歌搜索中找到的答案,所以我想在此发布一个有用的答案,因为我经常难以记住。 无论如何,这对我很有效,需要使用XML属性和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);
            }
        }
    });

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" />

// 在Java中

        mtvMarque.setEllipsize(TextUtils.TruncateAt.MARQUEE);
        mtvMarque.setSelected(true);
        mtvMarque.setSingleLine(true);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接