Texto giratorio de Android: uso dynamiclayout.draw (canvas) pero no se anima a lo largo de la ruta y no puedo usar canvas.drawTextOnPath (dynamiclayout)

11

Estoy usando mi propia versión de https://github.com/mdg-iitr/RotatingText para mostrar un widget de texto giratorio. Hay un video disponible en este GitHub, que le permite ver la animación. La idea es configurar filas de palabras. Las filas se muestran fila tras fila. La fila completa gira (también lo hacen sus palabras). Se muestra una fila después de la fila anterior cuando finaliza la animación de rotación de esta última.

Mi problema

Yo uso a DynamicLayoutpara mostrar las filas de texto. Recuerde: las filas deben rotar.

Mi problema es: obviamente, no puedo usar el método canvas.drawTextOnPath(dynamicLayoutObject). Así que lo que hago es: dynamicLayoutObjec.draw(canvas);. Pero no hay ninguna animación entonces. De hecho, el texto (por lo DynamicLayoutque lo contiene) debe rotar.

Resultado Esperado

El DynamicLayout(de hecho, su texto) debe estar animado (una rotación). La rotación se puede encontrar en la ilustración del repositorio original de Github que se encuentra al comienzo de esta Pregunta SO ( https://github.com/mdg-iitr/RotatingText ).

Mi pregunta

No sé cómo hacer que mi DynamicLayout(y / o su texto) gire a lo largo de mi camino.

Ejemplo mínimo y comprobable

He modificado la biblioteca original de RotatingText hace 8 meses aprox. para simplificarlo (menos clases, menos métodos, sin métodos no utilizados, etc.). De hecho, solo tengo dos clases:

  1. RotatingTextSwitcher, que es el widget XML

  2. Y Rotatable, que contiene el conjunto de cadenas a rotar.

  3. Un diseño .XML que contiene el widget XML RotatingTextSwitcherpara probarlo

  4. A Fragmentinflar el diseño mencionado anteriormente, configurar las palabras de cada fila giratoria y mostrarlas.

Para probarlo, cree una actividad que muestre el fragmento que se muestra a continuación, que a su vez utiliza las otras fuentes, presentadas anteriormente.

Clase giratoria

import android.graphics.Path;
import android.view.animation.Interpolator;

public class Rotatable {

    private final String[] text;
    private final int update_duration;
    private int animation_duration;
    private Path path_in, path_out;
    private int currentWordNumber;
    private Interpolator interpolator;

    public Rotatable(int update_duration, int animation_duration, Interpolator interpolator, String... text) {
        this.update_duration = update_duration;
        this.animation_duration = animation_duration;
        this.text = text;
        this.interpolator = interpolator;
        currentWordNumber = -1;
    }

    private int nextWordNumber() {
        currentWordNumber = (currentWordNumber + 1) % text.length;
        return currentWordNumber;
    }

    String nextWord() {
        return text[nextWordNumber()];
    }

    Path getPathIn() {
        return path_in;
    }
    void setPathIn(Path path_in) {
        this.path_in = path_in;
    }
    Path getPathOut() {
        return path_out;
    }
    void setPathOut(Path path_out) {
        this.path_out = path_out;
    }

    int getUpdateDuration() {
        return update_duration;
    }

    int getAnimationDuration() {
        return animation_duration;
    }

    Interpolator getInterpolator() { return interpolator; }
}

Clase RotatingTextSwitcher

package libs.rotating_text;

import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.text.DynamicLayout;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;

import androidx.appcompat.widget.AppCompatTextView;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;

import androidx.annotation.Nullable;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

public class RotatingTextSwitcher extends AppCompatTextView {

    Disposable disposable;

    private TextPaint textPaint = new TextPaint();
    private String text = "", old_text = "";
    SpannableStringBuilder base = new SpannableStringBuilder(text);
    SpannableStringBuilder base_old = new SpannableStringBuilder(old_text);
    private DynamicLayout layout = new DynamicLayout(base, textPaint,500, Layout.Alignment.ALIGN_CENTER,1.0F,0.0F,true);
    private DynamicLayout layout_old = new DynamicLayout(base_old, textPaint,500, Layout.Alignment.ALIGN_CENTER,1.0F,0.0F,true);

    private Rotatable rotatable;
    private Paint paint;
    private Path path_in, path_out;

    public RotatingTextSwitcher(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        paint = getPaint();
        paint.setAntiAlias(true);
    }

    public void setRotatable(Rotatable rotatable) {
        this.rotatable = rotatable;
        initialize();
    }

    private void initialize() {
        text = rotatable.nextWord();
        base.clear();
        base.append(text);
        old_text = text;
        base_old.clear();
        base_old.append(old_text);
        setUpPath();
        setDisposable();
        scheduleUpdateTextTimer();
    }

    private void setDisposable() {
        disposable = Observable.interval(1000 / 60, TimeUnit.MILLISECONDS, Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Long>() {
                    @Override
                    public void accept(Long aLong) {
                        invalidate();
                    }
                });
    }

    private void setUpPath() {
        post(new Runnable() {
            @Override
            public void run() {
                path_in = new Path();
                path_in.moveTo(0.0f, getHeight() - paint.getFontMetrics().bottom);
                path_in.lineTo(getWidth(), getHeight() - paint.getFontMetrics().bottom);
                rotatable.setPathIn(path_in);

                path_out = new Path();
                path_out.moveTo(0.0f, (2 * getHeight()) - paint.getFontMetrics().bottom);
                path_out.lineTo(getWidth(), (2 * getHeight()) - paint.getFontMetrics().bottom);
                rotatable.setPathOut(path_out);
            }
        });
    }

    private void scheduleUpdateTextTimer() {
        Timer update_text_timer = new Timer();
        update_text_timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                ((Activity) getContext()).runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        animateInHorizontal();
                        animateOutHorizontal();
                        old_text = text;
                        base_old.clear();
                        base_old.append(old_text);
                        text = rotatable.nextWord();
                        base.clear();
                        base.append(text);
                    }
                });
            }
        }, rotatable.getUpdateDuration(), rotatable.getUpdateDuration());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
        float size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 32, metrics);
        textPaint.setTextSize(size);

        if (rotatable.getPathIn() != null) {
            layout.draw(canvas);
            //canvas.drawTextOnPath(text, rotatable.getPathIn(), 0.0f, 0.0f, paint);
        }
        if (rotatable.getPathOut() != null) {
            layout_old.draw(canvas);
            //canvas.drawTextOnPath(old_text, rotatable.getPathOut(), 0.0f, 0.0f, paint);
        }
        setHeight(layout.getHeight() + layout_old.getHeight());
    }

    private void animateInHorizontal() {
        ValueAnimator animator = ValueAnimator.ofFloat(0.0f, getHeight());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                path_in = new Path();
                path_in.moveTo(0.0f, (Float) valueAnimator.getAnimatedValue() - paint.getFontMetrics().bottom);
                path_in.lineTo(getWidth(), (Float) valueAnimator.getAnimatedValue() - paint.getFontMetrics().bottom);
                rotatable.setPathIn(path_in);
            }
        });
        animator.setInterpolator(rotatable.getInterpolator());
        animator.setDuration(rotatable.getAnimationDuration());
        animator.start();
    }

    private void animateOutHorizontal() {
        ValueAnimator animator = ValueAnimator.ofFloat(getHeight(), getHeight() * 2.0f);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                path_out = new Path();
                path_out.moveTo(0.0f, (Float) valueAnimator.getAnimatedValue() - paint.getFontMetrics().bottom);
                path_out.lineTo(getWidth(), (Float) valueAnimator.getAnimatedValue() - paint.getFontMetrics().bottom);
                rotatable.setPathOut(path_out);
            }
        });
        animator.setInterpolator(rotatable.getInterpolator());
        animator.setDuration(rotatable.getAnimationDuration());
        animator.start();
    }


}

