在项目开发中经常需要自定义键盘和自定义输入框,尤其是涉及修改密码的相关功能;故此学习后进行记录~
因为是自己在Git上找的 功能项目,写Demo时发现引用不是很便利;所以查看作者源码了解到该功能采用了自定义控件实现,抱着求知的心态,就一步步跟着走了一次,索性最后是成功的,有兴趣的可以 下载Demo
一别数年
- 基本了解
- 实现过程
- 自定义 输入框
- 自定义 数字键盘
- 场景使用
这是2018年时记录的一篇blog,当时只顾着以实现业务功能为目的学习,其实内部实现并未完全搞懂,特此在2023重新完善一下
基本了解
Demo效果
分包结构
实现过程
主要由输入框自定义控件
,数字键盘自定义控件
和使用场景
组成,俩款自定义控件的实现方式也是采用了 xml + 自定义逻辑
的方式实现,相对更适合新手入门学习
~
自定义 输入框
以前没有细看逻辑,只顾着实现功能了,现在补充一下实现逻辑
根据输入框的实现方式,承载输入内容的其实有俩者
- 视图控件效果(
每个单独输入框的单数据
) - 用于提交使用的完整数据(
所有输入框的综合数据
)
调用数字键盘的监听回调,当用户点击数字键盘时获取用户操作
- 如果为
数字
,则设置输入框单数据
和完整数据appen拼接
- 如果为
删除
,则清除输入框单数据
和从后往前del完整数据
- 如果为
完成
,这部分逻辑每个人都不同,自定定义即可
单独对第六个输入框实行监听(addTextChangedListener
),判断完整数据的有效性,然后将最终结果回调到外部即可
PayEditText
package com.example.yongliu.payview;
import android.content.Context;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* author yongliu
* date 2018/2/28.
* desc: 自定义输入框样式、功能
*/
public class PayEditText extends LinearLayout{
private Context context;
private TextView tvFirst, tvSecond, tvThird, tvForth, tvFifth, tvSixth;
private StringBuilder mPassword;
private OnInputFinishedListener onInputFinishedListener;
public PayEditText(Context context){
this(context, null);
initPayEditText();
initEvent();
}
public PayEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
initPayEditText();
initEvent();
}
public PayEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
initPayEditText();
initEvent();
}
private void initEvent() {
tvSixth.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
//六个密码都输入完成时回调
if (onInputFinishedListener != null && mPassword != null && mPassword.toString().length() == 6 && !TextUtils.isEmpty(s.toString())) {
onInputFinishedListener.onInputFinished(mPassword.toString());
}
}
});
}
/**
* 初始化PayEditText
*/
private void initPayEditText() {
View view = View.inflate(context, R.layout.view_pay_edit, null);
tvFirst = (TextView) view.findViewById(R.id.tv_pay1);
tvSecond = (TextView) view.findViewById(R.id.tv_pay2);
tvThird = (TextView) view.findViewById(R.id.tv_pay3);
tvForth = (TextView) view.findViewById(R.id.tv_pay4);
tvFifth = (TextView) view.findViewById(R.id.tv_pay5);
tvSixth = (TextView) view.findViewById(R.id.tv_pay6);
mPassword = new StringBuilder();
addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
/**
* 输入第一个密码
* @param first
*/
public void setTextFirst(String first) {
tvFirst.setText(first);
mPassword.append(first);
}
/**
* 输入第二个密码
* @param second
*/
public void setTextSecond(String second) {
tvSecond.setText(second);
mPassword.append(second);
}
/**
* 输入第三个密码
* @param third
*/
public void setTextThird(String third) {
tvThird.setText(third);
mPassword.append(third);
}
/**
* 输入第四个密码
* @param forth
*/
public void setTextForth(String forth) {
tvForth.setText(forth);
mPassword.append(forth);
}
/**
* 输入第五个密码
* @param fifth
*/
public void setTextFifth(String fifth) {
tvFifth.setText(fifth);
mPassword.append(fifth);
}
/**
* 输入第六位密码
* @param sixth
*/
public void setTextSixth(String sixth) {
tvSixth.setText(sixth);
mPassword.append(sixth);
}
/**
* 输入密码
* @param value
*/
public void add(String value) {
if (mPassword != null && mPassword.length() < 6) {
mPassword.append(value);
if (mPassword.length() == 1) {
tvFirst.setText(value);
} else if (mPassword.length() == 2) {
tvSecond.setText(value);
}else if (mPassword.length() == 3) {
tvThird.setText(value);
}else if (mPassword.length() == 4) {
tvForth.setText(value);
}else if (mPassword.length() == 5) {
tvFifth.setText(value);
}else if (mPassword.length() == 6) {
tvSixth.setText(value);
}
}
}
/**
* 删除密码
*/
public void remove() {
if (mPassword != null && mPassword.length() > 0) {
if (mPassword.length() == 1) {
tvFirst.setText("");
} else if (mPassword.length() == 2) {
tvSecond.setText("");
}else if (mPassword.length() == 3) {
tvThird.setText("");
}else if (mPassword.length() == 4) {
tvForth.setText("");
}else if (mPassword.length() == 5) {
tvFifth.setText("");
}else if (mPassword.length() == 6) {
tvSixth.setText("");
}
mPassword.deleteCharAt(mPassword.length() - 1);
}
}
/**
* 返回输入的内容
* @return 返回输入内容
*/
public String getText() {
return (mPassword == null) ? null : mPassword.toString();
}
/**
* 当密码输入完成时的回调接口
*/
public interface OnInputFinishedListener {
void onInputFinished(String password);
}
/**
* 对外开放的方法
* @param onInputFinishedListener
*/
public void setOnInputFinishedListener(OnInputFinishedListener onInputFinishedListener) {
this.onInputFinishedListener = onInputFinishedListener;
}
}
view_pay_edit
实现效果
为了方便看效果,临时将根布局 layout_height 换为 wrap_content ,不必关注~
整体布局方式采用 LinearLayout 横向权重 + shape
实现
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/shape_pay_edit"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_pay1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:maxLength="1"
android:inputType="numberPassword"
android:textSize="32sp" />
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:background="#999999" />
<TextView
android:id="@+id/tv_pay2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:maxLength="1"
android:inputType="numberPassword"
android:textSize="32sp" />
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:background="#999999" />
<TextView
android:id="@+id/tv_pay3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:maxLength="1"
android:inputType="numberPassword"
android:textSize="32sp" />
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:background="#999999" />
<TextView
android:id="@+id/tv_pay4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="numberPassword"
android:maxLength="1"
android:textSize="32sp" />
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:background="#999999" />
<TextView
android:id="@+id/tv_pay5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="numberPassword"
android:maxLength="1"
android:textSize="32sp" />
<View
android:layout_width="1px"
android:layout_height="match_parent"
android:background="#999999" />
<TextView
android:id="@+id/tv_pay6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:inputType="numberPassword"
android:maxLength="1"
android:textSize="32sp" />
</LinearLayout>
shape_pay_edit(输出框背景圆角化
)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="5dp"></corners>
<solid android:color="@android:color/white"></solid>
<stroke android:color="@android:color/darker_gray" android:width="1dp"></stroke>
</shape>
自定义 数字键盘
现在补充一下实现逻辑
- 因为该数字键盘样式相对简单,这里
采用GridView(网格布局),可以快速实现三等分效果
- 采用列表布局时(
网格布局也属于列表,只不过后来都用RecyvlerView,然后设置对应Manger
),都需要写适配器(Adapter
),在Adapter中通过itemType来决定是加载数字按钮样式,还是删除按钮样式
- 针对用户点击不同按钮的行为,为数字键盘加入监听,
内部通过监听GridView回调事件向外部抛出一个监听回调
KeyBoard (数字键盘)
package com.example.yongliu.payview;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* author yongliu
* date 2018/2/28.
* desc: 自定义数字键盘
*/
public class Keyboard extends RelativeLayout {
private Context context;
private GridView gvKeyboard;
private String[] key;
private OnClickKeyboardListener onClickKeyboardListener;
//因为数据源是在外部通过Api(setKeyboardKeys)动态传入,故无需在构造参数中写死
public Keyboard(Context context) {
this(context, null);
}
public Keyboard(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Keyboard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
/**
* 初始化KeyboardView
*/
private void initKeyboardView() {
View view = View.inflate(context, R.layout.view_keyboard, this);
gvKeyboard = (GridView) view.findViewById(R.id.gv_keyboard);
gvKeyboard.setAdapter(keyboardAdapter);
initEvent();
}
/**
* 初始化键盘的点击事件
*/
private void initEvent() {
gvKeyboard.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (onClickKeyboardListener != null && position >= 0) {
onClickKeyboardListener.onKeyClick(position, key[position]);
}
}
});
}
public interface OnClickKeyboardListener {
void onKeyClick(int position, String value);
}
/**
* 对外开放的方法
*
* @param onClickKeyboardListener
*/
public void setOnClickKeyboardListener(OnClickKeyboardListener onClickKeyboardListener) {
this.onClickKeyboardListener = onClickKeyboardListener;
}
/**
* 设置键盘所显示的内容
*
* @param key
*/
public void setKeyboardKeys(String[] key) {
this.key = key;
initKeyboardView();
}
private BaseAdapter keyboardAdapter = new BaseAdapter() {
private static final int KEY_NINE = 9;
@Override
public int getCount() {
return key.length;
}
@Override
public Object getItem(int position) {
return key[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
return (getItemId(position) == KEY_NINE) ? 2 : 1;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
if (getItemViewType(position) == 1) {
//数字键
convertView = LayoutInflater.from(context).inflate(R.layout.item_grid_keyboard, parent, false);
viewHolder = new ViewHolder(convertView);
} else {
//删除键
convertView = LayoutInflater.from(context).inflate(R.layout.item_grid_keyboard_delete, parent, false);
}
}
if (getItemViewType(position) == 1) {
viewHolder = (ViewHolder) convertView.getTag();
viewHolder.tvKey.setText(key[position]);
}
return convertView;
}
};
/**
* ViewHolder,view缓存
*/
static class ViewHolder {
private TextView tvKey;
public ViewHolder(View view) {
tvKey = (TextView) view.findViewById(R.id.tv_keyboard_keys);
view.setTag(this);
}
}
}
view_keyboard(数字键盘
)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="1px"
android:background="#999999" />
<GridView
android:id="@+id/gv_keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:horizontalSpacing="1px"
android:numColumns="3"
android:background="#999999"
android:verticalSpacing="1px" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#999999" />
</LinearLayout>
item_grid_keyboard(键盘中数字按钮、完成按钮样式
)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_keyboard_keys"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/selector_keyboard_key_bg"
android:gravity="center"
android:padding="10dp"
android:textSize="25sp"/>
</LinearLayout>
item_grid_keyboard_delete(键盘中删除按钮样式
)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:layout_width="120dp"
android:layout_gravity="center"
android:gravity="center"
android:layout_height="52dp">
<ImageView
android:layout_width="26dp"
android:layout_height="26dp"
android:scaleType="fitXY"
android:src="@mipmap/ic_delete"/>
</LinearLayout>
</LinearLayout>
selector_keyboard_key_bg(点击数字键盘时的按钮状态:分为默认状态和点击状态(提升用户体验)
)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false" >
<shape android:shape="rectangle">
<solid android:color="#ffffff"/>
</shape>
</item>
<item android:state_pressed="true" >
<shape android:shape="rectangle">
<solid android:color="#e5e5e5"/>
</shape>
</item>
</selector>
场景使用
MainActivity
package com.example.yongliu.payview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private static final String[] KEY = new String[]{
"1", "2", "3",
"4", "5", "6",
"7", "8", "9",
"<<", "0", "完成"
};
private PayEditText payEditText;
private Keyboard keyboard;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setSubView();
initEvent();
}
private void initView() {
setContentView(R.layout.activity_main);
payEditText = (PayEditText) findViewById(R.id.PayEditText_pay);
keyboard = (Keyboard) findViewById(R.id.KeyboardView_pay);
}
private void setSubView() {
//设置键盘
keyboard.setKeyboardKeys(KEY);
}
private void initEvent() {
keyboard.setOnClickKeyboardListener(new Keyboard.OnClickKeyboardListener() {
@Override
public void onKeyClick(int position, String value) {
if (position < 11 && position != 9) {
payEditText.add(value);
} else if (position == 9) {
payEditText.remove();
} else if (position == 11) {
//当点击完成的时候,也可以通过payEditText.getText()获取密码,此时不应该注册OnInputFinishedListener接口
Toast.makeText(getApplication(), "您的密码是:" + payEditText.getText(), Toast.LENGTH_SHORT).show();
finish();
}
}
});
/**
* 当密码输入完成时的回调
*/
payEditText.setOnInputFinishedListener(new PayEditText.OnInputFinishedListener() {
@Override
public void onInputFinished(String password) {
Toast.makeText(getApplication(), "您的密码是:" + password, Toast.LENGTH_SHORT).show();
}
});
}
}
activity_main
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.yongliu.payview.PayEditText
android:id="@+id/PayEditText_pay"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentTop="true"
android:layout_marginTop="20dp"
android:paddingLeft="12dp"
android:paddingRight="12dp" />
<com.example.yongliu.payview.Keyboard
android:id="@+id/KeyboardView_pay"
android:layout_width="match_parent"
android:layout_height="215dp"
android:layout_alignParentBottom="true" />
</RelativeLayout>