在项目开发中自定义控件使用场景相对比较频繁,基本每一个app都会有一些自定义控件,尤其金融产品中涉及的可能更多一些,例如普通app一般使用的都是原生的系统键盘,只需设定初始展示类型为数字键盘、英文键盘基本就可以满足诉求;但是金融方面的键盘一般都需要自定义,故此该篇先从简单的数字键盘开始…
一直以来自定义控件应该是我的短板之一,所以决定今年好好再学习、巩固一下这方面的不足
在项目中也经常可以见到有些数字键盘是随机显示数字
的,不过采用当前所写方式尚不支持随机数键盘
,如果要用随机数键盘的话,需要改一些代码,等有机会我补充一下随机数键盘
+ + ~
基础了解
这款 自定义数字键盘 采用了 xml(视图) + 自定义类(逻辑)
的方式,优点是入门很简单,实现起来也挺快
;缺点也很明显就是相对死板、扩展有限
(os:这种方式严格意义上讲不能算是全自定义控件,因为不需要重写View的绘制流程,也不需要进行View的汇算操作,也没有用自定义属性等;
)
当前Demo - 实现效果
关于数字键盘的调用场景,在我目前能想到的话有以下俩种
- 静态页面,
当前页面显示数字键盘 + 输入内容(输入内容不可点击)
好多年前跟着别人学习过 自定义密码输入框、数字键盘 ,这种效果就是我讲的静态页面效果
; 不过,当前我们只讲数字键盘
…
- 动态页面,
用户点击特定控件时弹出数字键盘(相对使用场景更灵活)
,等后续有机会我再补一篇
自定义 数字键盘
NumKeyboard
内部逻辑实现简单,数据源固定不可变,故通过构造参数传入;同时针对不同按钮进行监听回调即可
package com.example.numkeyboard;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class NumKeyboard extends FrameLayout implements View.OnClickListener {
public NumKeyboard(@NonNull Context context) {
super(context);
initView();
}
public NumKeyboard(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView();
}
public NumKeyboard(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
View numView = LayoutInflater.from(this.getContext()).inflate(R.layout.num_key_board, null);
numView.findViewById(R.id.tv_0).setOnClickListener(this);
numView.findViewById(R.id.tv_1).setOnClickListener(this);
numView.findViewById(R.id.tv_2).setOnClickListener(this);
numView.findViewById(R.id.tv_3).setOnClickListener(this);
numView.findViewById(R.id.tv_4).setOnClickListener(this);
numView.findViewById(R.id.tv_5).setOnClickListener(this);
numView.findViewById(R.id.tv_6).setOnClickListener(this);
numView.findViewById(R.id.tv_7).setOnClickListener(this);
numView.findViewById(R.id.tv_8).setOnClickListener(this);
numView.findViewById(R.id.tv_9).setOnClickListener(this);
numView.findViewById(R.id.tv_del).setOnClickListener(this);
numView.findViewById(R.id.tv_done).setOnClickListener(this);
LayoutParams params = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
this.addView(numView, params);
}
@SuppressLint("NonConstantResourceId")
@Override
public void onClick(View view) {
if (onNumKeyBoardLister == null) {
return;
}
switch (view.getId()) {
case R.id.tv_0:
onNumKeyBoardLister.onNumLister(0);
break;
case R.id.tv_1:
onNumKeyBoardLister.onNumLister(1);
break;
case R.id.tv_2:
onNumKeyBoardLister.onNumLister(2);
break;
case R.id.tv_3:
onNumKeyBoardLister.onNumLister(3);
break;
case R.id.tv_4:
onNumKeyBoardLister.onNumLister(4);
break;
case R.id.tv_5:
onNumKeyBoardLister.onNumLister(5);
break;
case R.id.tv_6:
onNumKeyBoardLister.onNumLister(6);
break;
case R.id.tv_7:
onNumKeyBoardLister.onNumLister(7);
break;
case R.id.tv_8:
onNumKeyBoardLister.onNumLister(8);
break;
case R.id.tv_9:
onNumKeyBoardLister.onNumLister(9);
break;
case R.id.tv_del:
onNumKeyBoardLister.onDelLister();
break;
case R.id.tv_done:
onNumKeyBoardLister.onDownLister();
break;
default:
break;
}
}
public void setOnNumKeyBoardLister(NumKeyBoardLister onNumKeyBoardLister) {
this.onNumKeyBoardLister = onNumKeyBoardLister;
}
public NumKeyBoardLister onNumKeyBoardLister;
//事件监听-接口
public interface NumKeyBoardLister {
//点击数字按钮时的监听回调
void onNumLister(int num);
//点击删除按钮时的监听回调
void onDelLister();
//点击完成按钮时的监听回调
void onDownLister();
}
}
用 xml
画一个粗糙数字键盘页面
num_key_board
关于键盘方面的布局方式
- 有的人用的
GridView
、有的人用的RecyclerView
,用这种方式主要体现在有规则的列表或者网格数据; - 如果要定义性更强一点,那么就需要自行绘制了,我此处为图方便直接用
LinearLayoutCompat
权重画的
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="1" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="2" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="3" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_4"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="4" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_5"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="5" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_6"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="6" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_7"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="7" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_8"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="8" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_9"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="9" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_del"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="删除" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_0"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="0" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/tv_done"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="完成" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/black" />
</androidx.appcompat.widget.LinearLayoutCompat>
场景使用
逻辑很简单,简要说一下
- 获取键盘回调,含数字、删除、确定
- 当获取数字回调时光标置于尾部 + String拼接(
因禁用事件,可不管光标问题
) - 当获取删除回调时光标置于尾部 + 尾部逐位删除(
因禁用事件,可不管光标问题
) - 当获取完成回调时获取输入内容
MainActivity
package com.example.numkeyboard
import android.os.Bundle
import android.util.Log
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.numkeyboard.NumKeyboard.NumKeyBoardLister
import org.apache.commons.lang3.StringUtils
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var mEditNum = findViewById<EditText>(R.id.et_num)
var mDisplay = findViewById<TextView>(R.id.tv_display)
findViewById<NumKeyboard>(R.id.numKeyboard).setOnNumKeyBoardLister(object : NumKeyBoardLister {
override fun onNumLister(num: Int) {
Toast.makeText(this@MainActivity, num.toString(), Toast.LENGTH_SHORT).show()
//将输入的数字填写输入框中
mEditNum.text = mEditNum.text.append(num.toString())
//将光标设置到尾部
mEditNum.setSelection(mEditNum.text.length)
}
override fun onDelLister() {
Toast.makeText(this@MainActivity, "删除", Toast.LENGTH_SHORT).show()
if (mEditNum.text.isNotEmpty()) {
var length = mEditNum.text.length
//从后往前逐个删除
mEditNum.text = mEditNum.text.delete(length - 1, length)
//将光标设置到尾部
mEditNum.setSelection(mEditNum.text.length)
}
}
override fun onDownLister() {
Toast.makeText(this@MainActivity, "完成", Toast.LENGTH_SHORT).show()
if (mEditNum.text.isNotEmpty()) {
mDisplay.text = mEditNum.text
}
}
})
}
}
当前页呈现效果
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_num"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginHorizontal="50dp"
android:layout_marginTop="40dp"
android:background="@null"
android:clickable="false"
android:enabled="false"
android:focusable="false"
android:gravity="center"
android:hint="输入内容"
android:textColor="@color/black" />
<TextView
android:id="@+id/tv_display"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginHorizontal="50dp"
android:layout_marginTop="15dp"
android:gravity="center"
android:hint="完成 - 显示区域" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.example.numkeyboard.NumKeyboard
android:id="@+id/numKeyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>