引言

前一篇文章 Android进阶——自定义View之系统控件架构及自定义控件概述从宏观整体上总结了关于自定义View的相关知识点,也说过自定义View有三种方式,这篇文章就作为我们开发自定义View优先考虑的解决方案——继承系统现有控件扩展功能的第一篇实战,主要是实现类似IOS风格的EditText,可以设置抖动动画和自带删除小图标的UI效果。

一、自定义加强型EditText功能概述

这个所谓的加强型EditText,其实就是添加了两种效果:当输入时自动添加上删除按钮当输入为空的时候点击按钮触发非空的抖动动画,其他的和普通的EdiText无异。

二、自定义加强型EditText设计思想

首先我们知道android中任何一个控件其本质都是一个类,也同样拥有一些属性,无论是以java动态构造还是xml静态构造,最终显示到Activity上都需要先生成他们的实例,而任何一个类需要生成实例都需要通过它对应的构造方法,所以在构造方法里有很多事可以做。接下来再一步步分析下。

1、绘制出删除按钮

我们知道EditText继承自TextView,而我们知道TextView本身就自带drawableLeft、drawableRight、drawableTop、drawableRight属性可以在TextView的左右上下绘制对应的drawable,如下TextView源码所示,我们可以通过这个方法获取对应的drawable

/**
     * Returns drawables for the left, top, right, and bottom borders.
     *
     * @attr ref android.R.styleable#TextView_drawableLeft
     * @attr ref android.R.styleable#TextView_drawableTop
     * @attr ref android.R.styleable#TextView_drawableRight
     * @attr ref android.R.styleable#TextView_drawableBottom
     */
    @NonNull
    public Drawable[] getCompoundDrawables() {
        final Drawables dr = mDrawables;
        if (dr != null) {
            return dr.mShowing.clone();
        } else {
            return new Drawable[] { null, null, null, null };
        }
    }

2、动态改变删除按钮的显示和隐藏

当文本框里不为空的时候则显示;文本框为空的时候和失去焦点时则自动隐藏,我们都知道android为我们提供了个接口可以监听输入的变化——TextWatcher,我们只需要去实现这个接口并设置监听即可

/**
 * When an object of a type is attached to an Editable, its methods will be called when the text is changed.、
 * 每当文本改变的时候就会依次出发三个回调方法
 */
public interface TextWatcher extends NoCopySpan {
    /**
     * This method is called to notify you that, within ,
     * the  characters beginning at start
     * are about to be replaced by new text.
     */
    public void beforeTextChanged(CharSequence s, int start,
                                  int count, int after);
    /**
     * This method is called to notify you that, within characters beginning at start have just replaced old text 

    /**
     * This method is called to notify you that, somewhere within the text has been changed.
     */
    public void afterTextChanged(Editable s);
}

至于失去焦点的时候自动隐藏,View类封装了很多接口方法,其中onFocusChange可以监听焦点改变事件

/**
     * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置右边图标的显示与隐藏
     */
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        this.isFocuse = hasFocus;
        if (hasFocus) {
            setRightIconVisiable(getText().length() > 0);
        } else {
            setRightIconVisiable(false);
        }
    }

4、实现点击右边按钮清空文本

控件的触屏事件的起点均是由Touch开始的,所以在一定程度上来说可以通过重写onTouch方法来模拟点击事件

/**
     * 模拟点击事件当我们按下的位置 在  EditText的宽度 - 图标到控件右边的间距 - 图标的宽度  和EditText的宽度 - 图标到控件右边的间距
     * 之间就相当于点击了右边的Icon
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (getCompoundDrawables()[2] != null) {

                boolean isRightClick = event.getX() > (getWidth() - getTotalPaddingRight())
                        && (event.getX() < ((getWidth() - getPaddingRight())));

                if (isRightClick) {
                    this.setText("");
                }
            }
        }
        return super.onTouchEvent(event);
    }

5、抖动动画效果

只需要给控件添加上TranslateAnimation来实现位移动画即可。

private Animation genDefaultAnimation() {
        animtion = new TranslateAnimation(0, 10, 0, 0);
        animtion.setInterpolator(new CycleInterpolator(counts));
        animtion.setDuration(during);
        return animtion;
    }

三、实现加强型EditText

android 释放控件 安卓移除控件_可删除EditTex

package com.crazymo.training.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.animation.Animation;
import android.view.animation.CycleInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.EditText;

import com.crazymo.training.R;

/**
 * Created by cmo on 2016/10/6.
 * 加强型EditText:作为普通输入框可以设置非空提示的晃动动画效果,自带删除的按钮
 */
