FractalBob
FractalBob

Reputation: 3564

Display a stylized keyboard when EditText has the focus

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:

enter image description here

How can I replace the soft keyboard with a custom keyboard when the "Type some letters" EditText has focus?

Upvotes: 0

Views: 708

Answers (2)

I'am Lucy
I'am Lucy

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. ^ - ^


CHANGE


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.

enter image description here

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

FractalBob
FractalBob

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

Related Questions