Domanda

I have searched for hours now for a solution but can't find one. I am trying to create a custom TextView which animates the values if it is numbers. However, I get an exception even before it starts the first value. I hope you can help.

Here is my code:

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.TextView;

public class AnimatedValueTextView extends TextView {
    private static final int DURATION = 900;
    private float actualValue;
    private ValueAnimator animator;

    public AnimatedValueTextView(Context context) {
        super(context);
        init();
    }

    public AnimatedValueTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AnimatedValueTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }


    private void init()
    {
        actualValue = 0;
        animator = new ValueAnimator();

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                setText(valueAnimator.getAnimatedValue().toString());
                actualValue = (Float)valueAnimator.getAnimatedValue();
            }
        });

        animator.setDuration(DURATION);
    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);

        boolean validValue = true;
        float newValue = 0;

        try {
            newValue = Float.parseFloat(this.getText().toString());
        }

        catch (Exception e)
        {
            validValue = false;
        }

        if(validValue)
        {
            if(animator == null)
            {
                init();
            }

            animator.cancel();
            animator.setFloatValues(actualValue, newValue);
            animator.start();
        }
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

And my XML

<com.---.-------.AnimatedValueTextView
            android:id="@+id/actualPressure"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/FunkyTextBig"
            android:paddingLeft="2dp"
            android:text="1043"/>

And the LogCat

01-16 09:55:14.954    5085-5085/com.---.----- E/AndroidRuntime﹕ FATAL EXCEPTION: main
    android.view.InflateException: Binary XML file line #18: Error inflating class com.---.-----.AnimatedValueTextView
            at android.view.LayoutInflater.createView(LayoutInflater.java:620)
            at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:696)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:755)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:758)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
            at com.---.-----.ForecastFragment.onCreateView(ForecastFragment.java:70)
            at android.app.Fragment.performCreateView(Fragment.java:1695)
            at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:885)
            at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1057)
            at android.app.BackStackRecord.run(BackStackRecord.java:682)
            at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435)
            at android.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:474)
            at android.support.v13.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:167)
            at android.support.v4.view.ViewPager.populate(ViewPager.java:1068)
            at android.support.v4.view.ViewPager.populate(ViewPager.java:914)
            at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1436)
            at android.view.View.measure(View.java:15848)
            at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:728)
            at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:477)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5008)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5008)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1404)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:695)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5008)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2189)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1905)
            at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1104)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1284)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1004)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5481)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
            at android.view.Choreographer.doCallbacks(Choreographer.java:562)
            at android.view.Choreographer.doFrame(Choreographer.java:532)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
            at android.os.Handler.handleCallback(Handler.java:730)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5103)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:525)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
            at dalvik.system.NativeStart.main(Native Method)
     Caused by: java.lang.reflect.InvocationTargetException
            at java.lang.reflect.Constructor.constructNative(Native Method)
            at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
            at android.view.LayoutInflater.createView(LayoutInflater.java:594)
            at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:696)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:755)
            at android.view.LayoutInflater.rInflate(LayoutInflater.java:758)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
            at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
            at com.---.-----.ForecastFragment.onCreateView(ForecastFragment.java:70)
            at android.app.Fragment.performCreateView(Fragment.java:1695)
            at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:885)
            at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1057)
            at android.app.BackStackRecord.run(BackStackRecord.java:682)
            at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435)
            at android.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:474)
            at android.support.v13.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:167)
            at android.support.v4.view.ViewPager.populate(ViewPager.java:1068)
            at android.support.v4.view.ViewPager.populate(ViewPager.java:914)
            at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1436)
            at android.view.View.measure(View.java:15848)
            at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:728)
            at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:477)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5008)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5008)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1404)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:695)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5008)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2189)
            at android.view.View.measure(View.java:15848)
            at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1905)
            at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1104)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1284)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1004)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5481)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
            at android.view.Choreographer.doCallbacks(Choreographer.java:562)
            at android.view.Choreographer.doFrame(Choreographer.java:532)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
            at android.os.Handler.handleCallback(Handler.java:730)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:5103)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:525)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
            at dalvik.system.NativeStart.main(Native Method)
     Caused by: java.lang.StackOverflowError
            at java.lang.RealToString.floatToString(RealToString.java:126)
            at java.lang.Float.toString(Float.java:322)
            at java.lang.Float.toString(Float.java:310)

And the style used:

<style name="FunkyTextBig" parent="@android:style/TextAppearance">
        <item name="android:textColor">#eaf1f7</item>
        <item name="android:textStyle">bold</item>
        <item name="android:shadowColor">#000000</item>
        <item name="android:shadowRadius">13</item>
        <item name="android:textSize">45sp</item>
        <item name="android:shadowDx">0</item>
        <item name="android:shadowDy">0</item>
    </style>

    <style name="FunkyTextMedium" parent="@style/FunkyTextBig">
        <item name="android:textSize">22sp</item>
    </style>
È stato utile?

Soluzione

Another nice solution is not overriding the onTextChanged Method, and having two methods setText: A public one that will trigger the animation, and a private one that will change the value without animating:

private void init(){
    actualValue = 0;
    animator = new ValueAnimator();
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            setTextInternal(valueAnimator.getAnimatedValue().toString());
            actualValue = (Float)valueAnimator.getAnimatedValue();
        }
    });

    animator.setDuration(DURATION);
}


public void setNumber(float newValue){
    animator.cancel();
    animator.setFloatValues(actualValue, newValue);
    animator.start();
}

public void setText(String txt){
    try {
        float newValue = Float.parseFloat(txt);
        setNumber(newValue);
    }catch (Exception e){
        super.setText(txt);
    }
}


private void setTextInternal(String txt){
    super.setText(txt);
}

Altri suggerimenti

You are getting this error because you are looping the setting of the text.

1) onTextChanged is called to save the initial value

2) The code in onTextChanged starts animating and in every step calls setText (which automatically will call onTextChanged)

3) This onTextChanged will cancel the animation and start it again.

The solution is to control by flag the calling of onTextChanged, add another listener to the Animation:

animator.addListener(new Animator.AnimatorListener(){
    public void onAnimationStart(Animator animation) {
        isAnimating = true;
    }
    public void onAnimationEnd(Animator animation) {
        isAnimating = false;
    }
    public void onAnimationRepeat(Animator animation) {}
    public void onAnimationCancel(Animator animation) {}
})


@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
    if (isAnimating) return;
    super.onTextChanged(text, start, lengthBefore, lengthAfter);

    ...
}

In your onTextChanged method, you are starting the animator, which in turn calls setText() again, which in turn triggers onTextChanged again, which goes on forever, until the VM has enough.

If you change your onTextChanged method as follows, it works:

@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
    super.onTextChanged(text, start, lengthBefore, lengthAfter);
    if (animator != null && (animator.isStarted() || animator.isRunning()))
        return;

    // rest as above
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top