¿Hay alguna manera de hacer que ellipsize = "marquee" siempre se desplace?

93

Quiero usar el efecto de marco en un TextView, pero el texto solo se desplaza cuando el TextView se enfoca. Eso es un problema, porque en mi caso, no puede.

Estoy usando:

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

¿Hay alguna manera de que TextView siempre se desplace por su texto? He visto que esto se hace en la aplicación Android Market, donde el nombre de la aplicación se desplazará en la barra de título, incluso si no recibe el foco, pero no pude encontrar que esto se menciona en los documentos de la API.

Matías
fuente
Para hacer que la marquesina funcione, TextView debe estar seleccionado, no enfocado. El enfoque da la selección, pero no al revés.
Denis Gladkiy
1
Pruebe alwaysMarqueeTextView stackoverflow.com/a/28806003/3496570
AndroidGeek
Cambiaría la respuesta correcta para esta stackoverflow.com/a/3700651/4548520
user25

Respuestas:

62

He estado enfrentando el problema y la solución más corta que se me ha ocurrido es crear una nueva clase derivada de TextView. La clase debe anular tres métodos onFocusChanged , onWindowFocusChanged e isFocused para hacer que TextView esté todo enfocado.

@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;
}
hnviet
fuente
Perfecto, esta es la solución que estaba buscando.
Jason
6
También debe anular el método onWindowFocusChanged si desea que la marquesina no se detenga cuando aparezcan los menús.
virsir
7
Por cierto, en realidad tuvimos problemas con esta solución, ya que estropea los elementos de diseño con estado: si cambia los elementos de diseño con enfoque dentro / fuera, esto se romperá. Nos tomó casi una hora depurar hasta que nos dimos cuenta de que este hack lo estaba causando.
Matthias
¿Puede proporcionar una descripción más detallada del problema? Utilizo esta función en muchas aplicaciones. También me gustaría echar un vistazo a este problema.
Hnviet
1
Este truco funciona muy bien y solo es necesario cuando hay varios TextView en la pantalla al mismo tiempo (como cuando se usa en ListView), porque generalmente solo uno de ellos puede enfocarse, pero esta solución 'engaña' al sistema contándolos todos son :) De lo contrario, para un TextView único, se debe usar una respuesta más simple como stackoverflow.com/a/3510891/258848 .
dimsuz
120

Finalmente me encontré con este problema hoy y encendí hierarchyviewerla aplicación Android Market.

Al mirar el título en la pantalla de detalles de una aplicación, usan un antiguo TextView. El examen de sus propiedades mostró que no estaba enfocado, no podía enfocarse y, en general, era muy común, excepto por el hecho de que estaba marcado como seleccionado .

Una línea de código más tarde y lo tenía funcionando :)

textView.setSelected(true);

Esto tiene sentido, dado lo que dice el Javadoc :

Se puede seleccionar una vista o no. Tenga en cuenta que la selección no es lo mismo que el enfoque. Las vistas se seleccionan normalmente en el contexto de un AdapterView como ListView o GridView.

es decir, cuando se desplaza sobre un elemento en una vista de lista (como en la aplicación Market), solo entonces el texto seleccionado ahora comienza a desplazarse. Y dado que este en particular TextViewno se puede enfocar ni hacer clic, nunca perderá su estado de selección.

Desafortunadamente, hasta donde yo sé, no hay forma de preestablecer el estado seleccionado desde el XML de diseño.
Pero la frase de arriba funciona bien para mí.

Christopher Orr
fuente
solo me preguntaba: ¿esto también funcionaría para TextViews que son enfocables, pero aún deberían desplazarse cuando no están enfocados? Ahora, ¿el estado de selección se "pega" incluso cuando cambia el estado de enfoque?
Matthias
Supongo que sí. No veo por qué un cambio de enfoque en un básico TextView(es decir, uno no incrustado en algo "seleccionable" como a ListView) cambiaría el estado seleccionado.
Christopher Orr
A mí también me funciona bien. Gracias por su respuesta. ¿Existe la posibilidad de cambiar la forma de repetición?
Tima
@Mur: Sí, lee la TextViewdocumentación y échale un vistazo android:marqueeRepeatLimit.
Christopher Orr
1
Este método es mejor que el que tiene el enfoque, ya que no afecta el enfoque de las vistas. Si tiene puntos de vista que requieren que sus padres se concentren, ese método estropeará el enfoque. Este método es simple, eficiente y no depende de trucos para cambiar el enfoque cada vez. ¡Gracias!
Ionut Negru
75

Simplemente coloque estos parámetros en su TextView. Funciona :)

    android:singleLine="true" 
    android:ellipsize="marquee"
    android:marqueeRepeatLimit ="marquee_forever"
    android:scrollHorizontally="true"
    android:focusable="true"
    android:focusableInTouchMode="true" 
