Android开源框架PowerfulViewLibrary——PowerfulEditText的介绍和源码解析

Android开源框架PowerfulViewLibrary——PowerfulEditText的介绍###

  很高兴自己写的前两篇博客都得到了郭霖(人称郭神)的认可,答应帮我发布在他的微信公众号上,这让我更有决心写好博客。我宁愿一个月一更,也不愿滥竽充数,写博客一方面是为了分享自己的技术和经验,另一方面是为了可以互相学习、交流和进步。最近决定开发一个开源框架,叫做PowerfulViewLibrary,也就是功能强大的View库,主要是为了方便开发,封装一些常用的控件,下面是相关介绍和使用。

PowerfulEditText具有的功能###

1.自带清除文本功能

  PowerfulEditText自带清除文本功能,只需在布局文件该View属性中添加funcType,指定为canClear,就可以自带清除文本功能,使用如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
>

    <com.chaychan.viewlib.PowerfulEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:funcType="canClear"
        />

</LinearLayout>

运行后,效果如下:

  上图所示的删除图标是默认的,当然也可以指定右侧删除按钮的图标,只需添加多drawableRight属性,这里建议使用一个selector,分别为普通状态和按压状态设置一张图片,这样当按压图标的时候,会有一种按压的状态,selector的编写如下:

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="按压后的图标" />
    <item android:drawable="普通状态的图标" />
</selector>

源码分析:

public class PowerfulEditText extends EditText {

    /**普通类型*/
    private static final int TYPE_NORMAL = -1;
    /**自带清除功能的类型*/
    private static final int TYPE_CAN_CLEAR = 0;
    /**自带密码查看功能的类型*/
    private static final int TYPE_CAN_WATCH_PWD = 1;

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

     public PowerfulEditText(Context context, AttributeSet attrs) {
        //这里构造方法也很重要,不加这个很多属性不能在XML里面定义  
        this(context, attrs, android.R.attr.editTextStyle);
     }

     public PowerfulEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        ta = context.obtainStyledAttributes(attrs, R.styleable.PowerfulEditText);

        funcType = ta.getInt(R.styleable.PowerfulEditText_funcType, TYPE_NORMAL);

        ...

        init();
     }

}

  PowerfulEditText继承于EditText,这里定义了三种类型,分别是普通类型、自带清除功能类型以及自带查看密码的功能,其中第二个构造方法,也就是只有两个参数的构造方法,里面调用该类的第三个构造方法(带三个参数的构造方法),需要传多一个int类型的参数,这里不能像以前定义其他View的时候那样直接传一个0,而是要传android.R.attr.editTextStyle,否则很多属性不能在XML里面定义。自己的逻辑操作init()方法,在第三个构造方法调用即可。

