Search code examples
androiduser-interfacespeech-recognitionfloating-action-buttonobjectanimator

FloatingActionButton does not return to original size after animation


I wrote a little STT-functionality, with a floating button that is pulsating after being clicked on to notify that the app is listening. This works quite well so far with the one annoying behavior that my floating button does not return to its original size in some cases.

The animation increases and decreases the size of the button, and I guess it gets stuck in the increased state, hence the randomness of this behavior. I just can't figure out how to catch that and set the size to the original one.

Action Listener of my Button:

 private View.OnTouchListener setVoiceButtonOnClick()
   {
      return new View.OnTouchListener()
      {
         @Override
         public boolean onTouch(View v, MotionEvent event)
         {
            if (event.getAction() == MotionEvent.ACTION_DOWN)
            {
               if(!voiceButton.isInitialized())
                  voiceButton.initAnimationValues();

               voiceButton.setPressed(true);
               listen();
            }
            return true;
         }
      };
   }

My Button extends FloatingActionButton, and does the following:

public class FloatingVoiceButton extends FloatingActionButton
{

   public static final float DEFAULT_ANIMATION_FACTOR = 1.2f;
   private boolean isInitialized = false;
   private int originalHeight;
   private int originalWidth;
   private boolean isAnimationRunning;

   private ObjectAnimator animator;


   public FloatingVoiceButton(Context context)
   {
      super(context);
   }

   public void initAnimationValues()
   {
      isInitialized = true;
      isAnimationRunning = false;
      originalHeight = getMeasuredHeight();
      originalWidth = getMeasuredWidth();

      animator = ObjectAnimator.ofPropertyValuesHolder(
              this,
              PropertyValuesHolder.ofFloat("scaleX", DEFAULT_ANIMATION_FACTOR),
              PropertyValuesHolder.ofFloat("scaleY", DEFAULT_ANIMATION_FACTOR));
      animator.setDuration(200);
      animator.setRepeatCount(ObjectAnimator.INFINITE);
      animator.setRepeatMode(ObjectAnimator.REVERSE);
   }

   public boolean isInitialized()
   {
      return isInitialized;
   }



   public void resetButtonSize()
   {
      setMeasuredDimension(originalWidth, originalHeight);
   }


   public boolean isAnimationRunning()
   {
      return isAnimationRunning;
   }


   public void animate(boolean doAnimation)
   {
      isAnimationRunning = doAnimation;

      if(doAnimation)
         animator.start();
      else
      {
         animator.end();
         setPressed(false);
         resetButtonSize();
         //destroyDrawingCache(); tried these without success
         //postInvalidate();
      }
   }
}

Finally I am controlling the button the start and end of the animation with my RecognitionListener:

public class InputVoiceRecognitionListener implements RecognitionListener
{
   private EditText targetEditText;
   private String originalContent;
   private final String DELIMITER = "\n\n";
   private FloatingVoiceButton button;

   public InputVoiceRecognitionListener(EditText editText, FloatingVoiceButton button)
   {
      targetEditText = editText;
      originalContent = editText.getText().toString();
      this.button = button;
   }

   @Override
   public void onReadyForSpeech(Bundle params)
   {
      button.animate(true);
   }

   @Override
   public void onBeginningOfSpeech()
   {
      originalContent = targetEditText.getText().toString();
   }

   @Override
   public void onRmsChanged(float rmsdB)
   {}

   @Override
   public void onBufferReceived(byte[] buffer)
   {}

   @Override
   public void onEndOfSpeech()
   {
      if(button.isAnimationRunning())
         button.animate(false);
   }

   @Override
   public void onError(int error)
   {
      if(button.isAnimationRunning())
         button.animate(false);
   }

   @Override
   public void onResults(Bundle results)
   {
setRecognizedText(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
   }

   @Override
   public void onPartialResults(Bundle partialResults)
   {
               setRecognizedText(partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
   }

   @Override
   public void onEvent(int eventType, Bundle params)
   {

   }

   private void setRecognizedText(ArrayList<String> matches)
   {
      String result = "";

      if(matches != null)
         result = matches.get(0);

      if((originalContent.trim()).length() > 0)
      {
         if(!originalContent.endsWith("\n\n"))
            result = originalContent + DELIMITER + result;
         else result = originalContent + result;
      }

      targetEditText.setText(result);
      targetEditText.setSelection(result.length());
   }
}

EDIT

This did it for me:

resettingAnimator = ObjectAnimator.ofPropertyValuesHolder(
          this,
          PropertyValuesHolder.ofFloat("scaleX", 1.0f),
          PropertyValuesHolder.ofFloat("scaleY", 1.0f));
  resettingAnimator.setDuration(0);
  resettingAnimator.setRepeatCount(1);

and calling resettingAnimator.start(); when I finish my main animation.


Solution

  • Simple solution to this problem is that you define another animation after stopping your repeating one.

    I just can't figure out how to catch that and set the size to the original one.

    You, that is View, does know what is the "original" size, its the size of the scale factor 1f. So after stopping repeating animation just make another animations to set scale to 1f

    PropertyValuesHolder.ofFloat("scaleX", 1f)
    PropertyValuesHolder.ofFloat("scaleY", 1f))
    

    This animation will run always, but will not be visible if your button is already at "normal" size.

    With this in mind I would recommend that you use some other flag than isAnimationRunning(), either by some state (ex. selected) of your Fab, or some manually set arbitrary boolean.