• 教你写一个Android可快速复用的小键盘输入控件


    引子


    在Android项目开发中特别是一些稍大型的项目,面对需求文档的时候你经常会发现很多地方用到了同样的组件,但是又略有不同。比如这个:
    右边是一个小键盘输入板,左边当焦点不同的时候分别用右边的小键盘输入板来输入内容,同时发现很多别的地方也用到了这个小键盘输入板。
    按照以往的做法,我们可能这样子来做出这么一个控件:
    1. 写一个小键盘的布局单独存为一个layout文件
    2. 在用到这个小键盘的activity/fragment中的layout布局中include这个小键盘布局文件
    3. 在activity/fragment中监听每小键盘的按钮来改变输入框的内容
    这样子,别的地方同样用到这个小键盘就可以:
    • 同样include这个布局文件
    • 同样拷贝按钮逻辑处理代码
    那么你做到复用了吗?答案是否定的。因为这里有一些问题,也是一些变数:
    • 小键盘的布局可能不是这个样子的,其他地方布局万一有变化呢
    • 小键盘的每个按钮按下后输入的效果或者说内容是不一样的
    • 小键盘针对哪个输入框(比如EditText)起作用也是不定的
    这样子看来面临的问题还很多,所以需要重新考虑怎么制作这个控件了。
     
     
    如何抽象

    我们第一步需要把这个组件的共性和区别给抽象出来,通过仔细考虑,发现下列地方是可以公用:
    • 小键盘的各种布局中的每个按钮(0~9、小数点、删除)的view id是可以相同的
    • 小键盘中每个按钮按下后的效果是可以有一个共同操作的,比如按下1就出来一个1,按下删除就删除最后一个数字
    • 小键盘起作用的时候,影响的输入框在作用的时候引用是不变的(除非用户点击了另一个输入框期望对这个输入框操作)
    所以我们做出这样的一个小键盘输入组件:
    1. 提供默认的布局,同时提供接口让使用者自定义布局
    2. 提供默认的按钮操作,同时提供接口让使用者自定义按钮操作(比如某个奇葩用户希望按下1键后打出来a)
    3. 提供接口,可以设置正在作用的输入框
    下面将以上三点一一说明。
    最终做出来的空间效果为:
    (PS : 只是右边的小键盘控件,左边的可以忽略)
     
     
    布局

    首先我们这个自定义view继承自LinearLayout,我们把它命名为NumericPad类,首先是写一个默认的布局,这里很简单,花点时间即可:
    // filename : custom_numeric_pad.xml
    
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:orientation="horizontal"
            android:weightSum="3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:id="@+id/pad_1"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="1"/>
            <Button
                android:id="@+id/pad_2"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="2"/>
            <Button
                android:id="@+id/pad_3"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="3"/>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:weightSum="3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:id="@+id/pad_4"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="4"/>
            <Button
                android:id="@+id/pad_5"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="5"/>
            <Button
                android:id="@+id/pad_6"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="6"/>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:weightSum="3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <Button
                android:id="@+id/pad_7"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="7"/>
            <Button
                android:id="@+id/pad_8"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="8"/>
            <Button
                android:id="@+id/pad_9"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="9"/>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:weightSum="3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            android:layout_height="wrap_content">
            <Button
                android:id="@+id/pad_dot"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="."/>
            <Button
                android:id="@+id/pad_0"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:textSize="26sp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:text="0"/>
            <ImageButton
                android:id="@+id/pad_del"
                android:layout_width="0dp"
                android:layout_height="65dp"
                android:layout_weight="1"
                android:layout_margin="10dp"
                android:scaleType="center"
                android:background="@drawable/bg_cal_btn_selector"
                android:textColor="@color/btn_text_color"
                android:src="@drawable/cal_del_selector"/>
        </LinearLayout>
    </LinearLayout>
    上述layout的效果即为最终效果图右侧的那个12个按钮的输入板,我们在NumericPad的构造方法中inflater出来:
    public NumericPad(Context context, AttributeSet attrs) {
            super(context, attrs);
            mContext = context;
            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mRoot = (LinearLayout) mInflater.inflate(R.layout.custom_numeric_pad, this);
        }
    同时提供一个接口允许用户传入一个layout资源id,但是这个资源文件里面的每个viewid都是和默认的layout每个viewid是一致的,比如按钮1的viewid必须是R.id.pad_1,按钮删除的viewid必须是R.id.pad_del,依次类推。这样子做是为了第二步,封装和提供自定制按钮逻辑而做准备的。提供的设置layout的接口很简单:
    public void setLayout(@LayoutRes int layoutId) {
            if (layoutId != 0) {
                // use customized layout from parameter
                mRoot = (LinearLayout) mInflater.inflate(layoutId, this);
            }
            
        }
     
     
    按钮逻辑

    因为每个小键盘按钮都是个button,那么这里首先我们要处理的是点击事件的监听,这里我们不需要创建出来button,只需要依次迭代每个ViewID对他findviewbyid出来,同时设置setonclicklistener或者setonlongclicklistener即可:

     
    首先需要将NumericPad实现两个接口:
    public class NumericPad extends LinearLayout implements View.OnClickListener, View.OnLongClickListener{
    ...
    
    }
    在写一个ArrayList,把所有的viewId放进去,逐个的设置监听事件:
    //initView方法需要在NumericPad的构造函数和setLayout方法中调用
    
     private void initView() {
            mViewsId = new ArrayList<>();
            mViewsId.add(R.id.pad_0);
            mViewsId.add(R.id.pad_1);
            mViewsId.add(R.id.pad_2);
            mViewsId.add(R.id.pad_3);
            mViewsId.add(R.id.pad_4);
            mViewsId.add(R.id.pad_5);
            mViewsId.add(R.id.pad_6);
            mViewsId.add(R.id.pad_7);
            mViewsId.add(R.id.pad_8);
            mViewsId.add(R.id.pad_9);
            mViewsId.add(R.id.pad_dot);
            mViewsId.add(R.id.pad_del);
            for (int viewId : mViewsId) {
                findViewById(viewId).setOnClickListener(this);
                findViewById(viewId).setOnLongClickListener(this);
            }
        }
    随后就是在onClick和onLongClick里面实现默认的逻辑,比如输入一个1或者删除一个字符,或者长按删除清空输入框:
    @Override
        public boolean onLongClick(View v) {
            switch (v.getId()) {
                case R.id.pad_del:
                    if (mInputView != null) {
                        mInputView.setText("");
                    }
                    break;
            }
            return true;
        }
        @Override
        public void onClick(View v) {
            int result_param = PAD_ERR;
            switch (v.getId()) {
                case R.id.pad_0:
                    result_param = PAD_ZERO;
                    break;
                case R.id.pad_1:
                    result_param = PAD_ONE;
                    break;
                case R.id.pad_2:
                    result_param = PAD_TWO;
                    break;
                case R.id.pad_3:
                    result_param = PAD_THREE;
                    break;
                case R.id.pad_4:
                    result_param = PAD_FOUR;
                    break;
                case R.id.pad_5:
                    result_param = PAD_FIVE;
                    break;
                case R.id.pad_6:
                    result_param = PAD_SIX;
                    break;
                case R.id.pad_7:
                    result_param = PAD_SEVEN;
                    break;
                case R.id.pad_8:
                    result_param = PAD_EIGHT;
                    break;
                case R.id.pad_9:
                    result_param = PAD_NINE;
                    break;
                case R.id.pad_dot:
                    result_param = PAD_DOT;
                    break;
                case R.id.pad_del:
                    result_param = PAD_DEL;
                    break;
            }
            L.d("click button type : " + result_param);
            
            // do the default action
            doDefaultNumericAction(result_param);
        }
    doDefaultNumericAction这个方法的具体的逻辑就不说了,后面一并放出。上面代码有两个需要解释的地方:
    第一、mInputView这个变量即为所作用的inputView,这里需要提供一个接口让用户来设置所要作用的输入框,后面会说到。doDefaultNumericAction这个方法其实也就是在对mInputView的内容进行改变。
    第二、PAD_ZERO这些就是一个常量而已,标识目前按下了哪个按钮
     
    到此一个完整的具备布局和按钮逻辑的小键盘输入板已经完成了,下一步将提供用户可定制的输入板逻辑接口。
     
     
    用户可定制逻辑

    既然是用户自己想定制按钮输入逻辑,那么这个逻辑代码应该是放在含有NumericPad的activity/fragment中的,所以我们在NumericPad里面写一个interface:
    public interface NumericPadButtonClickListener {
            /**
             * 当按下数字键盘某个按钮后的回调
             * @param type 检查返回值是否为PAD_ERR(错误),否者就是正常按下了某个值,根据值来判断做对应的界面响应
             */
            void onNumericPadButtonClicked(int type);
    }
    /**
     * 设置键盘按钮响应回调方法
     * @param mListener
     */
    public void setNumericPadButtonClickListener(NumericPadButtonClickListener mListener) {
        this.mListener = mListener;
    }
    让activity/fragment来Implements这个接口,同时实现下面这个方法:
    @Override
        public void onNumericPadButtonClicked(int type) {
            
        }
    这个时候每当按钮按下,activity/fragment的onNumericPadButtonClicked会被毁掉,activity/fragment对type进行判断来执行相应的操作,比如如果type是PAD_ONE,在输入框中输入一个'a'。
     
    NumericPad里面需要将按钮按下的事件和NumericPadButtonClickListener接口绑定起来,即改造onClick方法:
    @Override
        public void onClick(View v) {
            int result_param = PAD_ERR;
            switch (v.getId()) {
                case R.id.pad_0:
                    result_param = PAD_ZERO;
                    break;
                case R.id.pad_1:
                    result_param = PAD_ONE;
                    break;
                case R.id.pad_2:
                    result_param = PAD_TWO;
                    break;
                case R.id.pad_3:
                    result_param = PAD_THREE;
                    break;
                case R.id.pad_4:
                    result_param = PAD_FOUR;
                    break;
                case R.id.pad_5:
                    result_param = PAD_FIVE;
                    break;
                case R.id.pad_6:
                    result_param = PAD_SIX;
                    break;
                case R.id.pad_7:
                    result_param = PAD_SEVEN;
                    break;
                case R.id.pad_8:
                    result_param = PAD_EIGHT;
                    break;
                case R.id.pad_9:
                    result_param = PAD_NINE;
                    break;
                case R.id.pad_dot:
                    result_param = PAD_DOT;
                    break;
                case R.id.pad_del:
                    result_param = PAD_DEL;
                    break;
            }
            L.d("click button type : " + result_param);
            if (mListener != null) {
                // 新添加的代码
                // do the custom action
                mListener.onNumericPadButtonClicked(result_param);
            } else {
                L.d("button click listener is null, will call the default action");
                // do the default action
                doDefaultNumericAction(result_param);
            }
        }

    设置正在操作的输入框


    这里很简单,就是一个设置的接口:

    public void setInputView(@NonNull TextView view) {
            this.mInputView = view;
        }

    至此,一个完整的可以快速复用定制的小键盘输入组件就搞定了

  • 相关阅读:
    MySQL集群常见高可用方案(转)
    upsource使用
    Hystrix 使用
    astah UML 先画图、后编程
    java ThreadLocal 使用
    Java基础 Annotation使用
    LVS+Keepalived+Nginx+Tomcat高可用负载均衡集群配置
    招聘求职学习
    Rotate List 面试题
    vue前台(四点二)
  • 原文地址:https://www.cnblogs.com/soaringEveryday/p/4739925.html
Copyright © 2020-2023  润新知