How to alpha / translate animate each word in a Te

2019-03-27 15:35发布

问题:

I am working with the following Spannable and TextView like so and I've got it animating each character but I want to animate each word how can I accomplish that?

Looking to alpha in and translate each word (from the bottom of the location of each word).

By translating I mean a translate (move) animation.

Source:

private static class FadeyTextView extends TextView
{
    private Interpolator mInterpolator;
    private long mStart, mDurationPerLetter;
    private boolean mAnimating = false;

    private SpannableString mFadeyText;
    private CharSequence mText;


    public FadeyTextView(Context context)
    {
        super(context);
        initView();
    }

    public FadeyTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        initView();
    }

    public FadeyTextView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        initView();
    }

    private void initView()
    {
        // Set defaults
        mInterpolator = new DecelerateInterpolator();
        mDurationPerLetter = 250;
    }

    public void setInterpolator(Interpolator interpolator)
    {
        mInterpolator = interpolator;
    }

    public void setDurationPerLetter(long durationPerLetter)
    {
        mDurationPerLetter = durationPerLetter;
    }

    @Override
    public void setText(CharSequence text, BufferType type)
    {
        mText = text;

        mFadeyText = new SpannableString(text);

        FadeyLetterSpan[] letters = mFadeyText.getSpans(0, mFadeyText.length(), FadeyLetterSpan.class);
        for(FadeyLetterSpan letter : letters){
            mFadeyText.removeSpan(letter);
        }

        final int length = mFadeyText.length();
        for(int i = 0; i < length; i++){
            mFadeyText.setSpan(new FadeyLetterSpan(), i, i + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        }

        super.setText(mFadeyText, BufferType.SPANNABLE);

        mAnimating = true;
        mStart = AnimationUtils.currentAnimationTimeMillis();
        ViewCompat.postInvalidateOnAnimation(this);
    }

    @Override
    @CapturedViewProperty
    public CharSequence getText()
    {
        return mText;
    }

    public boolean isAnimating()
    {
        return mAnimating;
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        if(mAnimating){
            long mDelta   = AnimationUtils.currentAnimationTimeMillis() - mStart;

            FadeyLetterSpan[] letters = mFadeyText.getSpans(0, mFadeyText.length(), FadeyLetterSpan.class);
            final int length = letters.length;
            for(int i = 0; i < length; i++){
                FadeyLetterSpan letter = letters[i];
                float delta = (float)Math.max(Math.min((mDelta - (i * mDurationPerLetter)), mDurationPerLetter), 0);
                letter.setAlpha(mInterpolator.getInterpolation(delta / (float)mDurationPerLetter));
            }
            if(mDelta < mDurationPerLetter * length){
                ViewCompat.postInvalidateOnAnimation(this);
            }else{
                mAnimating = false;
            }
        }
    }


    private class FadeyLetterSpan extends CharacterStyle implements UpdateAppearance
    {
        private float mAlpha = 0.0f;


        public void setAlpha(float alpha)
        {
            mAlpha = Math.max(Math.min(alpha, 1.0f), 0.0f);
        }

        @Override
        public void updateDrawState(TextPaint tp)
        {
            int color = ((int)(0xFF * mAlpha) << 24) | (tp.getColor() & 0x00FFFFFF);
            tp.setColor(color);
        }
    }

Update:

So I have it animating each word instead of each character now, the way I did it was to use a wrapper for each word to hold its start and end index values:

private static class IndexWrapper {

    private int mStart;
    private int mEnd;

    public IndexWrapper(int start, int end) {
        this.mStart = start;
        this.mEnd = end;
    }

    public int getStart() {
        return mStart;
    }

    public int getEnd() {
        return mEnd;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + mEnd;
        result = prime * result + mStart;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        IndexWrapper other = (IndexWrapper) obj;
        if (mEnd != other.mEnd)
            return false;
        if (mStart != other.mStart)
            return false;
        return true;
    }

}

This is then called inside this function here to generate a List<IndexWrapper> indices:

private List<IndexWrapper> getAllIndexes(CharSequence text) {

    // First convert to a string and return an empty array if no words
    String s = text.toString();
    if (TextUtils.isEmpty(s))
        return null;

    // Get the number of words
    String[] words = text.toString().split("\\s+");

    // For each word we need to find the indices and create FadeWordSpans for them
    List<IndexWrapper> indices = new ArrayList<>();
    for (String word : words) {

        // Get our sub index values for each word
        List<IndexWrapper> subIndices = findIndexesForEachWord(s, word);

        // Add it to the list of indices
        indices.addAll(subIndices);
    }

    // Return our list of all index wrappers
    return indices;
}

private List<IndexWrapper> findIndexesForEachWord(String wholeString, String word) {

    String regex = "\\b" + word + "\\b";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(wholeString);

    List<IndexWrapper> indices = new ArrayList<>();

    while (matcher.find()) {

        // Get start and end indexes
        int start = matcher.start();
        int end = matcher.end();

        // Create a new index wrapper and add it to the our list
        indices.add(new IndexWrapper(start, end));
    }

    return indices;
}

Once we have our List<IndexWrapper> indices we can then call this inside the setText(CharSequence text, BufferType type) function like so:

@Override
public void setText(CharSequence text, BufferType type) {

    // Go ahead and set our text
    this.mText = text;

    // We need to initialize our SpannableString
    this.mFadeText = new SpannableString(text);

    // Go ahead and set our characters styles
    FadeWordSpan[] letters = mFadeText.getSpans(0, mFadeText.length(), FadeWordSpan.class);

    // For each word remove the span
    for (FadeWordSpan fadeWordSpan : letters)
        mFadeText.removeSpan(fadeWordSpan);

    List<IndexWrapper> indices = getAllIndexes(mFadeText.toString());

    final int length = indices != null ? indices.size() : 0;
    for (int i = 0; i < length; i++) {

        // Get index
        IndexWrapper index = indices.get(i);

        int start = index.getStart();
        int end = index.getEnd();
        mFadeText.setSpan(new FadeWordSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
    }

    super.setText(mFadeText, BufferType.SPANNABLE);

    // Set animating to true
    this.mIsAnimating = true;

    // Set our start time and redraw on animation
    mStart = AnimationUtils.currentAnimationTimeMillis();
    ViewCompat.postInvalidateOnAnimation(this);
}

We can then animate in each word in the onDraw(Canvas canvas) function like so:

    // If the view is animating (meaning we set the text) go ahead and draw the animations
    if (mIsAnimating) {

        long delta = AnimationUtils.currentAnimationTimeMillis() - mStart;

        // Initialize our word span
        FadeWordSpan[] words = mFadeText.getSpans(0, mFadeText.length(), FadeWordSpan.class);

        // Get our length
        final int length = words.length;

        // For each letter animate in the word
        for (int i = 0; i < length; i++){

            // Get our word
            FadeWordSpan word = words[i];

            // Calculate our delta
            float dx = (float) Math.max(Math.min((delta - (i * mDurationPerWord)), mDurationPerWord), 0);

            // Set our interpolation alpha
            word.setAlpha(mInterpolator.getInterpolation(dx / (float) mDurationPerWord));
        }

        // If our delta is less than the duration per word times the length then we need to keep re-drawing
        if (delta < mDurationPerWord * length)
            ViewCompat.postInvalidateOnAnimation(this);
        else
            mIsAnimating = false;
    }

FadeWordSpan is the same as FadeyLetterSpan.

Still need an answer to translating (motion) text