Lily Monta
Lily Monta

Reputation: 15

How to create a custom EditText with a blinking red background for setting errors in Android Kotlin?

I want to create a custom EditText in Android that shows a blinking red background when there is an error. The idea is to visually indicate to the user that input is invalid.

Here is what I have tried so far:

  1. I created a custom EditText class by extending the EditText class.
  2. I tried changing the background color programmatically using postDelayed for blinking.

Here is the code I have written:

class BlinkingEditText(context: Context, attrs: AttributeSet?) : AppCompatEditText(context, attrs) {

    fun showErrorBlink() {
        val handler = Handler(Looper.getMainLooper())
        var isRed = false

        val runnable = object : Runnable {
            override fun run() {
                if (isRed) {
                    setBackgroundColor(Color.WHITE)
                } else {
                    setBackgroundColor(Color.RED)
                }
                isRed = !isRed
                handler.postDelayed(this, 500) // Blink every 500ms
            }
        }

        handler.post(runnable)

        // Stop blinking after 3 seconds
        handler.postDelayed({ handler.removeCallbacks(runnable) }, 3000)
    }
}

This works, but I am facing the following issues:

  1. The blinking effect sometimes continues even after it is supposed to stop.
  2. Is this the best way to handle a blinking effect, or is there a better approach using animations or XML?

I would like to know:

  1. How can I ensure the blinking stops reliably after a fixed duration?
  2. Is there a more efficient way to achieve this effect, such as using Android's animation framework or XML drawables?

Any guidance or suggestions for improving this implementation would be greatly appreciated.

Upvotes: 1

Views: 29

Answers (1)

Benjamin Lawson
Benjamin Lawson

Reputation: 21

I have the best solution for this issue. i have recently implemented this into my android project.

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

import com.app.materialwallpaperapps.R;
import com.app.materialwallpaperapps.base.BaseActivity;


public class CustomEditText extends androidx.appcompat.widget.AppCompatEditText {

    private static final String TAG = BaseActivity.TAG;

    public CustomEditText(@NonNull Context context) {
        super(context);
        init();
    }

    public CustomEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        // Add a TextWatcher to listen for user input and reset the background if necessary
        this.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // No action needed here
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // Reset the background to the default when the user starts typing
                if (s.length() > 0) {
                    setBackgroundResource(R.drawable.edittext_bg); // Reset to original background
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
                // No action needed here
            }
        });
    }

    /**
     * Shows a blinking error background to indicate an error.
     */
    public void showBlinkableError() {
        // Ensure the current background is mutable to modify its color dynamically
        Drawable background = getBackground().mutate();

        if (background instanceof GradientDrawable) {
            GradientDrawable gradientDrawable = (GradientDrawable) background;

            // Define the error color (light red) and original color
            int errorColor = Color.parseColor("#FFCDD2"); // Light red
            int originalColor = ContextCompat.getColor(getContext(), R.color.lightBlueEt);

            // Store the original drawable
            Drawable originalBackground = background.getConstantState().newDrawable();

            // Create an ObjectAnimator to animate the color
            ObjectAnimator animator = ObjectAnimator.ofArgb(gradientDrawable, "color", errorColor, originalColor);

            // Set animation properties
            animator.setDuration(200); // 500ms per blink
            animator.setRepeatCount(3); // Blink 3 times
            animator.setRepeatMode(ValueAnimator.REVERSE); // Reverse back to the original color

            // Start the animation
            animator.start();
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    // Restore the original background after animation end
                    Log.d(TAG, "onAnimationEnd: originalBackground = " + originalBackground);
                    setBackground(originalBackground);
                }
            });

            // Set focus on the EditText
            this.requestFocus();
        } else {
            Log.e("CustomEditText", "Background is not a GradientDrawable. Ensure correct drawable is used.");
        }
    }


    /**
     * Validates if the EditText is not empty.
     *
     * @return true if not empty, false otherwise
     */
    public boolean validateEmpty() {
        if (getText() == null || getText().toString().trim().isEmpty()) {
            showBlinkableError();
            return true;
        }
        return false;
    }
} 

you can implement this in .xml file and do validation process in java or kotlin file as below

<com.app.materialwallpaperapps.util.custom.CustomEditText
                android:id="@+id/etName"
                style="@style/CustomEditTextStyleOneLine"
                android:hint="Example Corp"
                android:text="@={viewModel.mainBoardRoot.companyContactDetails.name}" /> 

Put this into java or kotlin file for validation.

if (binding.etName.validateEmpty()) {
                showToast("Please enter Name");
                return;
            }

Upvotes: 0

Related Questions