Existe uma maneira de fazer ellipsize = “letreiro” sempre rolar?

93

Quero usar o efeito de letreiro em um TextView, mas o texto só está sendo rolado quando o TextView obtém o foco. Isso é um problema, porque no meu caso, não pode.

Estou usando:

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

Existe uma maneira de fazer o TextView sempre rolar seu texto? Já vi isso sendo feito no aplicativo Android Market, onde o nome do aplicativo vai rolar na barra de título, mesmo que não receba o foco, mas não consegui encontrar isso sendo mencionado nos documentos da API.

Matthias
fonte
Para fazer o letreiro funcionar, TextView deve ser selecionado, não focalizado. O foco fornece seleção, mas não o contrário.
Denis Gladkiy
1
Experimente alwaysMarqueeTextView stackoverflow.com/a/28806003/3496570
AndroidGeek
eu mudaria a resposta certa para este stackoverflow.com/a/3700651/4548520
user25

Respostas:

62

Tenho enfrentado o problema e a solução mais curta que encontrei é criar uma nova classe derivada de TextView. A classe deve substituir três métodos onFocusChanged , onWindowFocusChanged e isFocused para tornar o TextView totalmente focalizado.

@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
fonte
Perfeito - esta é exatamente a solução que eu estava procurando.
Jason
6
Você também deve substituir o método onWindowFocusChanged se quiser que o letreiro não pare quando os menus aparecerem.
virsir
7
A propósito, na verdade tivemos problemas com essa solução, pois ela bagunça drawables com estado: se você alterar drawables com foco interno / externo, isso será interrompido. Levamos quase uma hora de depuração até percebermos que esse hack estava causando isso.
Matthias
Você pode fornecer uma descrição mais detalhada do problema? Eu uso esse recurso em muitos aplicativos. Eu também gostaria de dar uma olhada neste problema.
hnviet
1
Este hack funciona muito bem e é necessário apenas quando vários TextViews estão na tela ao mesmo tempo (como quando usados ​​em ListView) - porque geralmente apenas um deles pode ser focado, mas esta solução 'engana' o sistema, contando todos eles são :) Caso contrário, para um único TextView, uma resposta mais simples como stackoverflow.com/a/3510891/258848 deve ser usada.
dimsuz 01 de
120

Finalmente me deparei com esse problema hoje e comecei hierarchyviewera usar o aplicativo Android Market.

Olhando para o título na tela de detalhes de um aplicativo, eles usam um simples e antigo TextView. O exame de suas propriedades mostrou que não estava focado, não podia ser focado e era geralmente muito comum - exceto pelo fato de que foi marcado como selecionado .

Uma linha de código depois e eu estava funcionando :)

textView.setSelected(true);

Isso faz sentido, dado o que o Javadoc diz :

Uma visualização pode ser selecionada ou não. Observe que a seleção não é o mesmo que o foco. As visualizações são normalmente selecionadas no contexto de uma AdapterView como ListView ou GridView.

ou seja, quando você rola sobre um item em uma exibição de lista (como no aplicativo Market), somente então o texto agora selecionado começa a rolar. E uma vez que este particular TextViewnão pode ser focalizado ou clicado, ele nunca perderá seu estado de seleção.

Infelizmente, até onde eu sei, não há como predefinir o estado selecionado no XML do layout.
Mas o one-liner acima funciona bem para mim.

Christopher Orr
fonte
apenas imaginando: isso também funcionaria para TextViews que podem ser focalizados, mas ainda devem rolar quando não estão em foco? Oi, o estado de seleção "fixa" mesmo quando o estado de foco muda?
Matthias,
Eu acho. Não vejo por que uma mudança de foco em um básico TextView(ou seja, um não incorporado em algo "selecionável" como a ListView) mudaria o estado selecionado.
Christopher Orr,
Funciona bem para mim também. Obrigado pela sua resposta. Existe possibilidade de alterar a forma de repetição ?!
Tima
@Mur: Sim, leia a TextViewdocumentação e dê uma olhada em android:marqueeRepeatLimit.
Christopher Orr
1
Este método é melhor do que aquele com o foco, pois não mexe com o foco das visualizações. Se você tem pontos de vista que exigem que os pais tenham foco, esse método bagunçará o foco. Este método é simples, eficiente e não depende de hacks para mudar o foco todas as vezes. Obrigado!
Ionut Negru
75

Basta colocar esses parâmetros em seu TextView. Funciona :)

    android:singleLine="true" 
    android:ellipsize="marquee"
    android:marqueeRepeatLimit ="marquee_forever"
    android:scrollHorizontally="true"
    android:focusable="true"
    android:focusableInTouchMode="true" 
