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
.