Android 自定义EditText(带清理、密码可见、不可见)

实际开发中经常会遇到输入框,各种不同的需求,在一般情况下,清除内容,以及密码可见与不可见基本很常见,那么不废话直接上代码!

  • res\values\attrs.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="SeniorEditText">
            <!--图片资源   使用的资源图不能使用vector形成的图片否则你懂的-->
            <!--清除按钮图-->
            <attr name="clearDrawable" format="reference"/>
            <!--可见按钮图-->
            <attr name="visibleDrawable" format="reference"/>
            <!--不可见按钮图-->
            <attr name="invisibleDrawable" format="reference"/>
            <!--按钮宽度大小-->
            <attr name="visibleDrawableWidth" format="dimension"/>
            <!--按钮间距大小 (自己的内边距)-->
            <attr name="visibleDrawableSpacing" format="dimension"/>
            <!--边框颜色-->
            <attr name="editFrameColor" format="color"/>
            <!--边样式     rectangle(矩形)、roundRect(圆角)、animator(动画划线特效)、halfRect(半矩形样式)-->
            <!-- 不填写样式 默认使用系统样式 -->
            <attr name="editFrameStyle" format="string" />
        </declare-styleable>
    </resources>
    
  • .java

   import android.animation.ObjectAnimator;
   import android.animation.ValueAnimator;
   import android.content.Context;
   import android.content.res.TypedArray;
   import android.graphics.Bitmap;
   import android.graphics.BitmapFactory;
   import android.graphics.Canvas;
   import android.graphics.Color;
   import android.graphics.Paint;
   import android.graphics.Rect;
   import android.graphics.RectF;
   import android.os.Build;
   import android.text.InputType;
   import android.util.AttributeSet;
   import android.util.Property;
   import android.view.MotionEvent;
   import android.view.animation.Animation;
   import android.view.animation.CycleInterpolator;
   import android.view.animation.TranslateAnimation;

   import androidx.annotation.Nullable;
   import androidx.appcompat.widget.AppCompatEditText;

   import com.chaian.phone.imlibrary.R;

   /**
    * 自定义可带清除、密码显示不显示的输入框
    */
   public class SeniorEditText extends AppCompatEditText {


       /**
        * 边框样式
        */
       private static final String STYLE_RECT = "rectangle";//矩形
       private static final String STYLE_ROUND_RECT = "roundRect";//圆角矩形
       private static final String STYLE_HALF_RECT = "halfRect";//半矩形
       private static final String STYLE_ANIMATOR = "animator";//动画特效

       private static final int DEFAULT_ROUND_RADIUS = 20;//圆角矩形圆角度
       private static final int ANIMATOR_TIME = 200;//动画时间
       private static final int DEFAULT_FOCUSED_STROKE_WIDTH = 8;//获取到焦点的边框宽
       private static final int DEFAULT_UNFOCUSED_STROKE_WIDTH = 4;//未获取焦点的边框宽
       private static final int DEFAULT_STYLE_COLOR = Color.RED;//边框默认颜色

       //按钮间隔
       private int visible_res_padding = 0;
       //按钮宽度
       private int visible_res_width = 0;
       //右内边距
       private int mTextPaddingRight;

       //需要显示的drawable 资源id
       private int clear_res_id = 0;
       private int visible_res_id = 0;
       private int invisible_res_id = 0;

       //需要显示的bitmap---通过drawable 资源id来
       private Bitmap clear_bitmap;
       private Bitmap visible_bitmap;
       private Bitmap invisible_bitmap;

       private String edit_border_style = "";
       private int edit_border_color = -1;

       //出现和消失动画
       private ValueAnimator show_animator;
       private ValueAnimator dismiss_animator;
       //状态值
       private boolean clear_image_isShow = false;
       private boolean show_password_image_isShow = false;
       private boolean dismiss_password_image_isShow = false;

       private boolean isAnimatorRunning = false;
       private int mAnimatorProgress = 0;
       private ObjectAnimator mAnimator;

       //自定义属性动画
       private static final Property<SeniorEditText, Integer> BORDER_PROGRESS
        = new Property<SeniorEditText, Integer>(Integer.class, "borderProgress") {
           @Override
           public Integer get(SeniorEditText seniorEditText) {
               return seniorEditText.getBorderProgress();
           }

           @Override
          public void set(SeniorEditText seniorEditText, Integer value) {
              seniorEditText.setBorderProgress(value);
          }
      };

      private Paint mPaint;


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

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

      private void init(Context context, AttributeSet attrs) {
          //抗锯齿和位图滤波
          mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
          //读取xml文件中的配置
          if (attrs != null) {
              TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SeniorEditText);
              //获取清除图标资源
              clear_res_id = array.getResourceId(R.styleable.SeniorEditText_clearDrawable, R.drawable.ic_content_clear);
              //获取可见图标资源
              visible_res_id = array.getResourceId(R.styleable.SeniorEditText_visibleDrawable, R.drawable.ic_content_visible);
              //获取不可见图标资源
              invisible_res_id = array.getResourceId(R.styleable.SeniorEditText_invisibleDrawable, R.drawable.ic_content_invisible);
              //可见图标按钮默认宽
              visible_res_width = array.getDimensionPixelSize(R.styleable.SeniorEditText_visibleDrawableWidth, dp2px(context,24));
              //可见图标按钮默认内边距
              visible_res_padding = array.getDimensionPixelSize(R.styleable.SeniorEditText_visibleDrawableSpacing, dp2px(context,5));
              //输入框边框样式
              edit_border_style = array.getString(R.styleable.SeniorEditText_editFrameStyle);
              //输入框边框颜色
              edit_border_color = array.getColor(R.styleable.SeniorEditText_editFrameColor, DEFAULT_STYLE_COLOR);
              array.recycle();
          }
          //初始化按钮显示的Bitmap
          clear_bitmap = createBitmap(context, clear_res_id, R.drawable.ic_content_clear);
          visible_bitmap = createBitmap(context, visible_res_id, R.drawable.ic_content_visible);
          invisible_bitmap = createBitmap(context, invisible_res_id, R.drawable.ic_content_invisible);
          //如果自定义,则使用自定义的值,否则使用默认值
          if (visible_res_padding == 0) {
              visible_res_padding = dp2px(context,5);
          }
          if (visible_res_width == 0) {
              visible_res_width = dp2px(context,24);
          }
          //给文字设置一个padding,避免文字和按钮重叠了
          mTextPaddingRight = visible_res_padding * 4 + visible_res_width * 2;
          //按钮出现和消失的动画
          show_animator = ValueAnimator.ofFloat(1f, 0f).setDuration(ANIMATOR_TIME);
          dismiss_animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATOR_TIME);
          //是否是密码样式
          show_password_image_isShow = getInputType() == (InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_CLASS_TEXT);

      }

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

          //设置右内边距, 防止清除按钮和文字重叠
          setPadding(getPaddingLeft(), getPaddingTop(), mTextPaddingRight, getPaddingBottom());
      }

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

          mPaint.setStyle(Paint.Style.STROKE);

          //使用自定义颜色。如未定义,则使用默认颜色
          if (edit_border_color != -1) {
              mPaint.setColor(edit_border_color);
          } else {
              mPaint.setColor(DEFAULT_STYLE_COLOR);
          }

          //控件获取焦点时,加粗边框
          if (isFocused()) {
              mPaint.setStrokeWidth(DEFAULT_FOCUSED_STROKE_WIDTH);
          } else {
              mPaint.setStrokeWidth(DEFAULT_UNFOCUSED_STROKE_WIDTH);
          }


          //绘制边框
          drawBorder(canvas);

          //绘制清空和明文显示按钮
          drawButtons(canvas);
      }

      private void drawBorder(Canvas canvas) {
          int width = getWidth();
          int height = getHeight();

          switch (edit_border_style) {
              //矩形样式
              case STYLE_RECT:
                  setBackground(null);
                  canvas.drawRect(0, 0, width, height, mPaint);
                  break;

              //圆角矩形样式
              case STYLE_ROUND_RECT:
                  setBackground(null);
                  float roundRectLineWidth = 0;
                  if (isFocused()) {
                      roundRectLineWidth = DEFAULT_FOCUSED_STROKE_WIDTH / 2;
                  } else {
                      roundRectLineWidth = DEFAULT_UNFOCUSED_STROKE_WIDTH / 2;
                  }
                  mPaint.setStrokeWidth(roundRectLineWidth);
                  if (Build.VERSION.SDK_INT >= 21) {
                      canvas.drawRoundRect(
                        roundRectLineWidth / 2, roundRectLineWidth / 2, width - roundRectLineWidth / 2, height - roundRectLineWidth / 2,
                        DEFAULT_ROUND_RADIUS, DEFAULT_ROUND_RADIUS,
                        mPaint);
                  } else {
                      canvas.drawRoundRect(
                           new RectF(roundRectLineWidth / 2, roundRectLineWidth / 2, width - roundRectLineWidth / 2, height - roundRectLineWidth / 2),
                        DEFAULT_ROUND_RADIUS, DEFAULT_ROUND_RADIUS,
                        mPaint);
                  }
                  break;

              //半矩形样式
              case STYLE_HALF_RECT:
                  setBackground(null);
                  canvas.drawLine(0, height, width, height, mPaint);
                  canvas.drawLine(0, height / 2, 0, height, mPaint);
                  canvas.drawLine(width, height / 2, width, height, mPaint);
                  break;

              //动画特效样式
              case STYLE_ANIMATOR:
                  setBackground(null);
                  if (isAnimatorRunning) {
                      canvas.drawLine(width / 2 - mAnimatorProgress, height, width / 2 + mAnimatorProgress, height, mPaint);
                      if (mAnimatorProgress == width / 2) {
                          isAnimatorRunning = false;
                      }
                  } else {
                      canvas.drawLine(0, height, width, height, mPaint);
                  }
                  break;
              default:
                  break;
          }
      }

      private void drawButtons(Canvas canvas) {
          if (clear_image_isShow) {
              //播放按钮出现的动画
              if (dismiss_animator.isRunning()) {
                  float scale = (float) dismiss_animator.getAnimatedValue();
                  drawClearButton(scale, canvas);
                  if (show_password_image_isShow) {
                      drawVisibleButton(scale, canvas, dismiss_password_image_isShow);
                  }
                  invalidate();
                  //绘制静态的按钮
              } else {
                  drawClearButton(1, canvas);
                  if (show_password_image_isShow) {
                      drawVisibleButton(1, canvas, dismiss_password_image_isShow);
                  }
              }
          } else {
              //播放按钮消失的动画
              if (show_animator.isRunning()) {
                  float scale = (float) show_animator.getAnimatedValue();
                  drawClearButton(scale, canvas);
                  if (show_password_image_isShow) {
                      drawVisibleButton(scale, canvas, dismiss_password_image_isShow);
                  }
                  invalidate();
              }
          }
      }

      private void drawClearButton(float scale, Canvas canvas) {

          int right = (int) (getWidth() + getScrollX() - visible_res_padding - visible_res_width * (1f - scale) / 2f);
          int left = (int) (getWidth() + getScrollX() - visible_res_padding - visible_res_width * (scale + (1f - scale) / 2f));
          int top = (int) ((getHeight() - visible_res_width * scale) / 2);
          int bottom = (int) (top + visible_res_width * scale);
          Rect rect = new Rect(left, top, right, bottom);
          canvas.drawBitmap(clear_bitmap, null, rect, mPaint);
      }

      private void drawVisibleButton(float scale, Canvas canvas, boolean isVisible) {

          int right = (int) (getWidth() + getScrollX() - visible_res_padding * 3 - visible_res_width - visible_res_width * (1f - scale) / 2f);
          int left = (int) (getWidth() + getScrollX() - visible_res_padding * 3 - visible_res_width - visible_res_width * (scale + (1f - scale) / 2f));
          int top = (int) ((getHeight() - visible_res_width * scale) / 2);
          int bottom = (int) (top + visible_res_width * scale);
          Rect rect = new Rect(left, top, right, bottom);
          if (isVisible) {
              canvas.drawBitmap(visible_bitmap, null, rect, mPaint);
          } else {
              canvas.drawBitmap(invisible_bitmap, null, rect, mPaint);
          }

      }

      // 清除按钮出现时的动画效果
      private void startVisibleAnimator() {
          endAllAnimator();
          dismiss_animator.start();
          invalidate();
      }

      // 清除按钮消失时的动画效果
      private void startGoneAnimator() {
          endAllAnimator();
          show_animator.start();
          invalidate();
      }

      // 结束所有动画
      private void endAllAnimator() {
          show_animator.end();
          dismiss_animator.end();
      }

      @Override
      protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
          super.onFocusChanged(focused, direction, previouslyFocusedRect);

          //播放按钮出现和消失动画
          if (focused && getText().length() > 0) {
              if (!clear_image_isShow) {
                  clear_image_isShow = true;
                  startVisibleAnimator();
              }
         } else {
              if (clear_image_isShow) {
                  clear_image_isShow = false;
                  startGoneAnimator();
              }
          }

          //实现动画特效样式
          if (focused && edit_border_style.equals(STYLE_ANIMATOR)) {
              isAnimatorRunning = true;
              mAnimator = ObjectAnimator.ofInt(this, BORDER_PROGRESS, 0, getWidth() / 2);
              mAnimator.setDuration(ANIMATOR_TIME);
              mAnimator.start();
          }
      }

      protected void setBorderProgress(int borderProgress) {
          mAnimatorProgress = borderProgress;
          postInvalidate();
      }

      protected int getBorderProgress() {
          return mAnimatorProgress;
      }

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

          if (text.length() > 0 && isFocused()) {
              if (!clear_image_isShow) {
                  clear_image_isShow = true;
                  startVisibleAnimator();
              }
          } else {
              if (clear_image_isShow) {
                  clear_image_isShow = false;
                  startGoneAnimator();
              }
          }
      }

      @Override
      public boolean onTouchEvent(MotionEvent event) {
          if (event.getAction() == MotionEvent.ACTION_UP) {

        boolean clearTouched =
                (getWidth() - visible_res_padding - visible_res_width < event.getX())
                        && (event.getX() < getWidth() - visible_res_padding)
                        && isFocused();
              boolean visibleTouched =
                (getWidth() - visible_res_padding * 3 - visible_res_width * 2 < event.getX())
                        && (event.getX() < getWidth() - visible_res_padding * 3 - visible_res_width)
                        && show_password_image_isShow && isFocused();

              if (clearTouched) {
                  setError(null);
                  setText("");
                  return true;
              } else if (visibleTouched) {
                  if (dismiss_password_image_isShow) {
                      dismiss_password_image_isShow = false;
                      setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_CLASS_TEXT);
                      setSelection(getText().length());
                      invalidate();
                  } else {
                      dismiss_password_image_isShow = true;
                      setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
                        setSelection(getText().length());
                      invalidate();
                  }
                  return true;
              }
          }
          return super.onTouchEvent(event);
      }

      /**
       * 开始晃动的入口
       */
      public void startShakeAnimation() {
          if (getAnimation() == null) {
              setAnimation(shakeAnimation(4));
          }
          startAnimation(getAnimation());
      }

      /**
       * 晃动动画
       *
       * @param counts 0.5秒钟晃动多少下
       * @return
       */
      private Animation shakeAnimation(int counts) {
          Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0);
          translateAnimation.setInterpolator(new CycleInterpolator(counts));
          translateAnimation.setDuration(500);
          return translateAnimation;
      }


      private Bitmap createBitmap(Context context, int resId, int defResId) {
          if (resId != 0) {
              return BitmapFactory.decodeResource(context.getResources(), resId);
          } else {
              return BitmapFactory.decodeResource(context.getResources(), defResId);
          }
      }

       public int dp2px(Context context, float dpValue){
          return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpValue,context.getResources().getDisplayMetrics());
      }
  }
  • XML

    <com.chaian.phone.imlibrary.view.SeniorEditText
            android:inputType="textPassword"
            app:clearDrawable="@drawable/ic_content_clear"
            app:visibleDrawable="@drawable/ic_content_visible"
            app:invisibleDrawable="@drawable/ic_content_invisible"
            app:editFrameStyle="roundRect"
            app:editFrameColor="#5D2BC5"
            android:layout_marginTop="30dp"
            android:layout_marginStart="20dp"
            android:layout_marginEnd="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
  • 资源图标


    ic_content_clear.png
ic_content_invisible.png
ic_content_visible.png

参考:wang_android