package me.omidh.ripplevalidatoredittext; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.AnimRes; import android.support.annotation.ColorInt; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import me.omidh.ripplevalidatoredittext.util.KeyboardUtility; import me.omidh.ripplevalidatoredittext.validator.RVEValidator; /** * Created by Omid Heshmatinia on 5/7/2017. */ public class RippleValidatorEditText extends LinearLayout{ List<RVEValidator> mValidators=new ArrayList<>(); private int mValidColor = Color.GREEN ; private int mNormalColor = Color.BLUE ; private int mBackgroundColor = Color.TRANSPARENT ; private int mTypingColor = Color.MAGENTA ; private int mErrorColor = Color.RED ; private int mWarningColor = Color.YELLOW ; private int mEditTextInputType = EditorInfo.TYPE_NULL; private int mImeOptions = 0; private int mEditTextColor = Color.BLACK ; private int mHintColor = Color.BLACK ; private int mHelperTextSize = 10; private int mStrokeWidth = 0; private int mEditTextSize = 10; private int mHelperTextGravity = Gravity.RIGHT; private int mEditTextGravity = Gravity.RIGHT|Gravity.CENTER; private Boolean mAutoValidate = true; private float[] mCornerRadius =new float[]{0,0,0,0,0,0,0,0}; private String mHintText=""; private TextView mHelperTextView; private EditText mEditText; private Typeface mTypeFace; private int mCircleRadius=0; private Paint mCirclePaint; private Paint mTransparentPaint; private RippleType rippleType = RippleType.IsPlaying; private Drawable mLastBorderDrawable; private int[] mNextFocusIds = new int[]{0,0,0,0,0}; //{ Down , Left , Up , Right , Forward} private int mHelperAnimation = R.anim.fade_in_slide_right; public void setText(String txt) { mEditText.setText(txt); } /** * Set entrance animation of helper text when error happen * @param animation */ public void setHelperTextAnimation(@AnimRes int animation){ mHelperAnimation=animation; } /** * Set Editor listener for handling keyboard Extra commands * @param listener */ public void setOnEditorActionListener(EditText.OnEditorActionListener listener){ mEditText.setOnEditorActionListener(listener); } public boolean validateWith(RVEValidator validator,Boolean showAnimation) { if(validator==null) throw new NullPointerException("The Validator Should Not Be NULL"); Boolean isValid=validator.isValid(getText()); if(!isValid) { updateViewColor(UIMode.ERROR,validator.getErrorMessage()); return false; } if(showAnimation) drawCircle(); updateViewColor(UIMode.COMPLETE,""); return true; } public void addTextChangedListener(TextWatcher textWatcher) { mEditText.addTextChangedListener(textWatcher); } private enum RippleType{ IsPlaying,IsClearing,IsEnded,Nothing } public RippleValidatorEditText(Context context) { super(context); init(null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); switch (rippleType){ case IsEnded: rippleType = RippleType.Nothing; mCircleRadius = 0; canvas.drawPaint(mTransparentPaint); //canvas.drawColor(Color.parseColor("#00000000")); mLastBorderDrawable.draw(canvas); break; case IsPlaying: // canvas.drawCircle(0,getHeight()/2,mCircleRadius,mCirclePaint); drawEffectWithBorder(canvas,mCirclePaint); mLastBorderDrawable.draw(canvas); break; case IsClearing: canvas.drawColor(mValidColor); // canvas.drawCircle(0, getHeight() / 2, mCircleRadius, mTransparentPaint); drawEffectWithBorder(canvas,mTransparentPaint); mLastBorderDrawable.draw(canvas); break; case Nothing: mCircleRadius = 0; canvas.drawPaint(mTransparentPaint); mLastBorderDrawable.draw(canvas); break; } } private void drawEffectWithBorder(Canvas canvas, Paint mTransparentPaint) { RectF clipBounds = new RectF(canvas.getClipBounds()); Path mPath= new Path(); mPath.addRoundRect(clipBounds, mCornerRadius, Path.Direction.CW); Bitmap result = Bitmap.createBitmap(getWidth(),getHeight(), Bitmap.Config.ARGB_8888); Canvas tempCanvas = new Canvas(result); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawCircle(0,getHeight()/2,mCircleRadius,mTransparentPaint); canvas.drawPath(mPath, paint); paint.setXfermode(null); //Draw result after performing masking canvas.drawBitmap(result, 0, 0, new Paint()); } private void drawEmptyCircle(){ rippleType = RippleType.IsClearing; ValueAnimator va = ValueAnimator.ofInt(0,getWidth()); va.setDuration(700); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { mCircleRadius=(int)animation.getAnimatedValue(); if(mCircleRadius==getWidth()) rippleType = RippleType.IsEnded; invalidate(); } }); va.start(); } private void drawCircle(){ rippleType = RippleType.IsPlaying; mCirclePaint.setColor(mValidColor); ValueAnimator va = ValueAnimator.ofInt(0,getWidth()); va.setDuration(700); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { mCircleRadius=(int)animation.getAnimatedValue(); if(mCircleRadius==getWidth()) drawEmptyCircle(); invalidate(); } }); va.setStartDelay(500); va.start(); } public RippleValidatorEditText(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } private void init(AttributeSet attrs){ setLayerType(LAYER_TYPE_HARDWARE, null); if(attrs!=null){ //if(!isInEditMode()) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RippleValidatorEditText); mEditTextInputType = a.getInt(R.styleable.RippleValidatorEditText_android_inputType, EditorInfo.TYPE_NULL); mNextFocusIds[0] = a.getResourceId(R.styleable.RippleValidatorEditText_android_nextFocusDown, 0); mNextFocusIds[1] = a.getResourceId(R.styleable.RippleValidatorEditText_android_nextFocusLeft, 0); mNextFocusIds[2] = a.getResourceId(R.styleable.RippleValidatorEditText_android_nextFocusUp, 0); mNextFocusIds[3] = a.getResourceId(R.styleable.RippleValidatorEditText_android_nextFocusRight, 0); mNextFocusIds[4] = a.getResourceId(R.styleable.RippleValidatorEditText_android_nextFocusForward, 0); mImeOptions = a.getInt(R.styleable.RippleValidatorEditText_android_imeOptions, 0); mHelperAnimation = a.getResourceId(R.styleable.RippleValidatorEditText_rve_helperAnimation,R.anim.fade_in_slide_right); mHintColor = a.getColor(R.styleable.RippleValidatorEditText_android_textColorHint, mHintColor); mBackgroundColor = a.getColor(R.styleable.RippleValidatorEditText_rve_backgroundColor, mBackgroundColor); mAutoValidate = a.getBoolean(R.styleable.RippleValidatorEditText_rve_validateOnFocusLost,mAutoValidate); mHintText = a.getString(R.styleable.RippleValidatorEditText_rve_hint); mEditTextGravity = a.getInteger(R.styleable.RippleValidatorEditText_rve_editTextGravity,mEditTextGravity); mHelperTextGravity = a.getInteger(R.styleable.RippleValidatorEditText_rve_helperTextGravity,mHelperTextGravity); String typeface=a.getString(R.styleable.RippleValidatorEditText_rve_font); if(typeface!=null){ mTypeFace = Typeface.createFromAsset(getContext().getAssets(),typeface); } mStrokeWidth = a.getDimensionPixelOffset(R.styleable.RippleValidatorEditText_rve_strokeWidth,mStrokeWidth); mEditTextSize=a.getDimensionPixelSize(R.styleable.RippleValidatorEditText_rve_editTextSize,(int) convertDipToPixels(mEditTextSize)); mHelperTextSize=a.getDimensionPixelSize(R.styleable.RippleValidatorEditText_rve_helperTextSize,(int)convertDipToPixels(mHelperTextSize)); //corner radius float topRight = a.getDimension(R.styleable.RippleValidatorEditText_rve_topRightCornerRadius,0); float topLeft = a.getDimension(R.styleable.RippleValidatorEditText_rve_topLeftCornerRadius,0); float BottomRight = a.getDimension(R.styleable.RippleValidatorEditText_rve_bottomRightCornerRadius,0); float BottomLeft = a.getDimension(R.styleable.RippleValidatorEditText_rve_bottomLeftCornerRadius,0); mCornerRadius = new float[]{topLeft,topLeft,topRight,topRight,BottomRight,BottomRight,BottomLeft,BottomLeft}; // colors mNormalColor = a.getColor(R.styleable.RippleValidatorEditText_rve_normalColor,mNormalColor); mErrorColor = a.getColor(R.styleable.RippleValidatorEditText_rve_errorColor,mErrorColor); mTypingColor = a.getColor(R.styleable.RippleValidatorEditText_rve_typingColor,mTypingColor); mEditTextColor = a.getColor(R.styleable.RippleValidatorEditText_rve_editTextColor,mEditTextColor); mValidColor = a.getColor(R.styleable.RippleValidatorEditText_rve_validColor,mValidColor); a.recycle(); //} } mCirclePaint = new Paint(); mCirclePaint.setColor(mErrorColor); mTransparentPaint=new Paint(); mTransparentPaint.setColor(Color.parseColor("#00000000")); mTransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); // To gain focus on edit text if user used next focus this.setOnFocusChangeListener(new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if(hasFocus) { mHelperTextView.setVisibility(GONE); mEditText.setVisibility(VISIBLE); mEditText.requestFocus(); KeyboardUtility.showKeyboard(getContext(),mEditText); } } }); this.setOrientation(VERTICAL); mEditText=new EditText(getContext()); LayoutParams lp =new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); mEditText.setPadding(getPaddingLeft(),getPaddingTop(),getPaddingRight(),getPaddingBottom()); lp.weight = 1; lp.gravity = mEditTextGravity; mEditText.setLayoutParams(lp); //mEditText.addTextChangedListener(new TextWatcher() { // @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // // } // // @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // // } // // @Override public void afterTextChanged(Editable s) { // //if(!s.toString().trim().equals("")){ // // mHelperTextView.setVisibility(VISIBLE); // // mHelperTextView.setText(s.toString().trim()); // // setBackGroundOfLayout(getShapeBackground(mWarningColor)); // // mHelperTextView.setTextColor(mWarningColor); // //} else { // // mHelperTextView.setVisibility(GONE); // // setBackGroundOfLayout(getShapeBackground(mNormalColor)); // //} // } //}); mEditText.setHint(mHintText); mEditText.setGravity(mEditTextGravity); mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX,mEditTextSize); mEditText.setBackgroundColor(Color.parseColor("#00000000")); mEditText.setTextColor(mEditTextColor); mEditText.setHintTextColor(mHintColor); mEditText.setInputType(mEditTextInputType); mEditText.setImeOptions(mImeOptions); mEditText.setOnFocusChangeListener(new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if(!hasFocus) isValid(mAutoValidate); else updateViewColor(UIMode.TYPING,""); } }); mHelperTextView = new TextView(getContext()); mHelperTextView.setMaxLines(1); mHelperTextView.setVisibility(GONE); mHelperTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,mHelperTextSize); mHelperTextView.setGravity(mHelperTextGravity); mHelperTextView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mHelperTextView.setVisibility(GONE); mEditText.setVisibility(VISIBLE); mEditText.requestFocus(); //showKeyboard(); KeyboardUtility.showKeyboard(getContext(),mEditText); } }); if(mTypeFace != null){ mHelperTextView.setTypeface(mTypeFace); mEditText.setTypeface(mTypeFace); } setupNextFocusViews(); updateViewColor(UIMode.NORMAL,""); setBackGroundOfLayout(getShapeBackground(mNormalColor)); this.addView(mHelperTextView); this.addView(mEditText); this.setGravity(Gravity.CENTER); } /** * Setup Next focus ids which are set in xml */ private void setupNextFocusViews() { for(int i=0;i<mNextFocusIds.length;i++){ if(mNextFocusIds[i]==0) continue; switch (i){ case 0: mEditText.setNextFocusDownId(mNextFocusIds[i]); break; case 1: mEditText.setNextFocusLeftId(mNextFocusIds[i]); break; case 2: mEditText.setNextFocusUpId(mNextFocusIds[i]); break; case 3: mEditText.setNextFocusRightId(mNextFocusIds[i]); break; case 4: mEditText.setNextFocusForwardId(mNextFocusIds[i]); break; } } } private void updateViewColor(@UIMode.UiType int type,String txt) { int color = 0; int visibility = VISIBLE; switch (type){ case UIMode.NORMAL: mEditText.setVisibility(VISIBLE); color = mNormalColor; visibility=GONE; break; case UIMode.COMPLETE: mEditText.setVisibility(VISIBLE); color = mValidColor; visibility=GONE; break; case UIMode.ERROR: mEditText.setVisibility(GONE); color = mErrorColor; showEntranceAnimation(); visibility=VISIBLE; break; case UIMode.TYPING: mEditText.setVisibility(VISIBLE); color = mTypingColor; visibility=GONE; break; } setBackGroundOfLayout(getShapeBackground(color)); mHelperTextView.setTextColor(color); mHelperTextView.setVisibility(visibility); mHelperTextView.setText(txt); } private void setBackGroundOfLayout(Drawable shape){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { setBackground(shape); } else { setBackgroundDrawable(shape); } } private Drawable getShapeBackground(@ColorInt int color){ GradientDrawable shape = new GradientDrawable(); ((GradientDrawable)shape.mutate()).setCornerRadii( mCornerRadius ); shape.setStroke(mStrokeWidth,color); shape.setColor(mBackgroundColor); mLastBorderDrawable = shape; return shape; } /** * Enter Error helper text With Animation */ private void showEntranceAnimation() { Animation viewAnimation= AnimationUtils.loadAnimation(getContext(),mHelperAnimation); mHelperTextView.startAnimation(viewAnimation); } private float convertDipToPixels(float dips) { return (dips * getContext().getResources().getDisplayMetrics().density + 0.5f); } public Boolean isValid(Boolean showAnimation) { for(int i=0;i<mValidators.size();i++){ Boolean isValid=mValidators.get(i).isValid(getText()); if(!isValid) { updateViewColor(UIMode.ERROR,mValidators.get(i).getErrorMessage()); return false; } } if(showAnimation) drawCircle(); updateViewColor(UIMode.COMPLETE,""); return true; } /** * @return entered input of edittext */ public CharSequence getText() { return mEditText.getText(); } public void addValidator(RVEValidator... validator) { mValidators = Arrays.asList(validator); } public EditText getEditText(){ return mEditText; } /** * Hide keyboard */ public void hideKeyboard(){ KeyboardUtility.hideKeyboard(getContext(),mEditText); } @Override protected Parcelable onSaveInstanceState() { super.onSaveInstanceState(); Bundle bundle = new Bundle(); bundle.putParcelable("superState", super.onSaveInstanceState()); bundle.putString("editText_title",mEditText.getText().toString()); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { if ( state!=null && state instanceof Bundle) { Bundle bundle = (Bundle) state; mEditText.setText(bundle.getString("editText_title")); state = bundle.getParcelable("superState"); } super.onRestoreInstanceState(state); } }