Vinayak
fonte
1
se este tópico de resposta ainda não está morto, esta deve ser a resposta aceita, em minha opinião. Eu adicionei isso à minha definição XML para a visualização de texto e funcionou na primeira foto. Tentei outras sugestões e esta é a mais simples. - Além disso, adicionar "android: marqueeRepeatLimit =" marquee_forever "faz com que role indefinidamente
parêntese de
19
Usar isso quebra a seleção de foco de uma ListView Row se TextView estiver dentro dela.
Tivie
Funciona perfeitamente. Solução simples e elegante. Obrigado!
Rabi de
Como iniciar e parar o letreiro de TextView
Dwivedi Ji
Isso não funcionou para mim até que tentei a outra resposta - textView.setSelected (true);
Justin
13

TranslateAnimationfunciona "puxando" a visualização em uma direção por um valor especificado. Você pode definir onde começar este "puxar" e onde terminar.

TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta);

fromXDelta define o deslocamento da posição inicial do movimento no eixo 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 a posição final do deslocamento do movimento no eixo 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.

Se a largura do seu texto for maior que o módulo da diferença entre fromXDelta e toXDelta, o texto não poderá se mover total e obrigatoriamente na tela.


Exemplo

Vamos supor que o tamanho de nossa tela seja 320x240 pxs. Temos um TextView com um texto de 700px de largura e desejamos criar uma animação que “puxa” o texto para que possamos ver o final da 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... ||
                             ---------------------------+|
                             |                           |
                             |                           |
                             |                           |
                             +---------------------------+

Primeiro, definimos de fromXDelta = 0forma que o movimento não tenha um deslocamento inicial. Agora precisamos descobrir o valor toXDelta. Para obter o efeito desejado, precisamos "puxar" o texto exatamente no mesmo px que ele expande para fora da tela. (no esquema é representado por <<<< X px >>>>) Como nosso texto tem largura de 700, e a área visível é de 320 px (largura da tela), definimos:

tXDelta = 700 - 320 = 380

E como calculamos a largura da tela e a largura do texto?


Código

Tomando o Snippet Zarah como ponto 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;
}

Pode haver maneiras mais fáceis de fazer isso, mas funciona para todas as visualizações que você possa imaginar e é reutilizável. É especialmente útil se você deseja animar um TextView em um ListView sem quebrar as habilidades enabled / onFocus do textView. Ele também rola continuamente, mesmo se a visualização não estiver focada.

Tivie
fonte
Minha resposta acima (ou seja, adicionar uma linha de código textView.setSelected(true);:) não funciona na sua situação?
Christopher Orr
Infelizmente não. Eu tinha um ListView preenchido com vários TextViews em cada linha (daí a crise de espaço = P). cada linha do textview é clicável e abre um menu de contexto. Definir setSelected como true parece quebrar o menu de contexto.
Tivie
é isso? Pode ser uma espécie de ultrapassagem na maioria dos casos. Para mim, pelo motivo descrito acima, era a única solução. (é reutilizável, aliás!) = P
Tivie
12

Não sei se você ainda precisa da resposta, mas encontrei uma maneira fácil de fazer isso.

Configure sua animação assim:

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_YE END_POS_Ysão floatvalores, enquanto que TICKER_DURATIONé um intI declarado com minhas outras constantes.

Então agora você pode aplicar esta animação ao seu TextView:

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

E é isso. :)

Minha animação começa no lado direito fora da tela (300f) e termina no lado esquerdo fora da tela (-300f), com duração de 15s (15.000).

Zarah
fonte
1
Isso não parece muito simples. Mas não acho que ele ainda precise da resposta; veja a resposta aceita acima .. :)
Christopher Orr
2
Mas acho que essa solução é mais simples do que criar uma nova classe. : D Do jeito que está, você também pode reutilizar o objeto de animação para outras visualizações que deseja animar. ;)
Zarah
Funciona bem, mas o que devo fazer para exibir textos longos ?! Agora meu texto será apenas
cortado
@Mur Votema: Você já tentou definir a largura do TextView como wrap_content?
Zarah
3
Não é uma boa solução, pois os parâmetros de tamanho e duração são extremamente dependentes do tamanho do texto. Além disso, será animado, mesmo se o comprimento do texto for menor que a largura da visualização. setSelected (true) é realmente a solução mais fácil.
Anm
5

Escrevi o seguinte código para um ListView com itens de texto de letreiro. Ele é baseado na solução setSelected descrita acima. Basicamente, estou estendendo a classe ArrayAdapter e substituindo o método getView para selecionar o TextView antes de retorná-lo:

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

Esta é a resposta que aparece no topo da minha pesquisa no Google, então pensei que poderia postar uma resposta útil aqui, já que tenho dificuldade em lembrar isso com bastante frequência. De qualquer forma, isso funciona para mim e requer atributos XML e um 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
fonte
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" />

// Em Java

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