前不久由于项目的需要,要做一个自定义的软键盘,我也上网看了很多,都觉得很繁琐,所以想自己动手实现个。以备不时之需把。我选择了参考百度钱包的软键盘,看起来还不错:
下面一起来实现它:
1.写一个键盘控件,这个实现起来比较简单,就不多说了
public class SoftInputBoard extends RelativeLayout
implements View.OnClickListener{
private Scroller mScroller;
private int mScreenHeigh = 0;
private int mScreenWidth = 0;
private Boolean isMoving = false;
private int viewHeight = 0;
public boolean isShow = false;
public boolean mEnabled = true;
public boolean mOutsideTouchable = true;
private int mDuration = 800;
private boolean userIsLongPress = false;
@Override
public void onClick(View v) {
String value = "";
int id = v.getId();
if(id == R.id.tv0){
value = "0";
}else if(id == R.id.tv1){
value = "1";
}else if(id == R.id.tv2){
value = "2";
}else if(id == R.id.tv3){
value = "3";
}else if(id == R.id.tv4){
value = "4";
}else if(id == R.id.tv5){
value = "5";
}else if(id == R.id.tv6){
value = "6";
}else if(id == R.id.tv7){
value = "7";
}else if(id == R.id.tv8){
value = "8";
}else if(id == R.id.tv9){
value = "9";
}else if(id == R.id.iv_delete){
value = "delete";
}else if(id == R.id.tvx){
value = "X";
}
if(payListener != null){
payListener.chooseWay(value);
}
}
public interface ChoosePayWayListener{
void chooseWay(String value);
}
public ChoosePayWayListener payListener;
public void setOnChoosePayWayListener(ChoosePayWayListener payListener){
this.payListener = payListener;
}
private final static String TAG = "SoftInputView";
public SoftInputBoard(Context context) {
super(context);
init(context);
}
public SoftInputBoard(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SoftInputBoard(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setFocusable(true);
mScroller = new Scroller(context);
mScreenHeigh = BaseTools.getWindowHeigh(context);
mScreenWidth = BaseTools.getWindowWidth(context);
this.setBackgroundColor(Color.argb(0, 0, 0, 0));
final View view = LayoutInflater.from(context).inflate(R.layout.view_soft_input, null);
TextView tv0 = (TextView)view.findViewById(R.id.tv0);
TextView tv1 = (TextView)view.findViewById(R.id.tv1);
TextView tv2 = (TextView)view.findViewById(R.id.tv2);
TextView tv3 = (TextView)view.findViewById(R.id.tv3);
TextView tv4 = (TextView)view.findViewById(R.id.tv4);
TextView tv5 = (TextView)view.findViewById(R.id.tv5);
TextView tv6 = (TextView)view.findViewById(R.id.tv6);
TextView tv7 = (TextView)view.findViewById(R.id.tv7);
TextView tv8 = (TextView)view.findViewById(R.id.tv8);
TextView tv9 = (TextView)view.findViewById(R.id.tv9);
TextView tvx = (TextView)view.findViewById(R.id.tvx);
final RelativeLayout ivDelete = (RelativeLayout)view.findViewById(R.id.iv_delete);
ivDelete.setClickable(true);
tv0.setOnClickListener(this);
tv1.setOnClickListener(this);
tv2.setOnClickListener(this);
tv3.setOnClickListener(this);
tv4.setOnClickListener(this);
tv5.setOnClickListener(this);
tv6.setOnClickListener(this);
tv7.setOnClickListener(this);
tv8.setOnClickListener(this);
tv9.setOnClickListener(this);
tvx.setOnClickListener(this);
ivDelete.setOnClickListener(this);
ivDelete.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(payListener != null){
payListener.chooseWay("xdelete");
}
return false;
}
});
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);
addView(view, params);
this.setBackgroundColor(Color.argb(0, 0, 0, 0));
view.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
viewHeight = view.getHeight();
}
});
SoftInputBoard.this.scrollTo(0, mScreenHeigh);
ImageView btn_close = (ImageView)view.findViewById(R.id.btn_close);
btn_close.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
dismiss();
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if(!mEnabled){
return false;
}
return super.onInterceptTouchEvent(ev);
}
public void startMoveAnim(int startY, int dy, int duration) {
isMoving = true;
mScroller.startScroll(0, startY, 0, dy, duration);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
isMoving = true;
} else {
isMoving = false;
}
super.computeScroll();
}
public void show(){
if(!isShow && !isMoving){
SoftInputBoard.this.startMoveAnim(-viewHeight, viewHeight, mDuration);
isShow = true;
Log.d("isShow", "true");
changed();
}
}
public int getSoftInputBoardHeight(){
return viewHeight;
}
public void dismiss(){
if(isShow && !isMoving){
SoftInputBoard.this.startMoveAnim(0, -viewHeight, mDuration);
isShow = false;
Log.d("isShow", "false");
changed();
if(l!=null){
l.dismiss();
}
}
}
public interface dismissListener{
void dismiss();
}
private dismissListener l;
public void setOnDismissListener(dismissListener l){
this.l = l;
}
public boolean isShow(){
return isShow;
}
public boolean isSlidingEnabled() {
return mEnabled;
}
public void setSlidingEnabled(boolean enabled) {
mEnabled = enabled;
}
public void setOnStatusListener(onStatusListener listener){
this.statusListener = listener;
}
public void setOutsideTouchable(boolean touchable) {
mOutsideTouchable = touchable;
}
public void changed(){
if(statusListener != null){
if(isShow){
statusListener.onShow();
}else{
statusListener.onDismiss();
}
}
}
public onStatusListener statusListener;
public interface onStatusListener{
public void onShow();
public void onDismiss();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
super.onLayout(changed, l, t, r, b);
}
}
其实,开发了这么久的android了,觉得Scoller这个类还是很重要的,可以帮助我们实现很多效果,比如这个控件的出现和消失。关于Scoller这个类还不熟悉的可以看我之前总结的文章 — 带你彻彻底底弄懂Scroller。
2.第二步,也是我在开发中遇到的问题,就是如果我们的EditText太靠底端了,就是说,当我们的SoftInputBoard 调用show()方法的时候,会把用户填写的EditText给遮挡住,我们不是继承系统给我们的KeyBoard这个类,所以这个问题还是得我们自己解决。这里我还是用Scroller这类把SoftInputBoard这个控件的外布局给滑动到上面去:
/**
* Created by Nipuream on 2016/4/15 0015.
*/
public class AutoPopLayout extends RelativeLayout {
private Scroller mScroller;
private boolean isMove = false;
private SoftInputBoard softInputBoard;
private Context context;
private int currentCursorIndex = 0;
private static final int ADD = 0x45;
private static final int DE = 0x46;
//默认是加
private int addOrde = ADD;
public AutoPopLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
DecelerateInterpolator interpolator = new DecelerateInterpolator();
mScroller = new Scroller(context,interpolator);
post(new Runnable() {
@Override
public void run() {
softInputBoard = new SoftInputBoard(AutoPopLayout.this.context);
RelativeLayout rl = (RelativeLayout) AutoPopLayout.this.getParent();
rl.addView(softInputBoard);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) softInputBoard.getLayoutParams();
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.WRAP_CONTENT;
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
softInputBoard.setLayoutParams(params);
softInputBoard.setOnDismissListener(new SoftInputBoard.dismissListener() {
@Override
public void dismiss() {
AutoPopLayout.this.startMoveAnim(AutoPopLayout.this.getScrollY(),- pixDistance,500);
pixDistance = 0;
}
});
softInputBoard.setOnChoosePayWayListener(new SoftInputBoard.ChoosePayWayListener() {
@Override
public void chooseWay(String value) {
try{
String preStr = ed.getText().toString();
//银行帐号
if(!TextUtils.equals(value,"delete")){
//长按删除
if(TextUtils.equals(value,"xdelete")){
ed.setText("");
currentCursorIndex = 0;
return;
}
addOrde = ADD;
currentCursorIndex = getEditTextCursorIndex(ed);
insertText(ed,value);
preStr = ed.getText().toString();
ed.setText(preStr);
}else{
if(!TextUtils.isEmpty(preStr)){
addOrde = DE;
currentCursorIndex = getEditTextCursorIndex(ed);
deleteText(ed);
preStr = ed.getText().toString();
ed.setText(preStr);
}
}
}catch (Exception e){
}
}
});
}
});
}
/**获取EditText光标所在的位置*/
private int getEditTextCursorIndex(EditText mEditText){
return mEditText.getSelectionStart();
}
/**向EditText指定光标位置插入字符串*/
private void insertText(EditText mEditText, String mText){
mEditText.getText().insert(getEditTextCursorIndex(mEditText), mText);
}
/**向EditText指定光标位置删除字符串*/
private void deleteText(EditText mEditText){
if(!TextUtils.isEmpty(mEditText.getText().toString())){
mEditText.getText().delete(getEditTextCursorIndex(mEditText)-1, getEditTextCursorIndex(mEditText));
}
}
private int pixDistance = 0;
private void AutoPilled(final EditText view){
currentCursorIndex = 0;
view.post(new Runnable() {
@Override
public void run() {
// int screenHeight = UIUtil.getScreenHeight(getActivity().getWindowManager());
int screenHeight = BaseTools.getWindowHeigh(context);
int softInputHeight = softInputBoard.getSoftInputBoardHeight();
/**
*
*
* --------------------------------------------------- <---- screenHeight-softInputHeight
* | --------------------------- |
* | | 输入框 | |
* | ---------------------------- <---- bottom |
* | |
* | |
* ---------------------------------------------------
*
*
*/
int bottom = 0;
Rect rect = new Rect();
boolean isGet = view.getGlobalVisibleRect(rect);
if(isGet){
bottom = rect.bottom;
}
if(bottom != 0) {
if (bottom > (screenHeight - softInputHeight)) {
pixDistance = bottom - (screenHeight - softInputHeight);
AutoPopLayout.this.startMoveAnim(AutoPopLayout.this.getScrollY(),pixDistance,500);
}
}
}
});
}
private EditText ed;
private WeakReference<Activity> ref;
// 隐藏系统键盘
public void hideSoftInputMethod(final List<EditText> ets, WeakReference<Activity> ref){
this.ref = ref;
currentCursorIndex = 0;
for(final EditText ed:ets){
ed.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// focus = WhichFocus.BANK_CARD;
// AutoPopLayout.this.hideSoftInputMethod(view,ref);
AutoPopLayout.this.ed = ed;
if(!softInputBoard.isShow){
softInputBoard.show();
}
if(! AutoPopLayout.this.isMove()){
if(softInputBoard.isShow){
AutoPilled(ed);
}
}
return false;
}
});
ed.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) {
String value = s.toString();
int length = value.length();
int index = getEditTextCursorIndex(ed);
try{
if(currentCursorIndex < length){
if(addOrde == ADD){
ed.setSelection(currentCursorIndex+1);
}else{
ed.setSelection(currentCursorIndex-1);
}
}else{
ed.setSelection(length);
}
}catch (Exception e){
}
}
});
ref.get().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){
ed.setInputType(InputType.TYPE_NULL);
}
else{
Class<EditText> cls = EditText.class;
Method setShowSoftInputOnFocus;
try {
setShowSoftInputOnFocus = cls.getMethod(methodName, boolean.class);
setShowSoftInputOnFocus.setAccessible(true);
setShowSoftInputOnFocus.invoke(ed, false);
} catch (NoSuchMethodException e) {
ed.setInputType(InputType.TYPE_NULL);
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void destroyAutoPopLayout(){
if(ref != null){
ref = null;
this.removeAllViews();
}
}
public void startMoveAnim(int startY, int dy, int duration) {
isMove = true;
mScroller.startScroll(0, startY, 0, dy, duration);
invalidate();//通知UI线程的更新
}
@Override
public void computeScroll() {
//判断是否还在滚动,还在滚动为true
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//更新界面
postInvalidate();
isMove = true;
} else {
isMove = false;
}
super.computeScroll();
}
public boolean isMove(){
return isMove;
}
}
上面的AutoPopLayout 里面除了解决SoftInputBoard对EditText遮挡的问题,还实现了大量的对EditText处理的问题,包括我点击SoftInputBoard,讲键盘TextView中的内容,setText()到EditText上面去,当然,你可以对TextView的内容进行加密处理,然后传输到服务端去,不然我们自定义键盘也没有必要了,这里我这步操作就省略了。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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"
tools:context="com.hr.nipurem.softinputdemo.MainActivity"
>
<com.hr.nipurem.softinputdemo.view.AutoPopLayout
android:id="@+id/autoPopLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<EditText
android:id="@+id/auto_et1"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_centerInParent="true"
/>
<EditText
android:id="@+id/auto_et2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_below="@+id/auto_et1"
/>
</com.hr.nipurem.softinputdemo.view.AutoPopLayout>
</RelativeLayout>
可以看到,其实我在AutoPopLayout外面还嵌套了一层RelativeLayout,因为我们的SoftInputBoard不能在AutoPopLayout里面,不然,我们的SoftInputBoard就会随着我们的AutoPopLayout上移了。
3.第三步,我们来处理下对于EditText的处理,当然界面层上的EditText要传到我们这来,还需要个Activity的context,需要这个context去隐藏系统的软键盘
// 隐藏系统键盘
public void hideSoftInputMethod(final List<EditText> ets, WeakReference<Activity> ref){
this.ref = ref;
currentCursorIndex = 0;
for(final EditText ed:ets){
ed.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// focus = WhichFocus.BANK_CARD;
// AutoPopLayout.this.hideSoftInputMethod(view,ref);
AutoPopLayout.this.ed = ed;
if(!softInputBoard.isShow){
softInputBoard.show();
}
if(! AutoPopLayout.this.isMove()){
if(softInputBoard.isShow){
AutoPilled(ed);
}
}
return false;
}
});
ed.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) {
String value = s.toString();
int length = value.length();
int index = getEditTextCursorIndex(ed);
try{
if(currentCursorIndex < length){
if(addOrde == ADD){
ed.setSelection(currentCursorIndex+1);
}else{
ed.setSelection(currentCursorIndex-1);
}
}else{
ed.setSelection(length);
}
}catch (Exception e){
}
}
});
ref.get().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){
ed.setInputType(InputType.TYPE_NULL);
}
else{
Class<EditText> cls = EditText.class;
Method setShowSoftInputOnFocus;
try {
setShowSoftInputOnFocus = cls.getMethod(methodName, boolean.class);
setShowSoftInputOnFocus.setAccessible(true);
setShowSoftInputOnFocus.invoke(ed, false);
} catch (NoSuchMethodException e) {
ed.setInputType(InputType.TYPE_NULL);
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
这里我们的EditText是集合的形式传递过来的,因为界面上可能有多个EditText,我们通过弱引用的形式传递过来了,因为会导致内存泄漏,当然,你在Activity的onDestory()里面去把AutoPopLayout的context置为null,也是可以的。上面隐藏的方式我也是从网友那里借鉴过来的,不同版本都有不同的处理方式。
隐藏系统的软键盘之后,还需要处理光标,虽然你可以设置ed.setCursorVisible(true),但是它总是会显示在EditText的最前面,不管你有没有输入文字,不信,你可以尝试下。那怎么办呢,我们貌似只有监听EditText的addText的方法了:
ed.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) {
String value = s.toString();
int length = value.length();
int index = getEditTextCursorIndex(ed);
try{
if(currentCursorIndex < length){
if(addOrde == ADD){
ed.setSelection(currentCursorIndex+1);
}else{
ed.setSelection(currentCursorIndex-1);
}
}else{
ed.setSelection(length);
}
}catch (Exception e){
}
}
});
在这里我们处理了一些可能,主要为了光标能够随着文字而动,但是问题又来了,如果用户假如不小心输漏了一位怎么办?当用户把光标移动到某个位置的时候,我们还是处理下这个Bug。
softInputBoard.setOnChoosePayWayListener(new SoftInputBoard.ChoosePayWayListener() {
@Override
public void chooseWay(String value) {
try{
String preStr = ed.getText().toString();
//银行帐号
if(!TextUtils.equals(value,"delete")){
//长按删除
if(TextUtils.equals(value,"xdelete")){
ed.setText("");
currentCursorIndex = 0;
return;
}
addOrde = ADD;
currentCursorIndex = getEditTextCursorIndex(ed);
insertText(ed,value);
preStr = ed.getText().toString();
ed.setText(preStr);
}else{
if(!TextUtils.isEmpty(preStr)){
addOrde = DE;
currentCursorIndex = getEditTextCursorIndex(ed);
deleteText(ed);
preStr = ed.getText().toString();
ed.setText(preStr);
}
}
}catch (Exception e){
}
}
});
}
});
在SoftInputBoard传递给我们值的时候,我们之前要确定下光标的位置,然后在去设置,当然,删除亦是如此。当然还有很多细节没有描述,虽然看起来有点烦躁,幸运的是,我把他们全封装好了,直接在MainActivity调用就ok了:
public class MainActivity extends AppCompatActivity {
private AutoPopLayout autoPopLayout;
private EditText et1;
private EditText et2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
autoPopLayout = (AutoPopLayout)findViewById(R.id.autoPopLayout);
et1 = (EditText)findViewById(R.id.auto_et1);
et2 = (EditText)findViewById(R.id.auto_et2);
List<EditText> ets = new ArrayList<EditText>();
ets.add(et1);
ets.add(et2);
autoPopLayout.hideSoftInputMethod(ets,new WeakReference<Activity>(this));
}
@Override
protected void onDestroy() {
try{
autoPopLayout.destroyAutoPopLayout();
}catch (Exception e){
}
super.onDestroy();
}
}
怎么样,是不是很简单!!!下面看看效果图: