eyrolles - programmation android pdf site du zero




Comment capturer une saisie au clavier dans une vue? (3)

J'ai une Vue sous-classée qui affiche le clavier quand il reçoit une 'retouche' dans onTouchEvent. Il le montre en demandant le focus, en récupérant le InputMethodManager, puis en appelant showSoftInput.

Maintenant, j'ai besoin de comprendre comment capturer les lettres tapées du clavier logiciel, comme ils sont pressés . Je ne reçois actuellement une réponse que lorsque le bouton Suivant / Terminé est pressé sur le clavier virtuel.

Voici ma classe:

public class BigGrid extends View {

    private static final String TAG = "BigGrid";

    public BigGrid(Context context) {
        super(context);
        setFocusableInTouchMode(true); // allows the keyboard to pop up on
                                       // touch down

        setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                Log.d(TAG, "onKeyListener");
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    // Perform action on key press
                    Log.d(TAG, "ACTION_DOWN");
                    return true;
                }
                return false;
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        Log.d(TAG, "onTOUCH");
        if (event.getAction() == MotionEvent.ACTION_UP) {

            // show the keyboard so we can enter text
            InputMethodManager imm = (InputMethodManager) getContext()
                    .getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.showSoftInput(this, InputMethodManager.SHOW_FORCED);
        }
        return true;
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        Log.d(TAG, "onCreateInputConnection");

        BaseInputConnection fic = new BaseInputConnection(this, true);
        outAttrs.actionLabel = null;
        outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
        outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
        return fic;
    }

    @Override
    public boolean onCheckIsTextEditor() {
        Log.d(TAG, "onCheckIsTextEditor");
        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawColor(R.color.grid_bg);
        // .
        // .
        // alot more drawing code...
        // .
    }
}

Le clavier s'affiche, mais mon onKeyListener se déclenche uniquement lorsque j'appuie sur le bouton «Suivant» du clavier. J'ai besoin de quel caractère est tapé, afin que je puisse l'afficher dans ma méthode onDraw ().


Il est en fait possible de gérer vous-même les événements clés sans dériver votre vue de TextView.

Pour ce faire, il suffit de modifier votre code d'origine comme suit:

1) Remplacez la ligne suivante dans onCreateInputConnection():

outAttrs.inputType = InputType.TYPE_CLASS_TEXT;

avec celui-ci:

outAttrs.inputType = InputType.TYPE_NULL;

Selon la documentation pour InputType.TYPE_NULL: "Cela doit être interprété comme signifiant que la connexion d'entrée cible n'est pas riche, elle ne peut pas traiter et afficher des choses comme du texte candidat ni récupérer le texte courant, donc la méthode d'entrée doit fonctionner dans un mode "générer des événements clés" limité. "

2) Remplacez la ligne suivante par la même méthode:

BaseInputConnection fic = new BaseInputConnection(this, true);

avec celui-ci:

BaseInputConnection fic = new BaseInputConnection(this, false);

Le faux second argument met le BaseInputConnection en mode "fictif", ce qui est également nécessaire pour que les événements de la clé brute soient envoyés à votre vue. Dans le code BaseInputConnection, vous pouvez trouver plusieurs commentaires comme suit: "seulement si le mode fictif, un événement clé est envoyé pour le nouveau texte et le tampon éditable en cours effacé."

J'ai utilisé cette approche pour que le clavier logiciel envoie des événements bruts à une vue dérivée de LinearLayout (c'est-à-dire, une vue non dérivée de TextView), et peut confirmer que cela fonctionne.

Bien sûr, si vous n'avez pas besoin de définir la valeur IME_ACTION_DONE imeOptions pour afficher un bouton Terminé sur le clavier, vous pouvez simplement supprimer les onCreateInputConnection() et onCheckIsTextEditor() entièrement, et les événements bruts seront alors envoyés à votre vue. par défaut, puisqu'aucune connexion d'entrée capable d'un traitement plus sophistiqué n'aurait été définie.

Mais malheureusement, il ne semble pas y avoir un moyen simple de configurer les attributs EditorInfo sans surcharger ces méthodes et fournir un objet BaseInputConnection, et une fois que vous avez fait cela, vous devrez réduire le traitement effectué par cet objet comme décrit ci-dessus si vous vouloir à nouveau recevoir les événements clés bruts.