private void init() {
        //获取EditText的DrawableRight,假如没有设置我们就使用默认的图片,左上右下
        mRightDrawable = getCompoundDrawables()[2];
        
        if (mRightDrawable == null) {
            //如果右侧没有图标
            if (funcType == TYPE_CAN_CLEAR) {
                //有清除功能,设置默认叉号选择器
                mRightDrawable = getResources().getDrawable(R.drawable.delete_selector);
            } 
        }

        //如果是清除功能,则一开始隐藏右侧默认图标,否则不隐藏右侧默认图标
        setRightIconVisible(funcType == 0 ? false : true);
         //设置输入框里面内容发生改变的监听
        addTextChangedListener(new TextWatcher() {
            /**
             * 当输入框里面内容发生变化的时候回调的方法
             */
            @Override
            public void onTextChanged(CharSequence s, int start, int count,
                                      int after) {
        //如果是带有清除功能的类型,当文本内容发生变化的时候,根据内容的长度是否为0进行隐藏或显示
                if (funcType == 0) {
                    setRightIconVisible(s.length() > 0);
                }

                if (textListener != null) {
                    textListener.onTextChanged(s, start, count, after);
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,
                                          int after) {
                if (textListener != null) {
                    textListener.beforeTextChanged(s, start, count, after);
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
                if (textListener != null) {
                    textListener.afterTextChanged(s);
                }
            }

        });
    }


    /**
     * 设置右侧图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
     *
     * @param visible
     */
    protected void setRightIconVisible(boolean visible) {
        Drawable right = visible ? mRightDrawable : null;
        setCompoundDrawables(getCompoundDrawables()[0],
                getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
    }

 
   /**
     * 输入框文本变化的回调,如果需要进行多一些操作判断,则设置此listen替代*TextWatcher
     */
    public interface TextListener {

        void onTextChanged(CharSequence s, int start, int count, int after);

        void beforeTextChanged(CharSequence s, int start, int count, int after);

        void afterTextChanged(Editable s);
    }

  在init()方法中,我们首先获取到输入框右侧的Drawable对象,通过getCompoundDrawables()[2]获取,getCompoundDrawables()方法是用于获取输入框四个方向图标的方法,返回的类型是一个Drawable数组,数组的元素排序分别是左(0)上(1)右(2)下(3)四个Drawable,这里我们需要获取右侧的图标对象,对应的下标为2。先判断右侧图标mRightDrawable是否为空,如果为空,并且当前的功能类型为自带清除功能,则使用默认的清除图标,通过调用getResources().getDrawable()加载对应的selector。

  通过判断功能类型是否属于带有清除功能的类型,设置右侧图标初始化的时候是否显示,接着设置文本内容变化的监听,通过监听文本内容的变化,判断右侧图标是否要显示。由于此处已经设置TextWatcher进行文本的监听,并且在onTextChanged()方法中进行了操作,所以在使用PowfulEditText的时候,如果需要在文本内容监听中做相应操作,则需要使用自己定义的TextListener来进行回调,而不是使用TextWatcher。

  关于输入框右侧图标点击事件的处理,所采用的方法是重写onTouchEvent()方法,对触摸响应进行处理,判断触摸的位置是否落在右侧图标的范围内,如果是,则认为是点击了该图标。

    /**
     * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件
     * 当我们按下的位置 在  EditText的宽度 - 图标到控件右边的间距 - 图标的宽度  和
     * EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (getCompoundDrawables()[2] != null) {

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

                if (isTouched) {

                    if (onRightClickListener == null) {
                        if (funcType == TYPE_CAN_CLEAR) {
                            //如果没有设置右边图标的点击事件,并且带有清除功能,默认清除文本
                            this.setText("");
                        } else if (funcType == TYPE_CAN_WATCH_PWD) {
                            //如果没有设置右边图标的点击事件,并且带有查看密码功能,点击切换密码查看方式
                            ...
                        }
                    } else {
                        //如果有则回调
                        ...
                    }
                }
            }
        }

        return super.onTouchEvent(event);
    }

  当按下的x值在EditText的宽度 - (图标到控件右边的间距 + 图标的宽度)(getTotalPaddingRight()) 和 EditText的宽度 - 图标到控件右边的间距之间,则认为是点击了图标(如果为了精确计算,可以考虑y值方向的判断)然后进行相应的操作,如果没有设置右侧图标的点击事件,并且当前属于带有清除功能类型,则默认清除文本。

2.自带密码输入框切换明文密文格式的功能

  PowerfulEditText自带密码输入框切换明文密文格式的功能,目前大多数App密码输入栏一般支持密码明文、密文的显示,如果需要用到该功能,可以将funcType中指定为canWatchPwd,就可以轻松使用这种功能,使用如下:

<com.chaychan.viewlib.PowerfulEditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:funcType="canWatchPwd"
    android:inputType="textPassword"
    />

运行后,效果如下:

源码解析:

  同样也是在onTouchEvent()方法中做处理,如果当前的功能类型属于查看密码功能类型,则根据eyeOpen这个标识进行判断,判断当前是否是明文,如果是,点击后则变成密文,否则变成明文。

 /**
     * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件
     * 当我们按下的位置 在  EditText的宽度 - 图标到控件右边的间距 - 图标的宽度  和
     * EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (getCompoundDrawables()[2] != null) {

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

                if (isTouched) {

                    if (onRightClickListener == null) {
                        if (funcType == TYPE_CAN_CLEAR) {
                            //如果没有设置右边图标的点击事件,并且带有清除功能,默认清除文本
                            ...
                        } else if (funcType == TYPE_CAN_WATCH_PWD) {
                            //如果没有设置右边图标的点击事件,并且带有查看密码功能,点击切换密码查看方式
                            if (eyeOpen) {
                                //变为密文 
                                this.setTransformationMethod(PasswordTransformationMethod.getInstance());
                                eyeOpen = false;
                            } else {
                                //变为明文
                                this.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
                                eyeOpen = true;
                            }
                            switchWatchPwdIcon();//切换图标
                        }
                    } else {
                        //如果有则回调
                        ...
                    }
                }
            }
        }

        return super.onTouchEvent(event);
    }


    /**
     * 切换查看密码的图标
     */
    private void switchWatchPwdIcon() {
        if (eyeOpen) {
            //开启查看
            setCompoundDrawables(getCompoundDrawables()[0],
                    getCompoundDrawables()[1], mEyeOpenDrawable, getCompoundDrawables()[3]);
        } else {
            //关闭查看
            setCompoundDrawables(getCompoundDrawables()[0],
                    getCompoundDrawables()[1], mRightDrawable, getCompoundDrawables()[3]);
        }
    }

关于输入框明文和密文切换的设置有两种方法。

方法一:

setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);//密文密码
setInputType(EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); //明文密码

方法二:

setTransformationMethod(PasswordTransformationMethod.getInstance());//密文密码
setTransformationMethod(HideReturnsTransformationMethod.getInstance());//明文密码

  上述两种方法都可以实现明文密文的切换,但是方法一切换后EditText的光标会回到最左侧,所以这里选择使用方法二,个人觉得体验比较好一些。

  上图所示的右侧图标是默认的,同样也可以指定开启查看密码的图标和关闭查看密码的图标,只需要在属性eyeOpen中指定开启查看密码引用的图片,在eyeClosed中指定关闭查看密码引用的图片即可,如下,更换开启查看密码的图标,如项目默认的图标ic_launcher

<com.chaychan.viewlib.PowerfulEditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:funcType="canWatchPwd"
    android:inputType="textPassword"
    app:eyeOpen="@mipmap/ic_launcher"
    />

运行后,效果如下:

这样开启查看密码的图标就更换了,如果还需要更换关闭密码查看的图标,可以指定eyeClose,引用对应的图标。

3.设置drawableLeft和drawableRight图片大小的功能

  原生的EditText并不能在属性中指定drawableLeft或drawableRight图片的大小,所以一般开发的过程中,一些程序员会采用简单粗暴的方法,直接引用一张宽高都很小的图片。但是在不同屏幕分辨率下,兼容性就不是很好,比如在一些屏幕分辨率较高的手机上运行,图标会显得模糊。PowerfulEditText可以指定drawableLeft和drawableRight图片的宽高大小,可以指定为多少个dp,这样在开发的时候,可以在各个分辨率图片文件夹中放入不同尺寸的图标,通过设定图片的宽高属性来限制显示的大小,下面演示一下:

<com.chaychan.viewlib.PowerfulEditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:funcType="canWatchPwd"
    android:inputType="textPassword"
    android:drawableLeft="@mipmap/ic_launcher"
    />

  如图,指定了drawableLeft的图片为ic_laucher,图片看起来比较大,这时如果我们想要将其调小,则可以添加leftDrawableWidth、leftDrawableHeight指定左侧图片的宽高。

<com.chaychan.viewlib.PowerfulEditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:funcType="canWatchPwd"
    android:inputType="textPassword"
    android:drawableLeft="@mipmap/ic_launcher"
    app:leftDrawableWidth="30dp"
    app:leftDrawableHeight="30dp"
    />

上面代码,指定了leftDrawableWidth和leftDrawableHeight的大小都为30dp,运行的效果如下:

可以看到左侧的图标变小了,同样也可以设置右侧图片的宽高,对应的属性是rightDrawableWidth、rightDrawableHeight。

  源码解析:

 public PowerfulEditText(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    ta = context.obtainStyledAttributes(attrs, R.styleable.PowerfulEditText);
   
    ...

    init();
}

 if (leftDrawable != null) {
        leftWidth = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_leftDrawableWidth,leftDrawable.getIntrinsicWidth());
        leftHeight = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_leftDrawableHeight,leftDrawable.getIntrinsicHeight());
        leftDrawable.setBounds(0, 0, leftWidth, leftHeight);
    }

    if (mRightDrawable != null) {
        rightWidth = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_rightDrawableWidth,mRightDrawable.getIntrinsicWidth());
        rightHeight = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_rightDrawableWidth,mRightDrawable.getIntrinsicHeight());
        mRightDrawable.setBounds(0, 0, rightWidth, rightHeight);
        if (mEyeOpenDrawable != null) {
            mEyeOpenDrawable.setBounds(0, 0, rightWidth, rightHeight);
        }
       ...
    }

  这里通过TypedArray获取XML中配置的对应leftDrawableWidth、leftDrawableHeight、rightDrawableWidth、rightDrawableHeight的尺寸大小,如果没有设置这些属性,则默认使用图标的宽和高,然后通过Drawable.setBounds()方法,设置右侧图标的宽高,达到改变两侧图标大小的目的。