Vinayak
fuente
1
si este hilo de respuesta aún no está muerto, esta debería ser la respuesta aceptada, en mi opinión. Agregué esto a mi definición XML para la vista de texto y funcionó en la primera toma. He probado otras sugerencias y esta es la más sencilla. - Además, agregar "android: marqueeRepeatLimit =" marquee_forever "lo hace desplazarse indefinidamente
brack
19
El uso de esto rompe la selección de enfoque de una fila ListView si el TextView está dentro de ella.
Tivie
Funciona perfectamente. Solución simple y elegante. ¡Gracias!
Rabi
Cómo iniciar y detener la marquesina de TextView
Dwivedi Ji
Esto no funcionó para mí hasta que probé la otra respuesta: textView.setSelected (true);
Justin
13

TranslateAnimationfunciona "tirando" de la Vista en una dirección en una cantidad específica. Puede establecer dónde comenzar este "tirón" y dónde terminar.

TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);

fromXDelta establece el desplazamiento de la posición inicial del movimiento en el eje 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 define la posición final de desplazamiento del movimiento en el eje 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.

Si el ancho de su texto es mayor que el módulo de la diferencia entre fromXDelta y toXDelta, el texto no podrá moverse total y obligatoriamente dentro de la pantalla.


Ejemplo

Supongamos que nuestro tamaño de pantalla es de 320x240 px. Tenemos un TextView con un texto que tiene un ancho de 700px y deseamos crear una animación que "tire" del texto para que podamos ver el final de la frase.

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

Primero configuramos fromXDelta = 0para que el movimiento no tenga un desplazamiento inicial. Ahora necesitamos calcular el valor de toXDelta. Para lograr el efecto deseado, necesitamos "tirar" del texto exactamente con el mismo px que se extiende fuera de la pantalla. (en el esquema está representado por <<<< X px >>>>) Dado que nuestro texto tiene un ancho de 700, y el área visible es de 320 px (ancho de pantalla), establecemos:

tXDelta = 700 - 320 = 380

¿Y cómo calculamos el Ancho de pantalla y el Ancho del texto?


Código

Tomando el fragmento de Zarah como punto de partida:

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

Puede haber formas más fáciles de realizar esto, pero esto funciona para todas las vistas que pueda imaginar y es reutilizable. Es especialmente útil si desea animar un TextView en un ListView sin romper las capacidades habilitadas / onFocus del textView. También se desplaza continuamente incluso si la vista no está enfocada.

Tivie
fuente
¿Mi respuesta anterior (es decir, agregar una línea de código :) textView.setSelected(true);no funciona en su situación?
Christopher Orr
Lamentablemente no. Tenía un ListView poblado con varios TextViews en cada fila (de ahí la crisis espacial = P). se puede hacer clic en cada fila de la vista de texto y aparece un menú contextual. Establecer setSelected en verdadero parece romper el menú contextual.
Tivie
¿Lo es? Podría ser una especie de exageración en la mayoría de los casos. Para mí, por el motivo descrito anteriormente, era la única solución. (¡es reutilizable, por cierto!) = P
Tivie
12

No sé si todavía necesita la respuesta, pero encontré una manera fácil de hacerlo.

Configura tu animación así:

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_YY END_POS_Yson floatlos valores, mientras que TICKER_DURATIONes un intdeclaré con mis otras constantes.

Entonces ahora puede aplicar esta animación a su TextView:

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

Y eso es. :)

Mi animación comienza en el lado derecho fuera de la pantalla (300f) y termina en el lado izquierdo fuera de la pantalla (-300f), con una duración de 15 s (15000).

Zarah
fuente
1
Eso no parece muy sencillo. Pero no creo que todavía necesite la respuesta; ver la respuesta aceptada arriba .. :)
Christopher Orr
2
Pero creo que esta solución es más simple que crear una nueva clase. : D Tal como está, también puede reutilizar el objeto de animación para otras vistas que desee animar. ;)
Zarah
Aunque funciona bien, ¿pero qué debo hacer para mostrar textos largos? Ahora mi texto se acaba de editar
Tima
@Mur Votema: ¿Ha intentado configurar el ancho de su TextView en wrap_content?
Zarah
3
No es una gran solución, ya que los parámetros de tamaño y duración dependen en gran medida del tamaño del texto. Además, se animará, incluso si la longitud del texto es menor que el ancho de la vista. setSelected (true) es realmente la solución más sencilla.
Anm
5

Escribí el siguiente código para un ListView con elementos de texto de marquesina. Se basa en la solución setSelected descrita anteriormente. Básicamente, estoy extendiendo la clase ArrayAdapter y anulo el método getView para seleccionar TextView antes de devolverlo:

    // 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
fuente
1

Esta es la respuesta que aparece en la parte superior de mi búsqueda de Google, así que pensé que podría publicar una respuesta útil aquí, ya que me cuesta recordar esto con bastante frecuencia. De todos modos, esto funciona para mí y requiere atributos XML y A un 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
fuente
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" />

// En Java

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