AVERTISSEMENT: Deux bogues ont été introduits dans certaines versions récentes du clavier LatinIME par défaut livré avec Android (Google Clavier) qui peuvent influer sur le traitement des événements clavier (comme décrit ci-dessus) lorsque ce clavier est utilisé. J'ai conçu des solutions de contournement du côté de l'application, avec des exemples de code, qui semblent contourner ces problèmes. Pour afficher ces solutions de contournement, consultez la réponse suivante:

Android - ne peut pas capturer retour arrière / supprimer appuyez sur doux. clavier


Il s'avère que j'ai en fait besoin de sous-classer TextView et l'utilisation de addTextChangedListener () pour ajouter ma propre implémentation de TextWatcher afin d'écouter les événements de softkeys. Je ne pouvais pas trouver un moyen de le faire avec une vieille vue plaine.

Une autre chose, pour ceux qui vont essayer cette technique; TextView n'est pas capable de modifier le texte par défaut, donc si vous voulez rendre votre implémentation modifiable (au lieu de sous-classer EditText, ce que je ne voulais pas faire), vous devez également créer un InputConnection personnalisé, comme ceci:

 /**
 * MyInputConnection
 * BaseInputConnection configured to be editable
 */
class MyInputConnection extends BaseInputConnection {
    private SpannableStringBuilder _editable;
    TextView _textView;

    public MyInputConnection(View targetView, boolean fullEditor) {
        super(targetView, fullEditor);
        _textView = (TextView) targetView;
    }

    public Editable getEditable() {
        if (_editable == null) {
            _editable = (SpannableStringBuilder) Editable.Factory.getInstance()
            .newEditable("Placeholder");
        }
        return _editable;
    }

    public boolean commitText(CharSequence text, int newCursorPosition) {
        _editable.append(text);
        _textView.setText(text);
        return true;
    }
}

Ensuite, vous surchargez onCheckisTextEditor et onCreateInputConnection avec quelque chose comme ceci:

 @Override
 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
     outAttrs.actionLabel = null;
     outAttrs.label = "Test text";
     outAttrs.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
     outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;

     return new MyInputConnection(this, true);
 }

 @Override
 public boolean onCheckIsTextEditor() {
     return true;
 }

Après cela, vous devriez avoir une vue qui peut écouter le clavier logiciel et vous pouvez faire ce que vous voulez avec les valeurs d'entrée de la clé.


Selon la documentation , une View (éditeur) reçoit des commandes du clavier (IME) via un documentation et envoie des commandes au clavier via un InputMethodManager .

Je vais montrer le code entier ci-dessous, mais voici les étapes.

1. Faites apparaître le clavier

Comme la vue envoie une commande au clavier, elle doit utiliser un InputMethodManager . Pour le plaisir de l'exemple, nous dirons que lorsque la vue est tapée, le clavier sera affiché (ou masqué s'il est déjà affiché).

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_UP) {
        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
    }
    return true;
}

La vue doit également avoir setFocusableInTouchMode(true) précédemment.

2. Recevoir une entrée du clavier

Pour que la vue reçoive une entrée du clavier, elle doit remplacer onCreateInputConnection() . Cela renvoie l' InputConnection que le clavier utilise pour communiquer avec la vue.

@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
    return new MyInputConnection(this, true);
}

Les outAttrs spécifient le type de clavier demandé par la vue. Ici, nous demandons simplement un clavier de texte normal. Choisir TYPE_CLASS_NUMBER afficherait un pavé numérique (si disponible). Il y a beaucoup d'autres options. Voir EditorInfo .

Vous devez renvoyer une documentation , qui est généralement une sous-classe personnalisée de BaseInputConnection . Dans cette sous-classe, vous fournissez une référence à votre chaîne modifiable, à laquelle le clavier fera des mises à jour. Comme SpannableStringBuilder implémente l'interface Editable , nous l'utiliserons dans notre exemple de base.

public class MyInputConnection extends BaseInputConnection {

    private SpannableStringBuilder mEditable;

