How can I use a custom font in the input area of an input method?
Asked Answered
S

4

15

We're developing an app where we need to use a custom font (a Typeface loaded from the app's assets) in an EditText. The Android input method docs state the following:

When input focus moves into or out of an editable text field, Android shows or hides the input method (such as the on-screen keyboard) as appropriate. The system also makes decisions about how your UI and the text field appear above the input method. For example, when the vertical space on the screen is constrained, the text field might fill all space above the input method.

It's the part in bold that is tripping us up. The phrase “the text field might fill...” appears to be misleading, in that the text field that's used is not the EditText that we set up with our custom font. (NOTE: the answers so far all explain how to set a custom font in an EditText. We already are setting a custom typeface in the EditText. Please read the question carefully before answering.) Here's a screenshot with the input method hidden:

the dialog

Here's what happens when the soft keyboard is showing and vertical space is constrained:

constrained vertical space

As you can see, the font in the text field above the input method is not our custom font (I'm guessing it's the system's default Roboto font). This is happening for every soft keyboard we've tried as the input method.

I want to emphasize that when space is constrained, this view above the keyboard is generated internally by the system, not by any of our code.

The main question is: Is there a way (and, if so, what is it?) to control the appearance of this (for lack of better terminology) proxy view—at a minimum to get it to use our custom font?

It would be an added bonus if we could also control the layout and appearance of the entire proxy area above the keyboard (including the "Done" button). We are using a variation of the technique described in this thread to have our activity use a locale different from the one set in the system, but the activity's locale clearly isn't being applied here. (If it were, the button would be on the left and would read "בוצע", as does happen if I change the device's locale to Hebrew. [EDIT: I can get the correct button label by using the android:imeActionLabel attribute on the EditText. However, I don't know how to control the layout directionality.])

EDIT Per request, here's how I'm constructing my dialog (relevant parts excerpted from a DialogFragment):

public Dialog onCreateDialog(Bundle savedInstanceState) {
    final Dialog dlg = new Dialog(getActivity(), android.R.style.Theme_Holo_Light_Dialog_NoActionBar);
    dlg.setContentView(R.layout.edit_note_dialog);
    mAnimator = (ViewAnimator) dlg.findViewById(R.id.animator);
    final Typeface hebrew = SystemData.getHebrewFont();
    mNoteEditor = (EditText) dlg.findViewById(R.id.note_field);
    mNoteEditor.setTypeface(hebrew);

    // etc. (setting fonts for other elements, adding listeners, etc.)
}

And here's the layout:

<?xml version="1.0" encoding="utf-8"?>
<ViewAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/animator"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:paddingTop="10dp"
        android:text="@string/loading" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:background="@android:color/black"
            android:gravity="center"
            android:text="@string/edit_note_title"
            android:textColor="@android:color/white"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/citation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/title"
            android:layout_centerHorizontal="true"
            android:background="@android:color/black"
            android:gravity="center"
            android:textColor="@android:color/white"
            android:textSize="16sp" />

        <LinearLayout
            android:id="@+id/actions"
            style="?android:attr/buttonBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_margin="5dp" >

            <Button
                android:id="@+id/cancel"
                style="?android:attr/buttonBarButtonStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:gravity="center"
                android:minWidth="32dp"
                android:text="@string/cancel"
                android:textSize="@dimen/nav_interior_item_size" />

            <Button
                android:id="@+id/close"
                style="?android:attr/buttonBarButtonStyle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:gravity="center"
                android:minWidth="32dp"
                android:text="@string/save"
                android:textSize="@dimen/nav_interior_item_size" />
        </LinearLayout>

        <Button
            android:id="@+id/undo"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentStart="true"
            android:layout_margin="5dp"
            android:text="@string/edit_note_undo"
            android:textSize="@dimen/nav_interior_item_size" />

        <Button
            android:id="@+id/redo"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentEnd="true"
            android:layout_margin="5dp"
            android:text="@string/edit_note_redo"
            android:textSize="@dimen/nav_interior_item_size" />

        <EditText
            android:id="@+id/note_field"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@+id/actions"
            android:layout_below="@+id/citation"
            android:focusableInTouchMode="true"
            android:gravity="top"
            android:hint="@string/edit_note_hint"
            android:imeActionLabel="@string/done"
            android:inputType="textMultiLine|textNoSuggestions"
            android:lineSpacingMultiplier="1.2" />
    </RelativeLayout>

</ViewAnimator>
Sisco answered 10/9, 2014 at 3:51 Comment(7)
do you want to set typface for the hint part in edittext?Oft
Disabling the fullscreen won't be an option I suppose, right?Disillusion
@SmitPatel - No, this has nothing to do with the hint text. It's a problem with the layout that the system generates and displays above the soft keyboard when it decides that there's not enough vertical space there for it to display my layout.Sisco
@Disillusion - I tried setting android:imeOptions=flagNoFullscreen but unfortunately the EditText shrinks down into nothingness and the user can't see what's being entered. Same problem if I use flagNoExtractUi.Sisco
@TedHopp I have created one demo for testing and i found that it's not getting the full screen but not looks like the default Android UI so i kindly suggest you to use the default UI.Oft
@TedHopp Use the flagNoExtractUi and use RelativeLayout as root view of the dialog's custom layout. Set layout_alignParentBottom to true on the EditText. This ensures your EditText is always visible when IME is opened. You can also set ScrollingMovementMethod on EditText to make it's contents scrollable.Obrien
@ManishMulimani - Thanks for the suggestion. However, I can't set layout_alignParentBottom for the EditText because the bottom buttons need to be visible. (It would be satisfactory if they were obscured when the keyboard was showing in landscape mode, but otherwise they need to be there.) Also, the contents are already scrollable.Sisco
K
4