public class EnhancedEditText extends EditText implements OnFocusChangeListener, TextWatcher {

    private Drawable mRightIco;//显示于右边的Icon
    private Animation animtion;//用于提示非空的晃动动画
    private boolean isFocuse;//当前是否获得焦点
    private int counts;
    private int during;
    private float rightSize;
    private OnRightDrawableChanged rightChangedListener;

    public EnhancedEditText(Context context) {
        this(context, null);
    }

    public EnhancedEditText(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.editTextStyle);//直接引用系统的EditText的Style
    }

    public EnhancedEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //引用自定义属性
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.enhancedEditText);
        during=typedArray.getInt(R.styleable.enhancedEditText_during,2000);
        counts=typedArray.getInt(R.styleable.enhancedEditText_counts,6);
        rightSize=typedArray.getFloat(R.styleable.enhancedEditText_rightSize,0.7f);
        init();
    }

    /**
     * 初始化右边的Icon和设置监听器
     */
    private void init() {
        //如果没有设置drawableRight属性则会获取默认的值,设置了drawableRight则会显示设置的值
        mRightIco = getCompoundDrawables()[2];
        if (mRightIco == null) {
            mRightIco = getResources().getDrawable(R.drawable.clear_selector);
        }
        //重新设置左边的Icon为左边Icon的大小
        if(getCompoundDrawables()[0]!=null) {
            mRightIco.setBounds(0, 0, (int) ((getCompoundDrawables()[0].getIntrinsicWidth()) * rightSize), (int) ((getCompoundDrawables()[0].getIntrinsicHeight()) * rightSize));
        }else {
            //若没有设置LeftDrawable的图像则默认设置右边的图像大小为96*96
            mRightIco.setBounds(0, 0, 96, 96);
        }

        setRightIconVisiable(false);//默认设置隐藏图标
        setOnFocusChangeListener(this);//设置焦点改变的监听
        addTextChangedListener(this);//设置输入框里面内容发生改变的监听
    }


    /**
     * 模拟点击事件当我们按下的位置 在  EditText的宽度 - 图标到控件右边的间距 - 图标的宽度  和
     * EditText的宽度 - 图标到控件右边的间距之间就相当于点击了右边的Icon
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (getCompoundDrawables()[2] != null) {

                boolean isRightClick = event.getX() > (getWidth() - getTotalPaddingRight())
                        && (event.getX() < ((getWidth() - getPaddingRight())));

                if (isRightClick) {
                    this.setText("");
                }
            }
        }
        return super.onTouchEvent(event);
    }

    /**
     * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置右边图标的显示与隐藏
     */
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        this.isFocuse = hasFocus;
        if (hasFocus) {
            setRightIconVisiable(getText().length() > 0);
        } else {
            setRightIconVisiable(false);
        }
    }

    /**
     *设置Drawable显示于文字之上 setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)
     *设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
     * @param visible
     */
    protected void setRightIconVisiable(boolean visible) {
        Drawable right = visible ? mRightIco : null;
        if(rightChangedListener!=null) {
            if (visible) {
                rightChangedListener.onRightDisplay();
            } else {
                rightChangedListener.onRightInvisible();
            }
        }
        setCompoundDrawables(getCompoundDrawables()[0],
                getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
    }

    /**
     * 当输入框里面内容发生变化的时候回调的方法
     */
    @Override
    public void onTextChanged(CharSequence s, int start, int count,
                              int after) {
        if (isFocuse) {
            setRightIconVisiable(s.length() > 0);//如果没有输入字符串则会隐藏
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,
                                  int after) {
    }

    @Override
    public void afterTextChanged(Editable s) {
    }
    /**
     * 启动晃动动画
     * @param animation 可定义自定义的动画效果,若不定义则使用默认的动画
     */
    public void setShakeAnimation(Animation animation) {
        if(animation==null) {
            this.startAnimation(genDefaultAnimation());
        }else{
            this.startAnimation(animation);
        }
    }

    /**
     * 设置默认的晃动动画
     * @return animation
     */
    private Animation genDefaultAnimation() {
        animtion = new TranslateAnimation(0, 10, 0, 0);
        animtion.setInterpolator(new CycleInterpolator(counts));
        animtion.setDuration(during);
        return animtion;
    }
    public void setRightChangedListener(OnRightDrawableChanged listener){
        rightChangedListener=listener;
    }
    public interface OnRightDrawableChanged{

        void onRightDisplay();
        void onRightInvisible();
    }
}

四、应用自定义加强型EditText

package com.crazymo.training;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.method.HideReturnsTransformationMethod;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.crazymo.training.widget.EnhancedEditText;

public class MainActivity extends Activity implements EnhancedEditText.OnRightDrawableChanged {
    private final String TAG="EnhanceEditText";
    private Button btn;
    private EnhancedEditText clearEditText;
    private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }
    private void init(){
        getViews();
        setBtnClickListener();
        //clearEditText.setRightIco(getDrawable(R.mipmap.ic_user));
        clearEditText.setRightChangedListener(this);
    }
    private void getViews(){
        btn= (Button) findViewById(R.id.test_btn);
        clearEditText= (EnhancedEditText) findViewById(R.id.cleat_edt_user);
        imageView= (ImageView) findViewById(R.id.show_pwd);
    }
    private void setBtnClickListener(){
        btn.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                if(TextUtils.isEmpty(clearEditText.getText())){
                    //设置晃动
                    clearEditText.setShakeAnimation(null);
                    //设置提示
                    Toast.makeText(MainActivity.this,"用户名不能为空",Toast.LENGTH_LONG).show();
                    return;
                }else{
                    //设置密码可见
                    clearEditText.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
                    //clearEditText.setRightIco(getDrawable(R.mipmap.ic_user));
                    //clearEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
                }
            }
        });
    }

    @Override
    public void onRightDisplay() {
        imageView.setVisibility(View.VISIBLE);
    }

    @Override
    public void onRightInvisible() {
        imageView.setVisibility(View.GONE);
    }
}