设置右侧图标点击事件####

PowerfulEditText同样支持右侧图片的点击事件,如果funcType指定为canClear,则默认点击是清除文本。如果需要进行一些额外的操作,则可以设置回调,比如搜索输入框,右侧是一个搜索的按钮,需要为其设置点击事件的回调。

布局文件:

 <com.chaychan.viewlib.PowerfulEditText
    android:id="@+id/pet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:drawableRight="@mipmap/search"
    />

Activity

  PowerfulEditText petUsername = (PowerfulEditText) findViewById(R.id.pet);
    petUsername.setOnRightClickListener(new PowerfulEditText.OnRightClickListener() {
        @Override
        public void onClick(EditText editText) {
            String content = editText.getText().toString().trim();
            if (!TextUtils.isEmpty(content)){
                Toast.makeText(MainActivity.this, "执行搜索逻辑", Toast.LENGTH_SHORT).show();
            }
        }
    });

运行效果如下:

  上面布局文件中,和普通的EditText属性一样,funcType一共有三个属性,分别是normal(默认)、canClear(带清除功能)、canWatchPwd(带查看密码功能)。如果不指定funcType,则默认是normal,普通方式。

  Activity中,为PowerfulEditText设置右侧图片的点击事件,调用setOnRightClickListener设置点击后的回调,这里点击后如果有文本内容,则执行搜索逻辑。

  这里仅对EditText的一些功能进行封装,对于样式的更改,就要靠开发者根据自己的需求进行修改,修改的方式也不难,只要将background指定为自己编写的的xml即可。

  关于右侧图标点击事件的回调,和上面所讲到的是一致的,是在onTouchEvent()方法中,判断触摸范围是否落在右侧图标上,然后判断OnRightClickListener是否为null,如果不为null,则进行回调。OnRightClickListener接口很简单,回调的时候传递当前的EditText对象,如果需要对右侧图标点击事件进行自己的逻辑处理,则通过调用setOnRightClickListener()进行回调。

    /**
     * 右边图标点击的回调
     */
    public interface OnRightClickListener {
        void onClick(EditText editText);
    }

  关于PowerfulEditText的相关属性,可以通过查看attr.xml便一目了然,如下:

 <declare-styleable name="PowerfulEditText">
    <!--功能的类型-->
    <attr name="funcType">
        <enum name="normal" value="-1"/>
        <enum name="canClear" value="0"/>
        <enum name="canWatchPwd" value="1" />
    </attr>
    <!--关闭查看密码的图标-->
    <attr name="eyeClose" format="reference"/>
    <!--开启查看密码的图标-->
    <attr name="eyeOpen" format="reference"/>
    <!--左侧Drawable的宽度-->
    <attr name="leftDrawableWidth" format="dimension"/>
    <!--左侧Drawable的高度-->
    <attr name="leftDrawableHeight" format="dimension"/>
    <!--右侧Drawable的宽度-->
    <attr name="rightDrawableWidth" format="dimension"/>
    <!--右侧Drawable的高度-->
    <attr name="rightDrawableHeight" format="dimension"/>
</declare-styleable>

导入方式####

在项目根目录下的build.gradle中的allprojects{}中,添加jitpack仓库地址,如下:

allprojects {
    repositories {
        jcenter()
        maven { url 'https://jitpack.io' }//添加jitpack仓库地址
    }
}

打开app的module中的build.gradle,在dependencies{}中,添加依赖,如下:

dependencies {
        ......
        compile 'com.github.chaychan:PowerfulViewLibrary:1.0'
}

  这样就可以使用PowerfulViewLibrary下的控件了,目前只对EditText的一些功能进行了封装,往后会把一些常用的View进行封装,方便项目的开发,我会保持对PowerfulViewLibrary的更新和维护的,也希望大家可以向我提出一些建议。

源码github地址:https://github.com/chaychan/PowerfulViewLibrary.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容