It would be simple if you had an instance of InputMethodService. There is a way to set a theme or even set extracted view.

The problem is you have to create a custom implementation of Input Method and users will have to select it in the system settings.

Update:

You can try with:

    • android:windowSoftInputMode="adjustResize"
    • make the view scrollable

or

    • use IME_FLAG_NO_EXTRACT_UI
    • set an OnEditorActionListener on the EditText to have an action button on the keyboard.
    • create custom layout or hide/show elements when screen's height is too small. For example hide buttons at the bottom and show buttons on right side. It can look similar to the default extract view.

layout listener for the dialog or EditText:

    mViewContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            //unregister the layout listener if needed.
            int heightPixels = mViewContainer.getHeight();
            Resources resources = mContext.getResources();
            int heightDip = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, heightPixels, resources.getDisplayMetrics());
            if (heightDip < MIN_HEIGHT) {
                mBottomButtons.setVisibility(View.GONE);
                mSideButtons.setVisibility(View.VISIBLE);
            } else {
                mBottomButtons.setVisibility(View.VISIBLE);
                mSideButtons.setVisibility(View.GONE);                    
            }
        }
    });

Changing the visibility to GONE will request relayout and the listener will run again what can lead to a loop.

Kirt answered 13/9, 2014 at 17:5 Comment(4)
You're right, writing our own input method service would allow us to control this. So it might be doable, but it sure wouldn't be exactly simple. Also, users like to keep using their favorite keyboard. Also, I think it's impossible to set a custom typeface (one loaded from assets) simply by using a theme. Finally, calling setExtractViewShown is pretty much the same as setting IME_FLAG_NO_EXTRACT_UI, which, as I commented in the answer by Nachi, isn't working for me because the EditText shrinks to nothing when the vertical space is too short, resulting in the user simply typing blind.Sisco
There is no perfect solution. This one with layout listener is 'hacky' and needs more handling for corner cases, but it can be close enough to your requirements.Kirt
The listener approach is interesting. I'll play around with it and see if I can make it work for my needs.Sisco
Well, I haven't had time to do anything with this. (Too many other "high priority" tasks.) But the bounty has expired and this looks really promising, so you get it. Congrats or something!Sisco
P
3

The only way I can see is to disable the fullscreen editing mode for your EditText that automatically kicks in when your EditText is not tall enough.

mNoteEditor.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);

You can fix the dialog resizing issues with

dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

Your dialog layout should probably be enclosed in a ScrollView.

Purificator answered 12/9, 2014 at 15:20 Comment(5)
This is an interesting suggestion, but unfortunately it makes the editor unusable in landscape mode. The EditText shrinks vertically to the point that the text cannot be seen. (I also tried IME_FLAG_NO_FULLSCREEN, which had the same problem.) I'll try playing around with the dialog's layout (I'm not using an AlertDialog) to see if I can fix the problem.Sisco
You should consider adding a minimal example of how you are constructing your dialog (code and xml) to the question.Purificator
I updated my question to show the layout and the relevant parts of the code.Sisco
This is a shot in the dark, but you can try adding android:windowSoftInputMode="adjustPan" to the relevant <activity> definition in your manifestPurificator
I tried that, but then the buttons at the bottom of the dialog are obscured even when there's plenty of vertical space on the screen for them, which our testers hated.Sisco
H
2

I think as other have stated this is unfortunately up the IME and may not be possible through supported APIs.

Beware, hacky and untested solution incoming:

Digging through the Android source code, I found that every IME contains an implementation of AbstractInputMethodService. Android provides a standard implementation (android.inputmethodservice.InputMethodService) which if you look at, has a public setExtractView method, and an onCreateExtractTextView method, which inflates com.android.internal.R.layout.input_method_extract_view.xml, which in turn contains com.android.internal.R.id.inputExtractEditText! So a hacky solution would be to hope that most of your customer's IMEs' InputMethodServices use this same XML (which as far as I can tell, has been in Android for a while), and you can attempt to grab a reference to this EditText by it's ID and change it's typeface. Unfortunately I'm not sure offhand where in the view hierarchy/window the views of an InputMethodService are added, and if they're accessible from your app/activitys process.

Source for InputMethodService: https://github.com/android/platform_frameworks_base/blob/master/core/java/android/inputmethodservice/InputMethodService.java

Source for the layout XML: https://github.com/android/platform_frameworks_base/blob/master/core/res/res/layout/input_method_extract_view.xml

If you really want to get yourself in trouble, perhaps there's a way to get a reference to the IME service itself, and use reflection to alter the extracted view. The standard InputMethodService holds a package-private reference to mExtractEditText :)

I know not the answer you're looking for but perhaps it'll put you in the right direction...or some direction at least.

Hadria answered 15/9, 2014 at 17:53 Comment(1)
I believe that the IME usually runs in its own process. The source for InputMethodManager seems to be set up to communicate to a service through IPC. If that's the case, there will be no opportunity to do all that hacking (inventive though it is!)Sisco
P
2

If you have a single editable element on your Dialog, how about handling orientation changes and

  • in case of Portrait orientation, show the Dialog
  • in case of Landscape orientation, show a fullscreen Fragment with the EditText and Buttons (as others have stated; disable fullscreen editing mode for the EditText), request focus for the EditText, show Keyboard.
  • in case of orientation switching dismiss Dialog / show Fragment with the already entered text (and vice versa)

This might be conceptually good because if the user wishes to enter text, he/she will always end up in a fullscreen editing layout in Landscape orientation. And since there is only one editable view in the original Dialog, you don't explicitly need the Dialog to select anything else to edit (another EditText for example).

Protestation answered 16/9, 2014 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.