Un diseño

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <libs.rotating_text.RotatingTextSwitcher
        android:id="@+id/textView_presentation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="50dp"
        android:textSize="35sp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

Un fragmento

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.widget.ImageView;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.example.androidframework.R;

import libs.rotating_text.Rotatable;
import libs.rotating_text.RotatingTextSwitcher;

public class FragmentHomeSlide extends Fragment {

    private View inflated;
    private int drawable_id;
    private String[] text_presentation;

    @Override
    public void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        assert getArguments() != null;
        text_presentation = new String[];
        text_presentation[0] = "One row is set up with several words";
        text_presentation[1] = "This is another row";
    }

    @Override
    public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
        inflated = inflater.inflate(R.layout.home_slide, container, false);
        setWidgets();
        return inflated;
    }

    private void setWidgets() {    
        final RotatingTextSwitcher rotating_presentation = inflated.findViewById(R.id.textView_presentation);
        rotating_presentation.setRotatable(new Rotatable(1000, 500, new AccelerateInterpolator(), text_presentation));
    }
}
JarsOfJam-Scheduler
fuente
1
Usted ha descrito el problema, pero no ha dicho cuál es su solución esperada. ¿Qué le gustaría ver cuando la fila tiene demasiados símbolos?
azizbekian
1
No es demasiado, señor. Acabo de analizar su pregunta y no pude entender su requerimiento. Lo pedí para que quede claro también para otros lectores. Pensaré en el problema y encontraré una respuesta si encuentro alguna. Gracias.
azizbekian
@azizbekian He progresado mucho en la resolución de mi problema. Ahora creo que sé lo que estaba mal: no lo usé DynamicLayout. Entonces, ahora lo uso ... pero no puedo hacer que gire a lo largo del camino. He editado la pregunta. La recompensa aún está disponible :-).
JarsOfJam-Scheduler

Respuestas:

0

mi idea es cambiar la entrada con sus límites de problemas ... por ejemplo, si puede escribir 20 caracteres correctamente, cambie las matrices de cadenas a líneas con 20 caracteres o menos ...

agregue este código al constructor rotativo

    ArrayList<String> newttext = new ArrayList<>();
    for (int i = 0; i < text.length; i++) {
        if (text[i].length() > 20){ // 20 is number characters
            ArrayList<String> strings = myCutter(text[i]);
            newttext.addAll(strings);
        }else{
            newttext.add(text[i]);
        }
    }

el código del cortador de línea

    public ArrayList<String> myCutter(String s) {
    String trueLine = "";
    ArrayList<String> result = new ArrayList<>();
    if (s.length() > 20) {
        String[] split = s.split(" ");
        for (int i = 0; i < split.length; i++) {
            if (trueLine.length() + split[i].length() < 20) {
                trueLine = trueLine + " " + split[i];
            } else {
                result.add(trueLine);
                trueLine = "";
            }
        }
    } else {
        result.add(s);
    }
    return result;
}
mohandes
fuente
He progresado mucho en la resolución de mi problema. Ahora creo que sé lo que estaba mal: no lo usé DynamicLayout. Entonces, ahora lo uso ... pero no puedo hacer que gire a lo largo del camino. He editado la pregunta. La recompensa aún está disponible :-).
JarsOfJam-Scheduler