前言
最近Android开发中用到了自定义数字键盘,网上找的demo不能满足我的需求,比如删除和插入的时候只能在最后删除和插入,不能通过滑动键盘来移动光标。所以现在完成后把它总结写出来。
概述
主要实现以下功能:
(1)只有数字键,包括没有标点符号。
(2)可以在任意点插入和删除数字。
(3)可以通过手指左右滑动键盘来改变光标位置。
(4)输入框右边删除图标和按钮可随着内容有无变化。
(5)每三个数字空一格,输入框最多输入13个字符(包括空格)。
步骤:
1、新建一个KeyboardView.java文件。用来自定义所需要的键盘。
首先初始化绑定布局文件。
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.layout_key_board, this);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
rl_back=findViewById(R.id.rl_back);
rl_back.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) { // 点击关闭键盘
dismiss();
}
});
initData();
initView();
initAnimation();
}
填充数据(如果需要标点符号,则在i==9中填充即可)
private void initData() {
keyboardWords = new ArrayList<>();
for (int i = 0; i < 12; i++) {
if (i < 9) {
keyboardWords.add(String.valueOf(i + 1));
} else if (i == 9) {
keyboardWords.add("");
} else if (i == 10) {
keyboardWords.add("0");
} else {
keyboardWords.add("");
}
}
}
设置适配器
private void initView() {
int spanCount = 12;
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
recyclerView.setNestedScrollingEnabled(false);
recyclerView.addItemDecoration(new SpaceItemDecoration(spanCount));
adapter = new KeyboardAdapter(getContext(), keyboardWords);
recyclerView.setAdapter(adapter);
}
对键盘的操作
//判断软键盘的状态
public boolean isVisible() {
if (getVisibility() == VISIBLE) {
return true;
}
return false;
}
//弹出软键盘
public void show() {
startAnimation(animationIn);
setVisibility(VISIBLE);
}
//关闭软键盘
public void dismiss() {
if (isVisible()) {
startAnimation(animationOut);
setVisibility(GONE);
}
}
2、Adapter操作。
private Context context;
private List<String> datas;
private OnKeyboardClickListener listener;
public KeyboardAdapter(Context context, List<String> datas) {
this.context = context;
this.datas = datas;
}
@Override
public KeyboardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_key_board, parent, false);
KeyboardHolder holder = new KeyboardHolder(view);
setListener(holder);
return holder;
}
private void setListener(final KeyboardHolder holder) {
holder.tvKey.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (listener != null) {
if (holder.getAdapterPosition() != 9) {
listener.onKeyClick(view, holder, holder.getAdapterPosition());
}
}
}
});
holder.rlDel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (listener != null) {
listener.onDeleteClick(view, holder, holder.getAdapterPosition());
}
}
});
}
@Override
public void onBindViewHolder(KeyboardHolder holder, int position) {
if (position == 9) {
holder.setVisibility(false);
} else if (position == 11) {
holder.rlDel.setVisibility(View.VISIBLE);
holder.tvKey.setVisibility(View.GONE);
} else {
holder.tvKey.setText(datas.get(position));
}
}
@Override
public int getItemCount() {
return datas == null ? 0 : datas.size();
}
class KeyboardHolder extends RecyclerView.ViewHolder {
public TextView tvKey;
public RelativeLayout rlDel;
private View convertView;
public KeyboardHolder(View itemView) {
super(itemView);
convertView = itemView;
tvKey = itemView.findViewById(R.id.tv_key);
rlDel = itemView.findViewById(R.id.rl_del);
}
public View getconvertView() {
return convertView;
}
public void setVisibility(boolean b) {
RecyclerView.LayoutParams param = (RecyclerView.LayoutParams) itemView.getLayoutParams();
if (b) {
param.height = LinearLayout.LayoutParams.WRAP_CONTENT;
param.width = LinearLayout.LayoutParams.MATCH_PARENT;
itemView.setVisibility(View.VISIBLE);
} else {
itemView.setVisibility(View.GONE);
param.height = 0;
param.width = 0;
}
itemView.setLayoutParams(param);
}
}
public interface OnKeyboardClickListener {
void onKeyClick(View view, RecyclerView.ViewHolder holder, int position);
void onDeleteClick(View view, RecyclerView.ViewHolder holder, int position);
}
public void setOnKeyboardClickListener(OnKeyboardClickListener listener) {
this.listener = listener;
}
}
3、在Activity中的运用。
关闭系统键盘
private void enableSystemKeyboard() {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
try {
Class<EditText> cls = EditText.class;
Method setShowSoftInputOnFocus = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
setShowSoftInputOnFocus.setAccessible(true);
setShowSoftInputOnFocus.invoke(videoIdEt, false);
} catch (Exception e) {
e.printStackTrace();
}
}
键盘数字按钮的点击事件,并且获取输入当前光标位置
@SuppressLint("SetTextI18n")
@Override
public void onKeyClick(View view, RecyclerView.ViewHolder holder, int position) {
String editText = videoIdEt.getText().toString();
switch (position) {
case 9:
break;
default:
//获取当前光标位置
int index = videoIdEt.getSelectionStart();
if (index != videoIdEt.getText().length()) {
//在光标处插入数字
String inputEditText = keyboardNumbers.get(position);
Log.d(TAG, "inputEditText:" + inputEditText);
videoIdEt.getText().insert(index, inputEditText);
} else {
videoIdEt.setText(videoIdEt.getText().toString().trim() + keyboardNumbers.get(position));
videoIdEt.setSelection(videoIdEt.getText().length());
}
if (videoIdEt.getText().length() > 0) {
findViewById(R.id.ai_long_tv_confirm).setBackgroundResource(R.drawable.bg_determine_text);
Drawable drawable = getResources().getDrawable(R.drawable.ic_delete1);
//设置 ClickableEditText中只显示左边图标,删除图标可见
videoIdEt.setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, drawable,null);
}
break;
}
}
手指触摸事件:获取手指按下和滑动停止的位置。将结果传给手势监听。
private View.OnTouchListener onTouchListener = new View.OnTouchListener() {
@RequiresApi(api = Build.VERSION_CODES.ECLAIR)
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "MotionEvent.ACTION_DOWN");
}
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
Log.d(TAG, "MotionEvent.ACTION_UP");
refreshStartPoint = true;
}
if (motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
if (refreshStartPoint) {
startPointEvent = motionEvent.getX(0);
curatorIndex = videoIdEt.getSelectionStart();
}
refreshStartPoint = false;
stopPointEvent = motionEvent.getX();
Log.d(TAG, "MotionEvent.ACTION_MOVE");
}
return mDetector.onTouchEvent(motionEvent);
}
};
用户手势的监测。计算开始和停止滑动距离的大小(滑动前和滑动后的距离),通过计算滑动距离来计算光标移动多少,并通过距离的正负来判断滑动的方向,从而达到在键盘上滑动来控制光标移动的目的。
@RequiresApi(api = Build.VERSION_CODES.CUPCAKE)
private void initGestureDetector() {
mDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent motionEvent) {
Log.d(TAG, "onDown");
return true;
}
@Override
public void onShowPress(MotionEvent motionEvent) {
Log.d(TAG, "onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent motionEvent) {
Log.d(TAG, "onShowPress");
return false;
}
@Override
public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
mScreenWidth = displayMetrics.widthPixels;
float x = stopPointEvent - startPointEvent;
float newPercent = Math.abs(x) / mScreenWidth;
int range = (int) ((videoIdEt.getText().length()) * newPercent);
int newIndex;
if (x > 0) {
newIndex = curatorIndex + range;
if (curatorIndex == videoIdEt.getText().length()) {
videoIdEt.setSelection(curatorIndex);
}
if (newIndex > videoIdEt.getText().length()) {
videoIdEt.setSelection(videoIdEt.getText().length());
} else {
videoIdEt.setSelection(newIndex);
}
} else {
newIndex = curatorIndex - range;
if (curatorIndex == 0) {
videoIdEt.setSelection(curatorIndex);
}
if (newIndex < 0) {
videoIdEt.setSelection(0);
} else {
videoIdEt.setSelection(newIndex);
}
}
return false;
}
@Override
public void onLongPress(MotionEvent motionEvent) {
Log.d(TAG, "onLongPress");
}
@Override
public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
// startPointEvent=motionEvent1;
Log.d(TAG, "onFling: " + "motionEvent:" + motionEvent + "motionEvent1" + motionEvent1);
return false;
}
});
}
接着我们实现每三个数字输入一个空格。通过对ClickableEditText的监听来达到这一目的。
private TextWatcher watcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int i2) {
if (isTextChanging) {
isTextChanging = false;
return;
}
isTextChanging = true;
String result = "";
String newStr = charSequence.toString();
newStr = newStr.replace(" ", "");
int index = 0;
while ((index + 3) < newStr.length()) {
result += (newStr.substring(index, index + 3) + " ");
index += 3;
}
result += (newStr.substring(index, newStr.length()));
int i = videoIdEt.getSelectionStart();
videoIdEt.setText(result);
try {
if (i % 4 == 0 && before == 0) {
if (i + 1 <= result.length()) {
videoIdEt.setSelection(i + 1);
} else {
videoIdEt.setSelection(result.length());
}
} else if (before == 1 && i < result.length()) {
videoIdEt.setSelection(i);
} else if (before == 0 && i < result.length()) {
videoIdEt.setSelection(i);
} else {
videoIdEt.setSelection(result.length());
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void afterTextChanged(Editable editable) {
}
};
对右下角删除按钮的监听,只能删除一个一个删除,不能一下删除所输入的内容
@Override
public void onDeleteClick(View view, RecyclerView.ViewHolder holder, int position) {
int currentIndex = videoIdEt.getSelectionStart();
if (currentIndex > 0) {
videoIdEt.getText().delete(currentIndex - 1, currentIndex);
if (videoIdEt.getText().length() == 0) {
//设置 ClickableEditText中只显示左边图标,删除图标不可见
videoIdEt.setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null,null);
findViewById(R.id.ai_long_tv_confirm).setBackgroundResource(R.drawable.bg_determine_disable);
}
}
}
输入框右边删除按钮的点击事件,点击可消除所有输入的内容。
@Override
public void onDrawableRightClick(View view) {
switch (view.getId()) {
case R.id.ai_long_et_video_code:
videoIdEt.getText().clear();
//设置 ClickableEditText中只显示左边图标,删除图标不可见
videoIdEt.setCompoundDrawablesWithIntrinsicBounds(mDrawable, null, null,null);
findViewById(R.id.ai_long_tv_confirm).setBackgroundResource(R.drawable.bg_determine_disable);
break;
default:
break;
}
}
结论
以上就是整个项目的实现过程,注意一点,ClickableEditText是自定义的一个EditText,由于使用ConstraintLayout,在输入框的前后两端加按钮,是为了可以点击右边删除按钮能删除所有输入的内容所写。