五、输入法软键盘悬浮于文本框的下方

再也不用去特意嵌套ScrollView来实现了,只需要设置EditText的属性即可

android:imeOptions="flagNoExtractUi"

六、实现输入小写英文字母自动转为大写

让EditText具备自动大小写转换的功能的需求,其实很简单,提供在android.text.method包中提供了ReplacementTransformationMethod类,我们只需要继承这个ReplacementTransformationMethod类并实现对应的方法,并且EditText的setTransformationMethod方法即可

///首先定义一个类继承ReplacementTransformationMethod类并重写两个方法。。
public class AutoCaseTransformation extends ReplacementTransformationMethod {
    /**
     * 获取要改变的字符。
     * @return 将你希望被改变的字符数组返回。
     */
    @Override
    protected char[] getOriginal() {
        return new char[]{'a', 'b', 'c', 'd', 'e',
                'f', 'g', 'h', 'i', 'j', 'k', 'l',
                'm', 'n', 'o', 'p', 'q', 'r', 's',
                't', 'u', 'v', 'w', 'x', 'y', 'z'};
    }

    /**
     * 获取要替换的字符。
     * @return 将你希望用来替换的字符数组返回。
     */
    @Override
    protected char[] getReplacement() {
        return new char[]{ 'A', 'B', 'C', 'D', 'E',
                'F', 'G', 'H', 'I', 'J','K','L','M',
                'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' };
    }
}

应用

editText.setTransformationMethod(new AutoCaseTransformationMethod());