Reputation: 3564
I'm developing a game that uses the keyboard for input, but I want to display ONLY the alpha characters, no digits or special symbols, when the user touches the input field. Right now, the app looks like the following when the keyboard is open:
How can I replace the soft keyboard with a custom keyboard when the "Type some letters" EditText has focus?
Upvotes: 0
Views: 708
Reputation: 56
use android:inputType
and android:digits
in your EditText
xml.
<EditText
android:id="@+id/plain_text_input"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:inputType="text"
android:digits="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "/>
This will allow only alphabetic characters and spaces to be inserted
You can also check if the user tried to enter a number or some other non-alphabetical character, and then display a warning to him. Then, you replace it with nothing using regex
[^\p{L}\p{Nd}]+
- this matches all characters that are neither letters nor digits.
EditText text = (EditText) findViewById(R.id.plain_text_input);
text.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
if(s.matches(".*\\d.*")){ // contains a number
text.setError("Sorry, numbers are not allowed");
text.replaceAll("[^\\p{L}\\p{Nd}]+", "");
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
Alternatively, you can create your own InputFilter that would filter out any non-alpha characters.
InputFilter input = new InputFilter() {
public CharSequence filter
(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String filtered = "";
for (int i = start; i < end; i++) {
char character = source.charAt(i);
if (!Character.isWhitespace(character) && Character.isLetter(character)) {
filtered += character;
}
}
return filtered;
}
};
text.setFilters(new InputFilter[]{input});
I hope this helps you in some way. ^ - ^
Since they deprecated KeyboardView
and the other related classes in api 29, disregard my last comment on that class, as they will remove it in the future.
The best way to create a custom keyboard is to use the InputMethodService
class, creating your own custom xml
layout.
However, as you are developing a game that uses a simpler input method, which would not be useful for the user anywhere outside the game, (messaging apps for example), consider this.
Remember that this solution does not reproduce all the action that a user normally performs with the keyboard. You can easily modify it to suit your needs.
1st create a keyboard.xml
layout. You can modify this example if you want.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyboard_parent"
android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"
android:focusable="true" android:clickable="true"
android:background="@android:color/white"
android:visibility="gone">
<LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="50dp">
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_q"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_w"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_e"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_r"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_t"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_y"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_u"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_i"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_o"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_p"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="50dp"
android:paddingStart="8dp" android:paddingEnd="8dp">
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_a"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_s"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_d"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_f"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_g"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_h"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_j"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_k"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_l"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_ç"/>
</LinearLayout>
<LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="50dp">
<ImageView
style="@style/action"
android:layout_weight="1"
android:id="@+id/keyboard_action_all_caps"
android:tint="@android:color/darker_gray"
app:srcCompat="@android:drawable/ic_menu_upload"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_z"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_x"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_c"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_v"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_b"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_n"/>
<TextView style="@style/alpha" android:id="@+id/keyboard_abc_m"/>
<ImageView
style="@style/action"
android:layout_weight="1"
android:id="@+id/keyboard_action_delete"
android:tint="@android:color/darker_gray"
app:srcCompat="@android:drawable/ic_input_delete"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<ImageView
style="@style/action"
android:id="@+id/keyboard_action_something"
android:layout_width="56dp"
android:layout_height="match_parent"
app:srcCompat="@android:drawable/ic_menu_sort_alphabetically"/>
<Button
android:id="@+id/keyboard_action_space"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
app:backgroundTint="@android:color/black"/>
<ImageView
style="@style/action"
android:layout_width="56dp"
android:layout_height="match_parent"
android:id="@+id/keyboard_action_send"
app:srcCompat="@android:drawable/ic_menu_send"/>
</LinearLayout>
</LinearLayout>
Where @style/alpha
and @style/action
<style name="alpha">
<item name="android:layout_weight">1</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">match_parent</item>
<item name="android:text">"A"</item>
<item name="android:textSize">8pt</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center_vertical</item>
</style>
<style name="action">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center_vertical</item>
<item name="android:scaleType">center</item>
<item name="android:padding">4dp</item>
<item name="android:tint">@android:color/black</item>
</style>
2nd create a constructor for your keyboard.
public class MyKeyboard {
private Context context;
private boolean is_all_caps_single = false;
private boolean is_all_caps_double = false;
public MyKeyboard(Context c, View container, boolean is_reference) {
this.context = c;
if (is_reference) {
keyboard = container.findViewById(R.id.keyboard_parent);
initializeComponents();
} else {
keyboard = View.inflate(context, R.layout.keyboard, null);
initializeComponents();
((ViewGroup) container).removeAllViews();
((ViewGroup) container).addView(keyboard);
}
}
private View keyboard;
private TextView[] characters;
private ImageView all_caps;
@SuppressLint("ClickableViewAccessibility")
private void initializeComponents() {
int[] ids = {
R.id.keyboard_abc_q, R.id.keyboard_abc_w, R.id.keyboard_abc_e, R.id.keyboard_abc_r, R.id.keyboard_abc_t, R.id.keyboard_abc_y, R.id.keyboard_abc_u, R.id.keyboard_abc_i, R.id.keyboard_abc_o, R.id.keyboard_abc_p,
R.id.keyboard_abc_a, R.id.keyboard_abc_s, R.id.keyboard_abc_d, R.id.keyboard_abc_f, R.id.keyboard_abc_g, R.id.keyboard_abc_h, R.id.keyboard_abc_j, R.id.keyboard_abc_k, R.id.keyboard_abc_l, R.id.keyboard_abc_ç,
R.id.keyboard_abc_z, R.id.keyboard_abc_x, R.id.keyboard_abc_c, R.id.keyboard_abc_v, R.id.keyboard_abc_b, R.id.keyboard_abc_n, R.id.keyboard_abc_m
};
String[] chrs =
{
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p",
"a", "s", "d", "f", "g", "h", "j", "k", "l", "ç",
"z", "x", "c", "v", "b", "n", "m"
};
characters = new TextView[ids.length];
for (int i = 0; i < ids.length; i++) {
characters[i] = keyboard.findViewById(ids[i]);
characters[i].setText(chrs[i]);
}
all_caps = keyboard.findViewById(R.id.keyboard_action_all_caps);
final GestureDetector all_caps_gesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
is_all_caps_double = false;
is_all_caps_single = !is_all_caps_single;
for (TextView tw: characters) {
tw.setAllCaps(is_all_caps_single);
}
setForegroundColor(all_caps, is_all_caps_single ? Color.BLACK : Color.GRAY);
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
is_all_caps_single = true;
is_all_caps_double = !is_all_caps_double;
for (TextView tw: characters) {
tw.setAllCaps(is_all_caps_double);
}
setForegroundColor(all_caps, Color.BLUE);
return super.onDoubleTap(e);
}
});
all_caps.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
all_caps_gesture.onTouchEvent(event);
return true;
}
});
for (int i = 0; i < ids.length; i++) {
final int finalI = i;
characters[i].setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String ss = characters[finalI].getText().toString();
if (is_all_caps_double) {
ss = ss.toUpperCase();
} else if (is_all_caps_single) {
is_all_caps_single = false;
ss = ss.toUpperCase();
for (TextView tw: characters) {
tw.setAllCaps(false);
}
setForegroundColor(all_caps, Color.BLACK);
} else {
ss = ss.toLowerCase();
}
popup(characters[finalI], ss);
edittext.getText().insert(edittext.getSelectionStart(), ss);
}
});
}
keyboard.findViewById(R.id.keyboard_action_space).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
edittext.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE));
}
});
final GestureDetector del_gesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
is_fast_delete = true;
fastDelete(edittext);
super.onLongPress(e);
}
});
keyboard.findViewById(R.id.keyboard_action_delete).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
is_fast_delete = false;
delete();
}
del_gesture.onTouchEvent(event);
return true;
}
});
keyboard.findViewById(R.id.keyboard_action_send).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
edittext.getText().insert(edittext.getSelectionStart(), "\n");
}
});
keyboard.findViewById(R.id.keyboard_action_something).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("log", "Make something here");
}
});
}
private EditText edittext;
public void show(EditText focus) {
this.edittext = focus;
keyboard.animate().y(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
keyboard.setVisibility(View.VISIBLE);
}
});
}
public void hide() {
if (isVisible()) {
edittext.clearFocus();
keyboard.animate().y(keyboard.getHeight()).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
keyboard.setVisibility(View.GONE);
}
});
}
}
public boolean isVisible() {
return keyboard.getVisibility() == View.VISIBLE;
}
// show popup when typing
private void popup(View vw, String s) {
final PopupWindow popup = new PopupWindow(context);
RelativeLayout layout = new RelativeLayout(context);
layout.setBackgroundColor(Color.BLACK);
TextView tw = new TextView(context);
tw.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
tw.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
tw.setTextSize(24);
tw.setTextColor(Color.WHITE);
tw.setText(s);
layout.removeAllViews();
layout.addView(tw);
popup.setContentView(layout);
popup.setHeight(120);
popup.setWidth(72);
popup.setOutsideTouchable(false);
popup.setFocusable(false);
layout.animate().alpha(1 f).setDuration(0).setStartDelay(100).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
popup.dismiss();
}
});
popup.showAsDropDown(vw, 0, -180);
}
// change color of an icon
private void setForegroundColor(ImageView v, int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
v.getDrawable().setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_ATOP));
} else {
v.getDrawable().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
}
}
private void delete() {
edittext.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
}
private void fastDelete(final View vw) {
final AlphaAnimation n = new AlphaAnimation(1 f, 1 f);
n.setRepeatCount(1000);
n.setDuration(200);
n.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
repeat_count = repeat_count + 1;
if (is_fast_delete) {
if (repeat_count == 5) {
n.setDuration(100);
} else if (repeat_count >= 10) {
n.setDuration(5);
}
delete();
} else {
n.cancel();
vw.getAnimation().cancel();
vw.clearAnimation();
n.setAnimationListener(null);
repeat_count = 0;
}
}
});
vw.startAnimation(n);
}
private boolean is_fast_delete = false;
private int repeat_count = 0;
}
3rd Initialize your keyboard and manually deal with the life cycle of your activity.
public class YourActivity extends AppCompatActivity {
private MyKeyboard keyboard;
private EditText edittext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.one);
keyboard = new MyKeyboard(this, findViewById(R.id.keyboard_container), true);
//if true, the layout must include the keyboard.xml manually
//else, the constructor will include keyboard.xml programatically
edittext = findViewById(R.id.edittext);
edittext.setShowSoftInputOnFocus(false); // not show system keyboard
edittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) { keyboard.show(edittext); }
else { keyboard.hide(); }
}
});
}
@Override
public void onBackPressed() {
if (keyboard.isVisible()) {
keyboard.hide();
} else {
super.onBackPressed();
}
}
}
Finally, your main activity layout should look like this
<?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"
android:animateLayoutChanges="true"
android:focusableInTouchMode="true"
android:focusable="true"
android:clickable="true">
<EditText
android:id="@+id/edittext"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_above="@+id/keyboard_container"
android:inputType="text|textNoSuggestions"
android:background="@android:color/white"
android:hint="@string/app_name"/>
<RelativeLayout
android:id="@+id/keyboard_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@android:color/white">
<include
layout="@layout/keyboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
</RelativeLayout>
Add focusableInTouchMode="true"
focusable="true"
and clickable="true"
to all Views where the user interacts, whenever you want the keyboard to be closed. This happens whenever Edittext loses focus
The end result is a keyboard without numbers and special characters.
I used the standard android icons for the example, you can replace them with others.
As I said at the beginning of this edition, this solution does not reproduce all the action that a user normally performs with the keyboard, you can improve it in the best possible way.
I hope this helps you in some way. ^-^
Upvotes: 0
Reputation: 3564
I ended up creating a layout of the keyboard I wanted and then I disabled the focus for the EditText:
<EditText
android:id="@+id/typed_word"
android:focusable="false"
android:maxLength="10"
android:inputType="text"
android:textCursorDrawable="@drawable/custom_cursor"
android:enabled="false"
android:layout_below="@+id/word_list"
android:digits="abcdefghijklmnopqrstuvwxyz"
android:textSize="25sp"
android:textColor="@color/colorPrimaryDark"
android:layout_margin="10dp"
android:hint="@string/type_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
By disabling the focus, I was able to prevent the standard keyboard from being displayed.
Upvotes: 0