自定义view,仿微信、支付宝密码输入控件的源码实现

研究支付宝密码输入控件及源码实现

目标效果图

这里写图片描述

实现思路

  1. 要想实现输入,就少不了EditText
  2. 看整体布局应该是一个横向的LinearLayout
  3. 每个格子看进来应该是多个子View
  4. 那么我们是不是有思路了?没错!一个LinearLayout包含了多个EditText,首先这个思路是对的;其次,有必要每个子View都是EditText吗?我们在监听文本变化时,只需要对一个EditText添加TextWatcher,因此,只需要一个EditText,其它的可以是TextView就可以了(PS:EditText是继承自TextView的)

一、初步实现外观框架

自定义View,通过继承LinearLayout的方式

  1. 先看布局inputview.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="horizontal">
    <EditText
        android:cursorVisible="false"
        android:gravity="center"
        android:id="@+id/indexView"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/bg_rec"
        android:text="a"/>
</LinearLayout>

需要注意的是要把光标设置成不可见,因为我们的目标效果中不应该有光标
2. 填充布局,关键代码是在init方法中填充几个TextView,并设置背景和LayoutParams

public class GridPasswordView extends LinearLayout {
    private EditText mIndexView;
    private LinearLayout mContainer;

    public GridPasswordView(Context context) {
        this(context, null);
    }
    public GridPasswordView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public GridPasswordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOrientation(HORIZONTAL);
        setBackgroundColor(Color.GRAY);
        mContainer = (LinearLayout) View.inflate(getContext(), R.layout.inputview, null);
        mIndexView = (EditText) mContainer.findViewById(R.id.indexView);
        for (int i = 0; i < 5; i++) {
            TextView child = new TextView(getContext());
            child.setText(i + "");
            child.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams
                    .MATCH_PARENT, 1));
            child.setGravity(Gravity.CENTER);
            child.setBackground(getResources().getDrawable(R.drawable.bg_rec));
            mContainer.addView(child);
        }
        mContainer.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        addView(mContainer);
    }
}

可以看到,我们又另外在mContainer中添加了5个TextView,并设置了内容
3. 矩形框背景使用的是shape

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <stroke android:color="@color/colorAccent"
            android:width="1dp"/>
</shape>
  1. 在demo中使用
<feifu.com.testview.gridpswview.GridPasswordView
        android:background="#f00"
        android:id="@+id/grid"
        android:layout_width="match_parent"
        android:layout_height="30dp"/>

效果图如下:
这里写图片描述
我们把EditText放在第一位也是有原因的,后面会讲。
到这里就实现了一个空的架子。

二、实现输入和删除逻辑

输入和删除的逻辑就要接收软键盘的输入和输出了,因此我们需要监听EditText的文本变化,如果有输入,就依次输入,如果按了删除,就依次删除。
1. 对EditText添加监听

    /**
     * 监听indexview的内容变化
     */
    private TextWatcher mIndextViewWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            oldText = s.toString();
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            Log.e(TAG, "count=" + count + ",start=" + start + ",before=" + before + ",s=" + s);
            //输入内容
            if (count == 1) {
                //文本到达最大长度,不再接受输入,要保证edittext不变
                if (currentIndex >= maxLength) {
                    mIndexView.removeTextChangedListener(this);
                    mIndexView.setText(s.charAt(0) + "");
                    mIndexView.setSelection(mIndexView.getText().length());//设置光标到最后,保证输入文本顺序
                    mIndexView.addTextChangedListener(this);//设置文本后再次添加监听
                    return;
                }
                if (currentIndex == 0) {
                    currentIndex++;
                } else {
                    TextView child = (TextView) mContainer.getChildAt(currentIndex++);
                    child.setText(s.charAt(s.length() - 1) + "");
                    //需要及时移除监听器,否则会形成死循环
                    mIndexView.removeTextChangedListener(this);
                    mIndexView.setText(s.charAt(0) + "");
                    mIndexView.setSelection(mIndexView.getText().length());//设置光标到最后,保证输入文本顺序
                    mIndexView.addTextChangedListener(this);//设置文本后再次添加监听
                }
            } else if (count == 0) {
                Log.e(TAG, "currentIndex=" + currentIndex);
                if (currentIndex <= 0) {
                    return;
                }
                //删除内容
                if (currentIndex == 1) {
                    currentIndex --;
                }else {
                    TextView child = (TextView) mContainer.getChildAt(currentIndex - 1);
                    currentIndex --;
                    child.setText("");
                    mIndexView.removeTextChangedListener(this);
                    mIndexView.setText(oldText);
                    mIndexView.addTextChangedListener(this);//设置文本后再次添加监听
                    mIndexView.setSelection(mIndexView.getText().length());//设置光标位置到最后,否则无法删除
                }
            }
        }
        @Override
        public void afterTextChanged(Editable s) {

        }
    };

主要的逻辑就在onTextChanged方法中,首先来说一个几个参数的意思:
* s 表示编辑后的文本内容
* start表示当前的光标位置
* before表示改变前的内容数量
* count表示改变的数量,1表示输入一个字符,0表示删除一个字符
2. 在onTextChanged方法中
* 首先判断是输入还是删除,根据count来判断,1表示输入一个字符,0表示删除一个字符
* 使用currentIndex表示当前文本个数
* 因为每次操作的其实都是EditText的内容,所以要及时的把EditText的内容给设置回原来的内容,要注意的一点是,在TextWatcher中设置EditText内容时,要先移除监听器再改变内容,改变完后再把监听器设置回来,否则会形成死循环

到这里,基本的输入和输出就搞定了。

添加自定义属性

接下来就是要添加一些自定义的属性了,这样更方便我们来定义自己需要的样式。

自定义属性的方式

  1. 在res/values/文件夹下新建文件attrs.xml或者attrs_xxx.xml
  2. 在attrs.xml中定义自己需要的属性
<declare-styleable name="GridPswView">
        <attr name="passwordTransformation" format="string"/>
        <attr name="length" format="integer"/>
        <attr name="borderColor" format="color"/>
        <attr name="borderWidth" format="dimension"/>
        <attr name="backgroundColor" format="color"/>
        <attr name="textColor" format="color"/>
        <attr name="textSize" format="dimension"/>
   </declare-styleable>

如果需要定义属性的值域可以如下

<attr name="percent_circle_gravity">
     <flag name="left" value="0" />
     <flag name="top" value="1" />
     <flag name="center" value="2" />
     <flag name="right" value="3" />
     <flag name="bottom" value="4" />
</attr>
  1. 在我们的自定义view中获取属性的值,并且设置到我们的view中
    构造方法中会有两个参数,context和attrs,我们就是要用这两个参数来获取属性值
private void initAttr(Context context, AttributeSet attrs) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GridPswView);
     if (typedArray != null) {
         mBorderColor = typedArray.getColor(R.styleable.GridPswView_borderColor, Color.BLUE);
         mTextColor = typedArray.getColor(R.styleable.GridPswView_textColor, Color.GRAY);
         mBackgroundColor = typedArray.getColor(R.styleable.GridPswView_backgroundColor, Color
                 .LTGRAY);
         mPswTransformation = typedArray.getString(R.styleable
                 .GridPswView_passwordTransformation);
         mMaxLength = typedArray.getInt(R.styleable.GridPswView_length, 6);
         mBroderWidth = typedArray.getDimension(R.styleable.GridPswView_borderWidth, 3);
         mTextSize = typedArray.getDimension(R.styleable.GridPswView_textSize, 16);
     }
 }

获取到值以后直接在view中设置属性就可以了,这里不再详述。
4. 在xml中设置属性的值
唯一需要注意的是要引入命名空间,在AndroidStudio中会自行引入,不必多言。
设置属性目前有以下几个选项,后序还会添加其它的:
* 背景颜色
* 字体颜色
* 边框颜色
* 字体大小
* 隐藏字符
* 长度

<feifu.com.testview.gridpswview.GridPswView
     android:background="#f00"
      android:layout_gravity="center_vertical"
      android:id="@+id/grid"
      ddDog:textColor="#0f0"
      ddDog:backgroundColor="#f00"
      ddDog:length="9"
      ddDog:textSize="30sp"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"/>

好了现在运行一下吧,看看效果
这里写图片描述

设置的颜色不是很好,看着较Low,读者可以自行设置自行设置。

遗留问题

待续。。。

github源码:
https://github.com/dd-Dog/grid-psw-view

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值