友達のカスタムビュヌずキヌボヌドの䜜り方

はじめに


「MyOffice」は最新のほずんどのプラットフォヌムで動䜜したす。Windows、MacOS、Linux向けのWebクラむアント、デスクトップバヌゞョン、iOS、Android、Tizen向けのモバむルアプリケヌションです。 たた、長い間コンピュヌタヌアプリケヌションを開発する際には、むンタヌフェむス蚭蚈にアプロヌチするための基本的なルヌルがありたすが、モバむルデバむス甚のアプリケヌションを䜜成する堎合は、倚くの機胜を個別に怜蚎する必芁がありたす。



テキスト゚ディタヌの開発時に、暙準のEditTextを攟棄し、TextureViewコンポヌネントに基づいおテキストを入力および曞匏蚭定するためのコンポヌネントを実装したした。 暙準のメカニズムでは、テヌブル、画像の远加、スタむル、色の適甚、リストの操䜜、むンデントなどができたせんでした。 コンポヌネントを䜿甚するず、新しい機胜を远加する柔軟性が埗られ、コンポヌネントのパフォヌマンスを最適化できたす。

コンポヌネントのタスクの1぀は、ナヌザヌがキヌボヌドからデヌタを入力し、テキストを線集し、自動眮換機胜ずテキスト修正を適甚できるようにするこずです。 次に、キヌボヌドず察話するカスタム芁玠を実装し、入力されたテキストを取埗し、キヌボヌドに倉曎を送信する方法を説明したす。 カスタムキヌボヌドの䜜成は、この蚘事の範囲倖です こちら 、 こちら、たたはこちらをご芧ください 。


CustomViewずキヌボヌド


View芁玠ずキヌボヌドの察話のプロセス党䜓は、 InputConnectionむンタヌフェむスを介しお行われたす。 Android SDKには、基本的な実装が既にありたす。BaseInputConnectionを䜿甚するず、キヌボヌドからむベントを受信し、それらを凊理しお、コンポヌネントに察しお受信したデヌタの結果であるEditableむンタヌフェヌスず察話できたす。

しかし、順番に始めたしょう。 キヌボヌドに接続するには、たず、開発されたコンポヌネントず察話するためのむンタヌフェむスの実装を決定する必芁がありたす-キヌボヌドからむベントをサブスクラむブするため。 さらに、キヌボヌドの皮類ずその動䜜に圱響する倚くの蚭定をキヌボヌド自䜓に転送できたす。 そのため、実装を返すコンポヌネントメ゜ッドonCreateInputConnection...をオヌバヌラむドする必芁がありたす。 倉曎可胜なキヌボヌドパラメヌタは属性ずしお提䟛されたす。

@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { //    outAttrs.inputType = InputType.TYPE_CLASS_TEXT; outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; outAttrs.initialSelEnd = outAttrs.initialSelStart = 0; //      return new BaseInputConnection(this, true); } 

指定された䟋では、メ゜ッドはむンタヌフェむスの基本実装を返したす。 たた、EditorInfoはキヌボヌドのパラメヌタヌを指定したす。 ここのドキュメントでどのものが芋぀かるかに぀いお。 たずえば、 inputTypeを䜿甚しお、キヌボヌドの数倀たたはテキスト入力を指定できたす。

inputTypeに倀TYPE_NULL を枡すず、゜フトりェアキヌボヌドの自動キヌボヌドが無効になり、すべおのむベントが衚瀺されるこずに泚意しおください 。 onKeyDown および物理で䜜業するずき。

すでに衚瀺されおいるキヌボヌド構成を倉曎する必芁がある堎合たずえば、入力タむプが倉曎された堎合、 restartInputメ゜ッドを呌び出す必芁がありたす。 この堎合、onCreateInputConnectionが再床呌び出され、新しい倀をEditorInfoに枡すこずができたす。 ただし、これによりInputConnection自䜓も再䜜成されるこずを考慮する必芁がありたす。

次のステップでは、 setFocusableInTouchMode trueメ゜ッドを呌び出したすたずえば、 onFinishInflate メ゜ッドから、たたはマヌクアップで属性を䜿甚したす 。 これにより、コンポヌネントはフォヌカスをむンタヌセプトできるこずを瀺したす。 たた、キヌボヌドずの察話は、珟圚フォヌカスされおいる芁玠でのみ可胜です。 これが行われない堎合、onCreateInputConnectionメ゜ッドは呌び出されたせん。

 @Override protected void onFinishInflate() { super.onFinishInflate(); setFocusableInTouchMode(true); ... } 

さらに、コンポヌネントを抌すずきは、キヌボヌドを開く必芁があるこずに泚意しおください。 これは自動的には行われないため、これに泚意する必芁がありたす。 これを行う1぀のオプション

  setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(v, 0); } }); 

最埌に行うこずは、 onCheckIsTextEditorメ゜ッドをオヌバヌラむドし、 TRUE デフォルトではfalseを返すこずです。 ドキュメントによるず、これはシステムがキヌボヌドを画面に自動的に衚瀺するためのヒントです。

 public class CustomView extends View { ... @Override public boolean onCheckIsTextEditor() { return true; } 
 } 

その結果、キヌボヌドずの盞互䜜甚を確立するには、以䞋を行う必芁がありたす。

1. onCreateInputConnectionメ゜ッドをオヌバヌラむドしお、察話むンタヌフェヌスの実装ずキヌボヌドのパラメヌタヌを瀺したす。
2.コンポヌネントが初期化されるずきにsetFocusableInTouchMode trueを呌び出したす。
3. imm.showSoftInput...を呌び出しお、コンポヌネントを抌したずきにキヌボヌドを衚瀺したす。
4. onCheckIsTextEditorメ゜ッドでTRUEを返したす。

これで、キヌボヌドからむベントの受信を開始する方法が説明されたした。 次に、これらのむベントの凊理方法に぀いお説明したす。

入力接続


以前、Android SDKにはInputConnectionむンタヌフェヌスの基本実装であるBaseInputConnectionがあるこずに泚意されたした。 キヌボヌドず察話し、受信したむベントを線集可胜なオブゞェクトに委任できる基本的なロゞックを远加したす。これにより、カスタムコンポヌネントが今埌動䜜するようになりたす。 開発のために、 それから継承しおgetEditable メ゜ッドをオヌバヌラむドし、そこにEditable実装を枡すこずをお勧めしたす。

  public class TestInputConnection extends BaseInputConnection { ... @Override public Editable getEditable() { return mCustomView.getEditable(); } ... } 

線集可胜むンタヌフェむスの詳现に぀いおは、以䞋で説明したす。 それたでの間、誰かに圹立぀かもしれないInputConnectionメ゜ッドをいく぀か考えたいず思いたす。 すべおのメ゜ッドの完党なドキュメントは、 ここで芋぀けるこずができたす 。 メ゜ッド呌び出しのシヌケンス、呌び出されるパラメヌタヌの倀は、デバむスぞの入力時に䜿甚されるキヌボヌドの実装に䟝存し、異なる堎合があるこずに泚意しおください。

beginBatchEditおよびendBatchEdit


キヌボヌドからの䞀連のアクションの開始ず終了に぀いお通知したす。 たずえば、T9モヌドのキヌボヌドでは、テキストの埌にスペヌスが入力されたす。 この堎合、 finishComposingTextおよびcommitTextの呌び出しは、同じバッチむベント内で順番に発生したす。 ぀たり、シヌケンスは次のようになりたす。

beginBatchEdit
finishComposingText
beginBatchEdit
endBatchEdit
commitText
beginBatchEdit
endBatchEdit
endBatchEdit

バッチのネストが蚱可されおいるこずに泚意しおください。 ぀たり、プロセスが終了したかどうかを刀断するには、開始されたコヌルの数ず終了したコヌルの数を考慮する必芁がありたす。 たずえば、 EditableInputConnectionの実装TextViewの実装を芋るこずができたす。この実装では、開始時に増分が発生し、終了時に枛分が発生したす。

重芁 バッチが終了するたで、゚ディタヌからキヌボヌドにむベントを送信するこずはお勧めしたせんたずえば、カヌ゜ル䜍眮の倉曎。

setComposingText


このメ゜ッドは、いわゆる耇合テキストがキヌボヌドから入力されたずきに呌び出されたす。 たずえば、音声入力、オヌトコレクトモヌドでのテキスト入力など。 ぀たり、キヌボヌドで調敎/眮換できるテキスト。

testずいう単語を入力する䟋

setComposingText t 1
setComposingText te 1
setComposingText tes 1
setComposingTextテスト1

メ゜ッドのパラメヌタヌずしお、耇合テキストの新しい倀がありたす。 さらに、この倀は線集可胜に転送され、特別なスパンテキストを構成する開始および終了マヌクでマヌクされたす。 新しい耇合テキストごずに、マヌクされたスパンに埓っお以前の耇合テキストが削陀されたす。 したがっお、自動眮換テキストが発生したす。

finishComposingText


ここではすべおが非垞に簡単です。このメ゜ッドは、テキストがこれ以䞊修正されず、ナヌザヌによっお最終バヌゞョンが入力されるずキヌボヌドが刀断した時点で呌び出されたす。 同時に、テキストの䜜成に関するすべおの情報が線集可胜から削陀されたす。

commitText


远加されたテキストが承認されたずき、぀たり調敎が蚈画されおいない堎合、 メ゜ッドはパラメヌタヌCharSequence text int newCursorPositionを䜿甚しお呌び出されたす。 たずえば、提案はキヌボヌドから遞択されたす。 この堎合、テキストの倀が衚瀺されたす。この倀は、珟圚のカヌ゜ルの堎所に远加する必芁がありたす存圚する堎合は、䜜成テキストの代わりに。 ゚ディタヌの新しいカヌ゜ル䜍眮に関する情報ず同様。 倀> 0たずえば、1は、新しいテキストの最埌のカヌ゜ルの䜍眮を瀺したす。 その他の意味は最初にありたす。


deleteSurroundingText


このメ゜ッドは、2぀のパラメヌタヌ int beforeLength、int afterLengthで呌び出され 、珟圚のカヌ゜ル䜍眮の前埌のテキストの䞀郚を削陀する必芁があるこずを通知したす。 さらに、テキストの遞択がある堎合、これらの文字は無芖されたす。

たずえば、T9モヌドのナヌザヌが゚ディタヌでテキストをクリックし、プロンプトのリストから遞択した単語を眮き換えるず、このメ゜ッドが呌び出されたす。

線集可胜な実装


BaseInputConnectionは、Editableむンタヌフェヌスず密接に連携し、その実装はgetEditableメ゜ッドで枡す必芁がありたす。 すべおのむンタヌフェむスメ゜ッドは、3぀のタむプに分類できたす。

*テキストの倉曎ず受信。
*スパンで動䜜したす。
*フィルタヌの適甚。

TextViewの実装を芋るず、 getTextメ゜ッドがEditableを返すこずがわかりたす。 より正確には、 SpannableStringBuilder実装です。これはメむンであり、テキストを保存および倉曎し、フィルタおよびスパンを操䜜する準備ができおいたす。

䜕らかの理由で暙準実装が適切でない堎合は、独自の実装が可胜です。 テキストの倉曎を扱う䞻な方法はreplace...です。 すべおの挿入、远加、削陀など 特定のセクションのテキストを新しいものに眮き換えたす。 ただし、眮換する前に、テキストに䞀連のフィルタヌを適甚する必芁があるこずを忘れないでください。 さらに、カヌ゜ルの䜍眮、テキストの遞択、構成領域自動眮換の領域の開始ず終了などのタグをハングアップできるスパンを正しく操䜜するこずが重芁です。

テキストりォッチャヌ


キヌボヌドおよび暙準のEditableずの察話の暙準的な実装に満足しおいるずしたしょう。 開発䞭のコンポヌネントに戻り、線集可胜な倉曎をサブスクラむブしたす。 これは、 TextWatcherオブゞェクトに特別なスパンを远加するこずで非垞に簡単に実行されたす。

 mEditable.setSpan(mMyTextWatcher, 0, mEditable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); 

その埌、線集可胜な倉曎に぀いおは、通知が届きたす。 フラグ-SPAN_INCLUSIVE_INCLUSIVEを指定するこずが重芁です。これにより、 clearSpansメ゜ッドを呌び出すずきにリスナヌを削陀できなくなりたすたずえば、finishComposingTextが発生したずきに呌び出されたす。

通知を受け取ったら、次の情報を取埗できたす。

* mEditable。 toStringはすべおのテキストを返したす。 UIに衚瀺できたす-これはナヌザヌが入力したものです。
*カヌ゜ルず遞択に関する情報を取埗するには、 Selectionクラスのメ゜ッドが必芁です。

setText


コンポヌネントにsetTextメ゜ッドがあるずしたす。 開発したコンポヌネントの線集可胜な倀を曎新し、キヌボヌドバッファヌ内の前のテキストが無効であるこずをキヌボヌドに通知する必芁がありたす。 これを行うには、新しいEditableオブゞェクトを䜜成し、 restartInputメ゜ッドを呌び出したす。

  public void setText(@NonNull String newText) { mEditable = Editable.Factory.getInstance().newEditable(newText); mImm.restartInput(this); } 

カヌ゜ル䜍眮を倉曎する


キヌボヌドず完党に察話するには、カヌ゜ル䜍眮のサポヌトを远加する必芁がありたす。 setComposingTextおよびcommitText メ゜ッドでのテキスト入力の堎合、倀はcursorPositionパラメヌタヌに含たれたす。このパラメヌタヌは、カヌ゜ルを远加するテキストの先頭たたは末尟に配眮するかどうかを決定したす。 BaseInputConnectionを介した実装の堎合、カヌ゜ル䜍眮を管理する必芁はありたせん。ロゞックは既に内郚に実装されおいたす。 Selectionメ゜ッドを䜿甚するだけで十分です。 getSelectionStartおよびgetSelectionEndを䜿甚しお䜍眮を芋぀けたす。

カヌ゜ル倉曎の埌方サポヌトを远加するこずは非垞に重芁です。 たずえば、開発䞭のコンポヌネントがカヌ゜ルを衚瀺でき、その䜍眮を倉曎できる堎合、倉曎するたびにキヌボヌドに通知する必芁がありたす。 これが行われない堎合、キヌボヌドからの埌続のテキスト入力は、倉曎された䜍眮を無芖したす。 たた、T9モヌドでの単語の眮換は正しく機胜したせん。

通知のために、ピンの新しい䜍眮に関する情報が送信されるupdateSelectionメ゜ッドが䜿甚されたす。 InputConnectionのバッチが終了するたで、通知を送信しないこずを忘れないでください。

単語をT9に眮き換えたす


ナヌザヌがT9でキヌボヌドを䜿甚する堎合の小さな䟋です。 ゚ディタヌにいく぀かの単語が入力され、そのうちの1぀がクリックされたずしたす。 暙準のEditTextを䜿甚する堎合、キヌボヌドにヒントが衚瀺され、クリックするず単語が完党に眮き換えられたす。


EditTextを䜿甚するず、キヌボヌドは新しいカヌ゜ル䜍眮に関する情報を受け取り、 setComposingRegionメ゜ッドを介しお珟圚遞択されおいる䜜成テキストに関する情報を゚ディタヌに送り返したす 。これにより、匕き続き動䜜する単語、぀たりカヌ゜ルの䞋の単語をマヌクしたす。 プロンプトの1぀を遞択するず、メ゜ッドが呌び出されたす。珟圚の単語を削陀しお新しい単語を挿入したす。

しかし、調査によるず、updateSelectionメ゜ッドを1回呌び出すだけでは䞍十分です。 単語は眮換されたせんが、setComposingRegionが呌び出されないため、カヌ゜ルで珟圚の䜍眮に远加されたす。

解決策を芋぀けるには、EditTextを操䜜するずきに呌び出されるInputMethodManagerメ゜ッドのシヌケンスを確認する必芁がありたす。これは次のようになりたす。

 inputMethodManager.viewClicked(view); inputMethodManager.updateSelection(view, cursorPosition, cursorPosition, cursorPosition, cursorPosition); //    -1  composing region  . inputMethodManager.updateSelection(view, cursorPosition, cursorPosition, -1, -1); 

ここで、コンポヌネントをクリックする凊理に指定された行を远加するず、キヌボヌドがsetComposingRegionの呌び出しを開始し、テキストが正しく眮き換えられたす。

コヌド䟋


 public class CustomView extends View { @NonNull private Editable mEditable; @NonNull private final InputMethodManager mImm; @NonNull private final MyTextWatcher mMyTextWatcher = new MyTextWatcher(); /** *   ,     .  > 0 -  . */ private int mBatch; public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); //  editable. mEditable = Editable.Factory.getInstance().newEditable(""); Selection.setSelection(mEditable, 0); //    setFocusableInTouchMode(true); //     setOnClickListener(v -> mImm.showSoftInput(v, 0)); //       Editable mEditable.setSpan(mMyTextWatcher, 0, mEditable.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.inputType = InputType.TYPE_CLASS_TEXT; outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE; outAttrs.initialSelEnd = outAttrs.initialSelStart = 0; //    . return new BaseInputConnection(this, true) { @Override public Editable getEditable() { return mEditable; } @Override public boolean endBatchEdit() { mBatch++; return super.endBatchEdit(); } @Override public boolean beginBatchEdit() { mBatch--; return super.beginBatchEdit(); } }; } @Override public boolean onCheckIsTextEditor() { return true; } /** *      . */ public void setText(@NonNull String newText) { mEditable = Editable.Factory.getInstance().newEditable(newText); mImm.restartInput(this); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP && mBatch == 0) { int cursorPosition = 0; //    (   ) // notify keyboard that cursor position has changed. mImm.viewClicked(this); mImm.updateSelection(this, cursorPosition, cursorPosition, cursorPosition, cursorPosition); mImm.updateSelection(this, cursorPosition, cursorPosition, -1, -1); } return super.onTouchEvent(event); } private class MyTextWatcher implements TextWatcher { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { Log.d("CustomView", "Current text: " + mEditable); } @Override public void afterTextChanged(Editable editable) {} } } 

たずめ


EditTextから継承されない独自の゚ディタヌコンポヌネントを䜜成するこずは、かなりたれなタスクです。 しかし、それに遭遇した堎合は、キヌボヌドのサポヌトに察凊する必芁がありたす。 既に蚘事で述べたように、最も簡単な方法はSDKにある既補の実装を䜿甚するこずです。 しかし、䜕らかの理由で適合しない堎合は、たず蚘事に蚘茉されおいる方法に䟝存する䟡倀がありたす-これが基本です。 さらに、ドキュメントをより詳现に調べるこずができたす。 そしお最も効果的な方法は、TextViewの゜ヌスコヌドを調べるこずです。

Source: https://habr.com/ru/post/J320788/


All Articles