需求自定义车牌输入框,可以动态添加输入框个数,7位正常车牌,8位新能源车牌。点击可修改输入过的车牌号码,附带点中效果。此输入框是基于 一个方框样式的EditText ,按照实际需求进行扩展的。

现已更新至 GitHub 可直接依赖。 传送门- > LicensePlateUtil ,喜欢的给点个星,谢谢啦。。。

动态效果如下:

实现思路:

   1.用一个透明的 EditText 与 8 个 TextView 重叠,并给 TextView 设置默认背景
   2.监听 EditText 文本变化,获取输入内容,给 TextView 赋值并改变 TextView 背景
   3.修改已输入文本,设置修改回调监听,根据对应的位置修改或删除对应的文本,同时设置选中背景
   4.EditText输入监听,设置对应位置的显示内容,监听输入完成,监听删除键添加删除回调

贴代码,看逻辑处理

注:下面为最新处理逻辑,若有好的建议请在 gitHub 参与。

public class LicensePlateView extends RelativeLayout implements View.OnClickListener {
    private EditText editText;
    private TextView[] TextViews;
    private Activity mActivity;
    private View mNumView;
    private View mProvinceView;
    private int count = 0;
    private int updateViewPosition;
    private static int ITEM_VIEW_COUNT = 7;
    private LayoutInflater mInflater;
    private String inputContent;
    private boolean isUpdateView = false;//是否更新view内容
    private StringBuffer stringBuffer = new StringBuffer();
    private OnFrameTouchListener mTouchListener = new OnFrameTouchListener();
    private static final int[] VIEW_IDS = new int[]{
            R.id.item_code_iv1, R.id.item_code_iv2, R.id.item_code_iv3,
            R.id.item_code_iv4, R.id.item_code_iv5, R.id.item_code_iv6,
            R.id.item_code_iv7, R.id.item_code_iv8
    private static final int[] VIEW_PROVINCE_IDS = new int[]{
            R.id.select_province_11_tv, R.id.select_province_12_tv, R.id.select_province_13_tv,
            R.id.select_province_14_tv, R.id.select_province_15_tv, R.id.select_province_16_tv,
            R.id.select_province_17_tv, R.id.select_province_18_tv, R.id.select_province_19_tv,
            R.id.select_province_110_tv,
            R.id.select_province_21_tv, R.id.select_province_22_tv, R.id.select_province_23_tv,
            R.id.select_province_24_tv, R.id.select_province_25_tv, R.id.select_province_26_tv,
            R.id.select_province_27_tv, R.id.select_province_28_tv, R.id.select_province_29_tv,
            R.id.select_province_210_tv,
            R.id.select_province_31_tv, R.id.select_province_32_tv, R.id.select_province_33_tv,
            R.id.select_province_34_tv, R.id.select_province_35_tv, R.id.select_province_35_tv,
            R.id.select_province_36_tv, R.id.select_province_37_tv, R.id.select_province_38_tv,
            R.id.select_province_41_tv, R.id.select_province_42_tv, R.id.select_province_43_tv,
            R.id.select_province_delete_tv
    private static final int[] VIEW_NUM_IDS = new int[]{
            R.id.select_num_100_tv, R.id.select_num_101_tv, R.id.select_num_102_tv,
            R.id.select_num_103_tv, R.id.select_num_104_tv, R.id.select_num_105_tv,
            R.id.select_num_106_tv, R.id.select_num_107_tv, R.id.select_num_108_tv,
            R.id.select_num_109_tv,
            R.id.select_num_200_tv, R.id.select_num_201_tv, R.id.select_num_202_tv,
            R.id.select_num_203_tv, R.id.select_num_204_tv, R.id.select_num_205_tv,
            R.id.select_num_206_tv, R.id.select_num_207_tv, R.id.select_num_208_tv,
            R.id.select_num_209_tv,
            R.id.select_num_300_tv, R.id.select_num_301_tv, R.id.select_num_302_tv,
            R.id.select_num_303_tv, R.id.select_num_304_tv, R.id.select_num_305_tv,
            R.id.select_num_306_tv, R.id.select_num_307_tv, R.id.select_num_308_tv,
            R.id.select_num_309_tv,
            R.id.select_num_400_tv, R.id.select_num_401_tv, R.id.select_num_402_tv,
            R.id.select_num_403_tv, R.id.select_num_404_tv, R.id.select_num_405_tv,
            R.id.select_num_406_tv,
            R.id.select_num_delete_tv
    public LicensePlateView(Context context) {
        this(context, null);
    public LicensePlateView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    public LicensePlateView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mActivity = (Activity) context;
        TextViews = new TextView[8];
        View.inflate(context, R.layout.layout_license_plate_frame, this);
        int textsLength = VIEW_IDS.length;
        for (int i = 0; i < textsLength; i++) {
            //textview放进数组中,方便修改操作
            TextViews[i] = (TextView) findViewById(VIEW_IDS[i]);
            TextViews[i].setOnTouchListener(mTouchListener);
        editText = (EditText) findViewById(R.id.item_edittext);
        TextViews[0].setBackgroundResource(R.drawable.license_plate_first_view_blue);//第一个输入框默认设置点中效果
        editText.setCursorVisible(false);//将光标隐藏
        setListener();
        hideSoftInputMethod();
    private void setListener() {
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            @Override
            public void afterTextChanged(Editable editable) {
                //如果字符不为""时才进行操作
                if (!editable.toString().equals("")) {
                    if (stringBuffer.length() > ITEM_VIEW_COUNT - 1) {
                        //当文本长度大于 ITEM_VIEW_COUNT - 1 位时 EditText 置空
                        editText.setText("");
                        return;
                    } else {
                        //将文字添加到 StringBuffer                         stringBuffer.append(editable);
                        //添加后将 EditText 置空  造成没有文字输入的错局
                        editText.setText("");
                        //记录 stringBuffer 的长度
                        count = stringBuffer.length();
                        inputContent = stringBuffer.toString();
                        if (count == 1) {
                            mProvinceView.setVisibility(GONE);
                            mNumView.setVisibility(VISIBLE);
                        if (stringBuffer.length() == ITEM_VIEW_COUNT) {
                            //文字长度为 sbLength  则调用完成输入的监听
                            if (inputListener != null) {
                                inputListener.inputComplete(inputContent);
                                mNumView.setVisibility(GONE);
                    for (int i = 0; i < stringBuffer.length(); i++) {
                        TextViews[i].setText(String.valueOf(inputContent.charAt(i)));
                        TextViews[i].setTextColor(onSetTextColor(R.color.colorBlack));
                    setTextViewsBackground(count);
        editText.setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_DEL
                        && event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (onKeyDelete()) {
                        return true;
                    return true;
                return false;
     * 设置框内字体颜色
    public int onSetTextColor(int resId) {
        return resId;
    public void setKeyboardContainerLayout(RelativeLayout layout) {
        mInflater = LayoutInflater.from(mActivity);
        mProvinceView = mInflater.inflate(R.layout.layout_keyboard_province, null);
        RelativeLayout.LayoutParams rlParams = new RelativeLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        rlParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        mProvinceView.setLayoutParams(rlParams);
        mNumView = mInflater.inflate(R.layout.layout_keyboard_num, null);
        mNumView.setLayoutParams(rlParams);
        int provinceLength = VIEW_PROVINCE_IDS.length;
        View view;
        for (int i = 0; i < provinceLength; i++) {
            view = mProvinceView.findViewById(VIEW_PROVINCE_IDS[i]);
            view.setOnClickListener(this);
        int numLength = VIEW_NUM_IDS.length;
        for (int i = 0; i < numLength; i++) {
            view = mNumView.findViewById(VIEW_NUM_IDS[i]);
            view.setOnClickListener(this);
        layout.addView(mProvinceView);
        layout.addView(mNumView);
        mNumView.setVisibility(GONE);
     * 显示 8 个输入框
    public boolean showLastView() {
        TextViews[7].setVisibility(VISIBLE);
        ITEM_VIEW_COUNT = 8;
        if (!TextUtils.isEmpty(TextViews[6].getText())) {
            mProvinceView.setVisibility(GONE);
            mNumView.setVisibility(VISIBLE);
        if (isUpdateView) {
            setTextViewsBackground(updateViewPosition);
        } else {
            setTextViewsBackground(count);
        return true;
     * 显示 7 个输入框
    public boolean hideLastView() {
        TextViews[7].setVisibility(GONE);
        ITEM_VIEW_COUNT = 7;
        if (stringBuffer.length() == 8 || stringBuffer.length() == 7) {
            TextViews[7].setText("");
            stringBuffer.delete(7, 8);
            inputContent = stringBuffer.toString();
            count = stringBuffer.length();
            inputListener.inputComplete(inputContent);
            if (!isUpdateView) {
                mNumView.setVisibility(GONE);
        if (isUpdateView) {
            setTextViewsBackground(updateViewPosition);
        } else {
            setTextViewsBackground(count);
        return false;
    private boolean onKeyDelete() {
        if (count == 0) {
            count = 7;
            return true;
        if (stringBuffer.length() > 0) {
            //删除相应位置的字符
            stringBuffer.delete((count - 1), count);
            count--;
            if (count == 0) {
                //切换回省份选择
                mProvinceView.setVisibility(VISIBLE);
                mNumView.setVisibility(GONE);
            inputContent = stringBuffer.toString();
            TextViews[stringBuffer.length()].setText("");
            setTextViewsBackground(count);
            //有删除就通知manger
            inputListener.deleteContent();
        return false;
     * 清空输入内容
    public void clearEditText() {
        stringBuffer.delete(0, stringBuffer.length());
        inputContent = stringBuffer.toString();
        for (int i = 0; i < TextViews.length; i++) {
            TextViews[i].setText("");
            TextViews[i].setBackgroundResource(R.drawable.license_plate_code_gray_bg);
    private @NonNull
    InputListener inputListener;
    public void setInputListener(InputListener inputListener) {
        this.inputListener = inputListener;
     * 键盘的点击事件
    @Override
    public void onClick(View view) {
        if (view instanceof TextView) {
            TextView tv = (TextView) view;
            tv.setSelected(true);
            String text = tv.getText().toString();
            if (view.getId() == R.id.select_province_delete_tv || view.getId() == R.id.select_num_delete_tv) {
                inputListener.deleteContent();
            setEditContent(text);
     * 输入完成监听回调接口
    public interface InputListener {
         * @param content 当输入完成时的全部内容
        void inputComplete(String content);
         * 删除操作
        void deleteContent();
     * 获取输入文本
     * @return
    public String getEditContent() {
        return inputContent;
     * 设置 EditText 的输入内容
     * 根据isUpdateView 判断修改/删除操作
    private void setEditContent(String content) {
        if (!isUpdateView) {
            if (!TextUtils.isEmpty(content)) {
                editText.setText(content);
            } else {
                onKeyDelete();
                setTextViewsEnable(true);
        } else {
            if (!TextUtils.isEmpty(content)) {
                stringBuffer.replace(updateViewPosition, updateViewPosition + 1, content);
                isUpdateView = !isUpdateView;
                setTextViewsEnable(true);
            } else {
                TextViews[updateViewPosition].setText(content);
                if (updateViewPosition + 1 == ITEM_VIEW_COUNT) {
                    isUpdateView = !isUpdateView;
                    stringBuffer.delete(updateViewPosition, updateViewPosition + 1);
                    count--;
                inputListener.deleteContent();
                setTextViewsEnable(false);
                return;
            TextViews[updateViewPosition].setText(content);
            inputContent = stringBuffer.toString();
            count = stringBuffer.length();
            setTextViewsBackground(count);
            //切换数字输入
            mProvinceView.setVisibility(GONE);
            mNumView.setVisibility(VISIBLE);
            if (stringBuffer.length() == ITEM_VIEW_COUNT) {
                //文字长度为sblength  则调用完成输入的监听
                if (inputListener != null) {
                    inputListener.inputComplete(inputContent);
                    mNumView.setVisibility(GONE);
     * 显示输入框的TouchListener
    private class OnFrameTouchListener implements OnTouchListener {
        @Override
        public boolean onTouch(View view, MotionEvent event) {
            if (view instanceof TextView) {
                TextView tv = (TextView) view;
                tv.setFocusable(true);
                String tvString = (String) tv.getText();
                if (TextUtils.isEmpty(tvString)) {
                    isUpdateView = false;
                    return false;
                int viewId = tv.getId();
                for (int i = 0; i < stringBuffer.length(); i++) {
                    if (viewId == VIEW_IDS[i]) {
                        updateViewPosition = i;
                        if (i == 0) {
                            mProvinceView.setVisibility(VISIBLE);
                            mNumView.setVisibility(GONE);
                        } else {
                            mProvinceView.setVisibility(GONE);
                            mNumView.setVisibility(VISIBLE);
                        isUpdateView = true;
                        setTextViewsBackground(i);
            return true;
     * 当修改选中的某个号码,其他数字不能被选中,防止只改变显示,造成数据错误
    private void setTextViewsEnable(boolean enabled) {
        for (int i = 0; i < TextViews.length; i++) {
            TextViews[i].setEnabled(enabled);
    private void setTextViewsBackground(int position) {
        //第一个框的样式
        if (position == 0) {
            TextViews[0].setBackgroundResource(R.drawable.license_plate_first_view_blue);
        } else {
            TextViews[0].setBackgroundResource(R.drawable.license_plate_first_view_all_gray);
        //从第二个开始,到倒数第二个
        //根据点击选中效果,设置两边的样式
        if (position < ITEM_VIEW_COUNT - 2 && position >= 1) {
            for (int i = 1; i < ITEM_VIEW_COUNT - 2; i++) {
                TextViews[i].setBackgroundResource(R.drawable.license_plate_view_right_gray);
            if (position == 1) {
                TextViews[position - 1].setBackgroundResource(R.drawable.license_plate_first_view_gray);
            } else {
                TextViews[position - 1].setBackgroundResource(R.drawable.license_plate_view_half_gray);
            TextViews[position].setBackgroundResource(R.drawable.license_plate_mid_view_blue);
        } else {
            for (int i = 1; i < ITEM_VIEW_COUNT - 2; i++) {
                TextViews[i].setBackgroundResource(R.drawable.license_plate_view_right_gray);
        //倒数第二个框的样式,根据选中的效果,设置前后两个框的样式
        if (position == ITEM_VIEW_COUNT - 2) {
            TextViews[position].setBackgroundResource(R.drawable.license_plate_mid_view_blue);
            TextViews[position + 1].setBackgroundResource(R.drawable.license_plate_last_view_bg);
            TextViews[position - 1].setBackgroundResource(R.drawable.license_plate_view_half_gray);
        } else {
            TextViews[ITEM_VIEW_COUNT - 2].setBackgroundResource(R.drawable.license_plate_mid_view_bg);
        //最后一个框的样式,根据选中的样式,前面一个样式需要改变
        if (position == ITEM_VIEW_COUNT - 1) {
            TextViews[position].setBackgroundResource(R.drawable.license_plate_last_view_blue);
            TextViews[position - 1].setBackgroundResource(R.drawable.license_plate_view_half_gray);
        } else {
            TextViews[ITEM_VIEW_COUNT - 1].setBackgroundResource(R.drawable.license_plate_last_view_bg);
     * 禁用系统软键盘
    public void hideSoftInputMethod() {
        mActivity.getWindow().setSoftInputMode(
                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
        int currentVersion = android.os.Build.VERSION.SDK_INT;
        String methodName = null;
        if (currentVersion >= 16) {
            // 4.2
            methodName = "setShowSoftInputOnFocus";
        } else if (currentVersion >= 14) {
            // 4.0
            methodName = "setSoftInputShownOnFocus";
        if (methodName == null) {
            editText.setInputType(InputType.TYPE_NULL);
        } else {
            Class<EditText> cls = EditText.class;
            Method setShowSoftInputOnFocus;
            try {
                setShowSoftInputOnFocus = cls.getMethod(methodName,
                        boolean.class);
                setShowSoftInputOnFocus.setAccessible(true);
                setShowSoftInputOnFocus.invoke(editText, false);
            } catch (NoSuchMethodException e) {
                editText.setInputType(InputType.TYPE_NULL);
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <EditText
        android:id="@+id/item_edittext"
        android:layout_width="match_parent"
        android:layout_height="46dp"
        android:background="@android:color/transparent"
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:gravity="center"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/item_code_iv1"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:background="@drawable/license_plate_first_view_blue"/>
        <TextView
            android:id="@+id/item_code_iv2"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:background="@drawable/license_plate_mid_view_bg"/>
        <TextView
            android:id="@+id/item_code_iv3"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:background="@drawable/license_plate_mid_view_bg"/>
        <TextView
            android:id="@+id/item_code_iv4"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:background="@drawable/license_plate_mid_view_bg"/>
        <TextView
            android:id="@+id/item_code_iv5"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:background="@drawable/license_plate_mid_view_bg"/>
        <TextView
            android:id="@+id/item_code_iv6"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:background="@drawable/license_plate_mid_view_bg"/>
        <TextView
            android:id="@+id/item_code_iv7"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:background="@drawable/license_plate_last_view_bg"/>
        <TextView
            android:id="@+id/item_code_iv8"
            style="@style/license_plate_editStyle"
            android:layout_weight="1"
            android:visibility="gone"/>
    </LinearLayout>
</FrameLayout>
<style name="license_plate_editStyle">
    <item name="android:layout_height">48dp</item>
    <item name="android:layout_width">48dp</item>
    <item name="android:gravity">center</item>
    <item name="android:textColor">@color/colorMainBlue</item>
    <item name="android:textSize">18dp</item>
</style>
实现的逻辑代码已经完成,可根据具体的业务需求进行拓展。

在项目中页面显示自定义的键盘视图,根据接口回调获取输入的内容,进行显示。

需求自定义车牌输入框,可以动态添加输入框个数,7位正常车牌,8位新能源车牌。点击可修改输入过的车牌号码,附带点中效果。此输入框是基于一个方框样式的EditText,按照实际需求进行扩展的。现已更新至 GitHub,可直接依赖。传送门-&amp;gt; LicensePlateUtil ,喜欢的给点个星,谢谢啦。。。动态效果如下:实现思路: 1.用一个透明的 EditText 与 8 个 TextVie... android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:..
最近在android项目中,遇到需要android车牌键盘的需求(需要支持普通车牌,新能源,警车,军车,领事馆车,教练车以及特种车辆等车牌)话不多说,分享一下android车牌键盘效果图,以及源码1、键盘控制器 2、键盘布局xml文件
    由于公司业务要求,对车牌输入和密码有特殊要求,而之前的输入呢有不合适,所以需重新定义,然而最气人的是公司的项目开发工具又是eclipse,很尴尬,用惯啦Android studio的我开始有点茫然,想着如何才能将这样子一个功能封装成一个JAR,这样子以后我就安逸啦。说做就做呀。 APP的UI效果图:      参考github大神的代码:https://github.com/yo...
if not plate[0].isalpha() or not plate[1].isalpha(): return False if not plate[2].isdigit() or not plate[3].isdigit() or not plate[4].isdigit() or not plate[5].isdigit(): return False if not plate[6].isalpha(): return False return Trueprint(validate_license_plate("A1B2C3D")) # True
AS升级3.1 编译报错:The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin. 47316 AS升级3.1 编译报错:The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin. winterXin: OK,不见不散 AS升级3.1 编译报错:The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin. winterXin: 滴滴专车见 PackageInstaller 类安装/卸载应用 Zuibbler: AS升级3.1 编译报错:The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin. 谢了,请你吃肉。