    MyInputConnection(View targetView, boolean fullEditor) {
        super(targetView, fullEditor);
        MyCustomView customView = (MyCustomView) targetView;
        mEditable = customView.mText;
    }

    @Override
    public Editable getEditable() {
        return mEditable;
    }
}

Tout ce que nous avons fait ici était de fournir à la connexion d'entrée une référence à la variable de texte dans notre vue personnalisée. BaseInputConnection prendra soin de modifier ce mText . Cela pourrait très bien être tout ce que vous devez faire. Cependant, vous pouvez consulter le code source et voir quelles méthodes disent "implémentation par défaut", en particulier "l'implémentation par défaut ne fait rien". Ce sont d'autres méthodes que vous pouvez vouloir remplacer en fonction de l'implication de votre éditeur. Vous devriez également regarder à travers tous les noms de méthodes dans la BaseInputConnection . Un certain nombre d'entre eux ont des notes à "rédacteurs en chef". Portez une attention particulière à ceux-ci.

Certains claviers n'envoient pas certaines entrées via InputConnection pour une raison quelconque (par exemple, delete , enter et certaines touches du pavé numérique ). Pour ceux que j'ai ajouté un OnKeyListener . Testant cette configuration sur cinq claviers souples différents, tout semblait fonctionner. Les réponses supplémentaires liées à ceci sont ici:

  • Différencier le code de texte du code de contrôle dans Android KeyEvent
  • Besoin de tableau des codes clés pour Android et présentateur
  • Connexion d'entrée pour un clavier de type numérique

Code de projet complet

Voici mon exemple complet pour référence.

MyCustomView.java

public class MyCustomView extends View {

    SpannableStringBuilder mText;

    public MyCustomView(Context context) {
        this(context, null, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setFocusableInTouchMode(true);
        mText = new SpannableStringBuilder();

        // handle key presses not handled by the InputConnection
        setOnKeyListener(new OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_DOWN) {

                    if (event.getUnicodeChar() == 0) { // control character

                        if (keyCode == KeyEvent.KEYCODE_DEL) {
                            mText.delete(mText.length() - 1, mText.length());
                            Log.i("TAG", "text: " + mText + " (keycode)");
                            return true;
                        }
                        // TODO handle any other control keys here
                    } else { // text character
                        mText.append((char)event.getUnicodeChar());
                        Log.i("TAG", "text: " + mText + " (keycode)");
                        return true;
                    }
                }
                return false;
            }
        });
    }

    // toggle whether the keyboard is showing when the view is clicked
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
        }
        return true;
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
        // outAttrs.inputType = InputType.TYPE_CLASS_NUMBER; // alternate (show number pad rather than text)
        return new MyInputConnection(this, true);
    }
}

MyInputConnection.java

public class MyInputConnection extends BaseInputConnection {

    private SpannableStringBuilder mEditable;

    MyInputConnection(View targetView, boolean fullEditor) {
        super(targetView, fullEditor);
        MyCustomView customView = (MyCustomView) targetView;
        mEditable = customView.mText;
    }

    @Override
    public Editable getEditable() {
        return mEditable;
    }

    // just adding this to show that text is being committed.
    @Override
    public boolean commitText(CharSequence text, int newCursorPosition) {
        boolean returnValue = super.commitText(text, newCursorPosition);
        Log.i("TAG", "text: " + mEditable);
        return returnValue;
    }
}

activity_main.xml

<?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.example.editorview.MainActivity">

    <com.example.editorview.MyCustomView
        android:id="@+id/myCustomView"
        android:background="@android:color/holo_blue_bright"
        android:layout_margin="50dp"
        android:layout_width="300dp"
        android:layout_height="150dp"
        android:layout_centerHorizontal="true"
        />

</RelativeLayout>

Il n'y a rien de spécial dans le code MainActivity.java.

S'il vous plaît laissez un commentaire si cela ne fonctionne pas pour vous. J'utilise cette solution de base pour un EditText personnalisé dans une bibliothèque que je suis en train de faire et s'il y a des cas de marge dans lesquels cela ne fonctionne pas, je veux savoir. Si vous souhaitez voir ce projet, la vue personnalisée est here . C'est InputConnection est here .

en relation

  • Comment faire un clavier système personnalisé
  • Comment créer un clavier intégré à l'application




android