
äŒæ¥ã®äœæ¥ããã»ã¹ã®ãåå¡ããšã¯ãã³ã©ãã¬ãŒã·ã§ã³ã®ããã®æ©èœããŸããŸãæºåž¯é»è©±ãŸãã¯ã¿ãã¬ããã«ç§»ãããããšãæå³ããŸãã ã¯ãã¹ãã©ãããã©ãŒã ã®ãããžã§ã¯ã管çãµãŒãã¹ã§ããWrikeã®å Žåãã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ã®æ©èœãå®å šã«å®å šã§äŸ¿å©ã§ããããŠãŒã¶ãŒã®äœæ¥ãå¶éããªãããšãéèŠã§ãã ãããŠãã¿ã¹ã¯èšè¿°ã®å ±åç·šéããµããŒããããªããããã¹ããšãã£ã¿ãŒãäœæããã¿ã¹ã¯ãçºçãããšããæ¢åã®WebViewã³ã³ããŒãã³ãã®æ©èœãè©äŸ¡ããŠãç¬èªã®æ¹æ³ã§ç¬èªã®ãã€ãã£ãããŒã«ãå®è£ ããããšã«ããŸããã
ãŸãã補åã®æŽå²ã«ã€ããŠå°ãã Wrikeã®ã³ã¢æ©èœã®1ã€ã¯ãããšããšã¡ãŒã«çµ±åã§ããã ã¿ã¹ã¯ã®æåã®ããŒãžã§ã³ãããé»åã¡ãŒã«ã§äœæããã³æŽæ°ããä»ã®åŸæ¥å¡ãšäžç·ã«äœæ¥ããããšãã§ããŸããã æçŽã®æ¬æã¯åé¡ã®èª¬æã«å€ããããã以äžã®è°è«ã¯ãã¹ãŠãã®ã³ã¡ã³ãã«ãããŸããã
ã¡ãŒã«ã§ã¯HTMLãã©ãŒãããã䜿çšã§ããããã補åã®åæããŒãžã§ã³ã§ã¯CKEditorã䜿çšããŠã¿ã¹ã¯ã®èª¬æãããã«åŠçããŸããã ããããã³ã©ãã¬ãŒã·ã§ã³æåã®ç°å¢ã§ã¯ãããã¯éåžžã«äžäŸ¿ã§ããããã¥ã¡ã³ãã®å šäœãŸãã¯äžéšããããã¯ããŠã誰ããæºåããã¿ã¹ã¯ã®èª¬æãäžæžãããªãããã«ããå¿ èŠããããŸãã ãã®çµæããªãã¬ãŒã·ã§ã³å€æïŒOTïŒã®å®è·µãæãäžããçã®ã³ã©ãã¬ãŒã·ã§ã³ã®ããã®ããŒã«ãäœæããããšã«ããŸããã ãã®èšäºã§ã¯ããªããããã¹ãããã¥ã¡ã³ãã®OTã®çè«ãšå®è£ ã«ã€ããŠã¯è©³ããæ€èšããŸããããããã«ã€ããŠã¯æ¢ã«ååãªè³æããããŸãã ç§ãã¡ã®ããŒã ãã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ã®éçºã§ééããå°é£ã®ã¿ãèæ ®ããŸãã
ã¹ããŒããã©ã³ã§ã®å ±åç·šé-ãªãã§ããïŒ
ãã¡ãããããã補åã®éèŠãªæ©èœã§ãªãéããããããå¿ èŠã¯ãããŸããã ãã¹ãŠã®ãã©ãããã©ãŒã ã§æ倧éã®åºæ¬æ©èœãæäŸãããšããäžè¬çãªç®æšã«å ããŠãããã«ã€ããŠèããªããã°ãªããªãã£ãããã€ãã®ããå ·äœçãªçç±ããããŸããã
- OTãå®è£ ããã«ã¯ãå ±åç·šéããµããŒãããç¹å®ã®åœ¢åŒã§ããã¥ã¡ã³ããä¿åããå¿ èŠããããŸãã ãã¬ãŒã³ããã¹ãã®å Žåãããã«ã¯ç¹å¥ãªåœ¢åŒã¯ãããŸãããåãªãæååã§ãã ãã ãããªããããã¹ãïŒæžåŒä»ãããã¹ãïŒã®å Žåãã¹ãã¬ãŒãžåœ¢åŒã¯ããè€éã«ãªããŸãã
- ããã¥ã¡ã³ããå£ãããšãªãããŸãä»ã®ãŠãŒã¶ãŒãåãæéã«è¡ãããšãã§ããå€æŽãšç«¶åããããšãªããã¢ãã€ã«ã¯ã©ã€ã¢ã³ãã«ãã£ãŠè¡ãããå€æŽãä¿åããæ¹æ³ãå¿ èŠã§ãã ãããã¯ãOTã¢ã«ãŽãªãºã ã«ãã£ãŠæ£ç¢ºã«è§£æ±ºãããã¿ã¹ã¯ã§ãã
- ãã©ã°ã©ã2ã®æ¡ä»¶ãæºããããã«OTã¢ã«ãŽãªãºã ãã¢ãã€ã«ãã©ãããã©ãŒã ã«è»¢éããå¿ èŠããããããæ¬æ Œçãªå ±åç·šéãè¡ãããã«ããã以äžå€§ããªåŽåã¯å¿ èŠãããŸããã
ãã®ãããåºæ¬çãªæ©èœãšããŠã®ã¿ã¹ã¯ã®ãªããããã¹ãèšè¿°ãç¹å®ã®ããã¥ã¡ã³ã圢åŒãšåæãããã³ã«ããµããŒãããå¿ èŠæ§ãããã®ã§ã解決çãèŠã€ããŸãããã
å®è£ ãªãã·ã§ã³
ã³ã©ãã¬ãŒã·ã§ã³ã³ã³ããŒãã³ãã®å®è£ ã«ã€ããŠã¯ãã§ã«çµéšããããŸããããAndroidã«è»¢éããæ¹æ³ãç解ããå¿ èŠããããŸããã ç·šéè ã®èŠä»¶ã«å€§ããäŸåããŠãããäžè¬çã«ã¯æ¬¡ã®2ã€ããããŸããã
- åºæ¬çãªæžåŒèšå®ããªã¹ããç»åãšè¡šã®æ¿å ¥ã
- ããã¹ãèªäœãšãã®ãã©ãŒãããã®äž¡æ¹ã§å€æŽãè¡ãã远跡ã§ããAPIã
æ¹æ³1ïŒWeb補åã®æ¢åã®ã³ã³ããŒãã³ãã䜿çšãã
å®éãæ¢ã«æã£ãŠããã³ã³ããŒãã³ãã䜿çšããŠãWebViewã«ã©ããããããšãã§ããŸãã å©ç¹ã®1ã€ã¯çµ±åã®å®¹æãã§ããããã¯ãå®è³ªçã«ãã¹ãŠã®ãšãã£ã¿ãŒã³ãŒããã¹ã¯ãªããã«å«ãŸããŠãããAndroid / iOSéçºè ã¯WebViewã©ãããŒã®ã¿ãå®è£ ã§ããããã§ãã
ããã«ãContentEditableããã¥ã¡ã³ãã§åäœããã¡ã€ã³ã¢ããªã±ãŒã·ã§ã³ã®æ¢åã®ã³ã³ããŒãã³ãã¯ãOSãšãã³ããŒã®ããŒãžã§ã³ã«ãã£ãŠã¯éåžžã«äžå®å®ã§ããããšãæããã«ãªããŸããã å Žæã®ãšããŸããã¯ãªãã°ãæŽèµ°ããŸãããã倧éšåã¯ããã¹ãã®åŒ·èª¿è¡šç€ºãšå ¥åã®æ©èœãããã³ãã©ãŒã«ã¹ãšããŒããŒãã®æ¬ èœã®æ©èœã®åšãã«ãããã¢ããããŸããã
ContentEditableã®åé¡ãåé¿ããããããšãã£ã¿ãŒã®ããã³ããšã³ããšããŠCodeMirrorã䜿çšããããšããŸããããããŒããŒãããã®ãã¹ãŠã®ã€ãã³ããåŠçããç¬èªã«ã¬ã³ããªã³ã°ãããããAndroidã§ã¯ããã«å®å®ããŠåäœããŸãã ãã¡ãããã€ãã¹ããããŸããããIMEã®ããŒæŒäžã€ãã³ãã®åŠçã«æªåé«ãå€æŽãçŸãããŸã§ãç°¡åãªåé¿çãšããŠéåžžã«ããŸãæ©èœããŸããããã®åé¡ã«ã€ããŠã¯ã ããã§è©³ãã説æããŸã ã ç°¡åã«èšãã°-LatinIMEã䜿çšããå ŽåãKEYCODE_DELã®ã€ãã³ãã¯éä¿¡ãããŸããã
ããã¯ãŠãŒã¶ãŒã«ãšã£ãŠäœãæå³ããŸããïŒ [åé€]ãã¯ãªãã¯ããŠãäœãèµ·ãããŸãããã€ãŸãããšãã£ã¿ãŒã¯æ£åžžã«åäœããããã¹ããå ¥åããæžåŒèšå®ãé©çšã§ããŸããã©ã®ããã«èãããŠããããã¹ãã¯åé€ã§ããŸããã ãã®åé¡ã®å¯äžã®è§£æ±ºçã¯ããšãããã次ã®ã³ãŒããå«ãŸããŠããŸããã
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { BaseInputConnection baseInputConnection = new BaseInputConnection(this, false) { @Override public boolean sendKeyEvent(KeyEvent event) { if (needsKeyboardFix() && event.getAction() == KeyEvent.ACTION_MULTIPLE && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) { passUnicodeCharToEditor(event); return true; } return super.sendKeyEvent(event); } @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) && (beforeLength == 1 && afterLength == 0)) { // Send Backspace key down and up events return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); } else { return super.deleteSurroundingText(beforeLength, afterLength); } } }; outAttrs.inputType = InputType.TYPE_NULL; return baseInputConnection; }
InputType.TYPE_NULLã¯åæã«IMEããç°¡ç¥åãããã圢åŒã«å€æããInputConnectionãå¶éã¢ãŒãã§åäœããŠããããšã瀺ããŸããã€ãŸããã³ããŒ/貌ãä»ããèªåä¿®æ£/èªåè£å®ããžã§ã¹ãã£ãŒã䜿çšããããã¹ãå ¥åããªãããšãæå³ããŸãããåæã«ãã¹ãŠã®ããŒããŒãã€ãã³ããåŠçã§ããŸãã
ãã®çµæãWebã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšãããšãã£ã¿ãŒã®ææ°ã®å®è£ ã«ã¯ã次ã®æ¬ ç¹ããããŸããã
- ããŠã³ããŒãé床ãé ã
- é«åºŠãªIMEæ©èœãžã®ã¢ã¯ã»ã¹ã®æ¬ åŠïŒã³ããŒ/貌ãä»ãããªãŒãã³ã³ããªãŒã/ãªãŒãã³ã¬ã¯ãããžã§ã¹ãã£ãŒå ¥åïŒ;
- å Žåã«ãã£ãŠã¯ãAPIã®ããŸããŸãªããŒãžã§ã³ã§ã®WebViewã®ããŸããŸãªå®è£ ããã³äžéšã®ãã³ããŒã«ãããã®ã³ã³ããŒãã³ãã®å€æŽã®ããã«ãåäœãäžå®å®ã«ãªããŸãã
- éåžžãWebViewã¯ãç¹ã«ã¡ã¢ãªãå°ãªãããã€ã¹ã§ã¯é·æéã¡ã¢ãªã«ãšã©ãŸããŸããããŸããã¢ããªã±ãŒã·ã§ã³ãæå°åãããã°ããããŠããåèµ·åãããšãã»ãšãã©ã®å ŽåãWebViewãå床åæåããå¿ èŠããããŸãã
- ã³ãŒãã«ã¯å€æ°ã®æŸèæãããããã®æ°ã¯æéãšãšãã«å¢å ããŸããã
ãã®ãããªãšãã£ã¿ãŒã®å®è£ ãç¶æããããšã¯å®¹æã§ã¯ãªãããšãèªèããèšèŒãããŠããæ¬ ç¹ãšå¶éãèæ ®ããŠããã©ãŒããããããããã¹ãã䜿çšã§ãããã€ãã£ãã³ã³ããŒãã³ããéçºããããšã«ããŸããã
æ¹æ³2ïŒãã€ãã£ãå®è£
ãã€ãã£ãå®è£ ã®å Žåã次ã®2ã€ã®åé¡ã解決ããå¿ èŠããããŸãã
- UIãšãã£ã¿ãŒãã€ãŸãããã©ãŒããããšç·šéã«åºã¥ããŠããã¹ãã衚瀺ããŸãã
- ããã¥ã¡ã³ã圢åŒãå€æŽè¿œè·¡ãããã³ãµãŒããŒãšã®ããŒã¿äº€æãåŠçããŸãã
æåã®åé¡ã解決ããããã«ãè»èŒªãåçºæããå¿ èŠã¯ãããŸãã-Androidã¯å¿ èŠãªããŒã«ãã€ãŸãEditTextã³ã³ããŒãã³ããšããã¹ãã®ã©ãã«ä»ããèšè¿°ããSpannableã€ã³ã¿ãŒãã§ãŒã¹ãæäŸããŸãã
2çªç®ã®ã¿ã¹ã¯ã¯ãOTã¢ã«ãŽãªãºã ãJavaScriptããJavaã«è»¢éããããšã§è§£æ±ºãããããã§ã®ããã»ã¹ã¯éåžžã«ééçã§ãã
EditTextã§ãªããããã¹ãã衚瀺ãã
Androidã«ã¯ãããã¹ãããŒã¯ã¢ãããèšå®ã§ãããã°ãããSpannableã€ã³ã¿ãŒãã§ã€ã¹ããããŸãã ããŒã¯ã¢ããããã»ã¹èªäœã¯éåžžã«ç°¡åã§ããç¹å¥ãªSpannableStringBuilderã¯ã©ã¹ã䜿çšããå¿ èŠããããŸãããã®ã¯ã©ã¹ã䜿çšãããšãããã¹ããèšå®/å€æŽããã¡ãœãããä»ããŠããã¹ãã®æå®ã»ã¯ã·ã§ã³ã®ã¹ã¿ã€ã«ãèšå®ã§ããŸãã
setSpan(Object what, int start, int end, int flags).
æåã®ãã©ã¡ãŒã¿ãŒã¯ã¹ã¿ã€ã«ãèšå®ããã ãã§ãã android.text.styleããã±ãŒãžã®1ã€ä»¥äžã®ã€ã³ã¿ãŒãã§ãŒã¹ïŒCharacterStyleãUpdateAppearanceãUpdateLayoutãParagraphStyleãªã©ïŒãå®è£ ããã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ã§ãªããã°ãªããŸããã ããã©ã«ãã®ã¹ã¿ã€ã«ã®ã»ããã¯éåžžã«åºããæåãã©ãŒãããïŒStyleSpanãUnderlineSpanïŒã®å€æŽãããã¹ããµã€ãºã®èšå®ïŒRelativeSizeSpanïŒãäœçœ®ã®å€æŽïŒAlignmentSpanïŒãããµããŒãã€ã¡ãŒãžïŒImageSpanïŒããã³ã¯ãªãã¯å¯èœãªããã¹ãïŒClickableSpanïŒãŸã§ã§ãã
æåŸã®ãã©ã¡ãŒã¿ãŒã¯ãã©ã°ãèšå®ããŸãããã®åœ¹å²ã«ã€ããŠã¯ä»¥äžã§èª¬æããŸãã ããšãã°ãããã¯ããã¹ãå šäœã®è²ãå€æŽããæ¹æ³ã§ãã
SpannableStringBuilder ssb = new SpannableStringBuilder(text); ssb.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setText(ssb, TextView.BufferType.SPANNABLE);
ãã®ãããå ¥åã«ã¯ç¹å®ã®åœ¢åŒã®ããã¹ãããããŸãããåºåã«ã¯ãã®è¡šçŸãSpannableãªããžã§ã¯ãã®åœ¢åŒã§ååŸããEditTextã«æž¡ãå¿ èŠããããŸãã ãã®å Žåãããã¥ã¡ã³ãã¯å±æ§ä»ãæååã®åœ¢åŒã§ãµãŒããŒããç¹å¥ãªåœ¢åŒã§ååŸãããŸããOTçšã®ã©ã€ãã©ãªã䜿çšããŠãã®æååã解æããããã¹ãã®æå®ãããéšåã«å±æ§ãé©çšããå¿ èŠããããŸãã ã¹ã¿ã€ã«ã«å¿ããŠãããã¹ãã®ã©ãã«ä»ãããŠãŒã¶ãŒã®æåŸ ã«åãããã«ãæ£ãããã©ã°ãèšå®ããå¿ èŠããããŸãã
ãã©ã°SPAN_EXCLUSIVE_INCLUSIVEã§ã¹ã¿ã€ã«ãããŒã¯ãããšãééã®æåŸã«å ¥åãããããã¹ãã«é©çšãããŸãããæåã«ã¯é©çšãããŸããã ããšãã°ãUnderlineSpan + SPAN_EXCLUSIVE_INCLUSIVEã¹ã¿ã€ã«ãèšå®ãããŠããéé[10ã20]ããããŸãã ãã®å Žåãäœçœ®9ã«ããã¹ããå ¥åãããšãUnderlineSpanã¹ã¿ã€ã«ã¯é©çšãããŸããããäœçœ®20ã«ããã¹ããå ¥åãå§ãããšãã¹ã¿ã€ã«ãã«ããŒããééãæ¡å€§ããŠ[10ã21]ã«ãªããŸãã åœç¶ãããã¯ã€ã³ã©ã€ã³æžåŒèšå®ïŒå€ªå/æäœ/äžç·ãªã©ïŒã«åœ¹ç«ã¡ãŸãã
SPAN_EXCLUSIVE_EXCLUSIVEãã©ã°ã䜿çšããå Žåãã¹ã¿ã€ã«ééã¯äž¡ç«¯ã§å¶éãããŸãã ããã¯ãããšãã°ãªã³ã¯ã«é©ããŠããŸã-ãªã³ã¯ã®çŽåŸã«ããã¹ãã®æ¿å ¥ãéå§ããå Žåããªã³ã¯ã¹ã¿ã€ã«ãé©çšããªãã§ãã ããã
ãã©ã°SPAN_EXLUSIVE_INCLUSIVEããã³SPAN_EXCLUSIVE_EXCLUSIVEã䜿çšãããšããŠãŒã¶ãŒã®æåŸ ã«å¿ããŠããã¹ããå ¥åãããšãã®æžåŒèšå®åäœãå¶åŸ¡ã§ããŸãã ããšãã°ã倪åæžåŒèšå®ã¢ãŒãããªã³ã«ããå Žåãå ¥åããã¹ãã¯å€ªåã®ãŸãŸã«ãªããŸãã ãŸãããªã³ã¯ãäœæããå ŽåãæåŸã«ããã¹ããè¿œå ããŠããªã³ã¯ã®å¢çã¯æ¡å€§ãããŸããã
BulletSpanã䜿çšããŠãªã¹ãã¢ã€ãã ã衚瀺ã§ããŸãããé åºã®ãªããªã¹ãã«ã®ã¿é©ããŠããŸãã çªå·ä»ããå¿ èŠãªå Žåã¯ãLeadingMarginSpanããã³UpdateAppearanceã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããç¬èªã®ã¯ã©ã¹ãèšè¿°ããŠãdrawLeadingMarginã¡ãœããã§ãªã¹ãã€ã³ãžã±ãŒã¿ãŒãåžæã©ããã«ã¬ã³ããªã³ã°ã§ããŸãã
ã«ã¹ã¿ã ã¹ã¿ã€ã«åŠç
ãšãã£ã¿ãŒã¯ããŠãŒã¶ãŒããã©ãŒããããé©çšã§ããããã«ããå¿ èŠãããããšã¯æããã§ããããã«ã¯ä»¥äžãå«ãŸããŸãã
- éžæããããã¹ãã«æ°ããã¹ã¿ã€ã«ãè¿œå ãã
- ã«ãŒãœã«äœçœ®ã«æ°ããã¹ã¿ã€ã«ãæ¿å ¥ãã
- ç·šéäžã«çŸåšã®ã¹ã¿ã€ã«ãé©çšããŸãã
ãŸãããšãã£ã¿ãŒã§ãµããŒããããŠããã¹ã¿ã€ã«ã®ã©ããã«ãã¿ã³ãé 眮ããå¿ èŠããããŸãã ããããã¢ã¯ãã£ããã£ããŒã«ããŒã«é 眮ããããšã¯ãAndroid MarshmallowããªãªãŒã¹ããããŸã§å®çšçã§ã¯ãããŸããã§ããã ããã©ã«ãã§ã¯ãããã¹ããéžæãããšãã«ã³ã³ããã¹ãã¡ãã¥ãŒã«åãããŒã«ããŒã䜿çšããããããéžæããããã¹ãã®ã¹ã¿ã€ã«ãéžæããããšã¯ã§ããŸããã ãã®ãããç»é¢äžéšã®ããŒã«ããŒã«ããããé 眮ã§ããŸãã ã¹ã¿ã€ã«ãã¿ã³ãã¯ãªãã¯ãããšããšãã£ã¿ãŒã®çŸåšã®ç¶æ ãå€å¥ããéžæããããã¹ãã«ã¹ã¿ã€ã«ãé©çšããããã«ãŒãœã«äœçœ®ã§ãã®ã¹ã¿ã€ã«ãäžæçãªãã®ãšããŠèšæ¶ããå¿ èŠããããŸãã
private void onApplyInlineAttributeToSelection(int selectionStart, int selectionEnd, TextAttribute attribute) { int selectionStart = mEditText.getSelectionStart(); int selectionEnd = mEditText.getSelectionEnd(); if (!mEditText.hasSelection()) { // if there's no selection, insert/delete empty span for the appropriate attribute, // but only in case the cursor is present if (selectionStart == selectionEnd && selectionStart != -1) { if (mTempAttributes == null || mTempAttributes.getPos() != selectionStart) { mTempAttributes = new TempAttributes(selectionStart); } Set<Object> attributeSpans = getAttributeSpans(selectionStart, selectionEnd, attribute); if (attributeSpans.size() > 0) { attribute.nullify(); } mTempAttributes.addAttribute(attribute); } return; } if (attribute == null) { return; } boolean changed = applyInlineAttributeToSelection(selectionStart, selectionEnd, attribute); // if nothing changed, then there's no need to build any changesets and send updates to server if (!changed) { return; } // ... }
mTempAttributesã¯ãTempAttributesã¯ã©ã¹ã®ã€ã³ã¹ã¿ã³ã¹ã§ãã ãŠãŒã¶ãŒãéžæãããã®äœçœ®ã®å±æ§ã®ã»ãããå®çŸ©ããŸãã ãã®å€æ°ã¯ã䜿çšåŸãŸãã¯ã«ãŒãœã«äœçœ®ã®å€æŽæã«ãŒãã«ãªã»ãããããŸãã
static class TempAttributes { private final int mPos; private final Map<AttributeName, TextAttribute> mAttributeMap = new HashMap<>(); public TempAttributes(int pos) { mPos = pos; } public int getPos() { return mPos; } public Collection<TextAttribute> getAttributes() { return mAttributeMap.values(); } public void addAttribute(TextAttribute attribute) { AttributeName name = attribute.getAttributeName(); TextAttribute oldAttribute = mAttributeMap.get(name); if (oldAttribute != null && !oldAttribute.isNull()) { attribute.nullify(); } mAttributeMap.put(name, attribute); } }
ãŠãŒã¶ãŒãããŒã«ããŒã®ç¹å®ã®ã¹ã¿ã€ã«ã«å¯Ÿå¿ãããã¿ã³ãã¯ãªãã¯ããããããã¹ããéžæãããŠããªãå Žåããã®å Žåããã®ã¹ã¿ã€ã«ãçŸåšã®ã«ãŒãœã«äœçœ®ã«ãäžæããšããŠä¿åãããã®äœçœ®ã«ããã¹ããå ¥åãããšãã«é©çšããå¿ èŠããããŸãã 詳现ã«ã€ããŠã¯ã以äžãã芧ãã ããã
ããã¹ããéžæããããããã®ã¹ã¿ã€ã«ãéžæããééã«æ¢ã«ååšãããã©ãããå€æããå¿ èŠããããŸãã ããã§ãªãå ŽåããŸãã¯éšåçã«ããã¹ãŠã®æ¢åã®ã¹ãã³ãçµåãããã®ã¹ã¿ã€ã«ã§ééãå®å šã«ã«ããŒããå¿ èŠããããŸãã ããå Žåã¯ãééãã察å¿ããã¹ãã³ãåé€ããå¿ èŠã«å¿ããŠåå²ããŸãã
äŸ1
ããã¹ãããããŸãïŒ Quick brown fox ã
倪å[0.4]ãšå€ªå[12.14]ã®2ã€ã®ã¹ãã³ããããŸãã ãŠãŒã¶ãŒããã¹ãŠã®ããã¹ããéžæããŠå€ªåã¹ã¿ã€ã«ãé©çšããå Žåãæçµçã«ã¯ééå šäœãã«ããŒããå¿ èŠããããŸãã ãããè¡ãã«ã¯ãäž¡æ¹ã®ã¹ãã³ãåé€ããŠæ°ãã倪å[0ã14]ãè¿œå ãããã2çªç®ã®ã¹ãã³ãåé€ããŠæåã®ã€ã³ã¿ãŒãã«ãééã®çµãããŸã§å»¶é·ããŸãã
äŸ2
ããã¹ãããããŸãïŒ Quick brown fox ã
倪å[0ã14]ã®1ã€ã®ã¹ãã³ããããŸãã ãŠãŒã¶ãŒãããã¹ã[4ã12]ãéžæããããŒã«ããŒã§å€ªåã¹ã¿ã€ã«ãéžæããå Žåãã¹ã¿ã€ã«ã¯éžæç¯å²ã«å®å šã«ååšãããããééããã¹ã¿ã€ã«ãåé€ããå¿ èŠããããŸãã ãããè¡ãã«ã¯ãééã2ã€ã®éšåã«åå²ããŸããéžæãå§ãŸãåã«ééå šäœ[0ã14]ãçããïŒ[0ã4]ïŒãéžæç¯å²ã®æåŸããããã¹ãã®çµãããŸã§æ°ããééãè¿œå ããŸãïŒ[4ã12]ïŒã
ããã¥ã¡ã³ãã®å€æŽã远跡ãã
ãŠãŒã¶ãŒã®å€æŽãæ£ãã远跡ããŠOTã¢ã«ãŽãªãºã ã«ããã£ãŒããããã«ã¯ããšãã£ã¿ãŒããããã远跡ã§ããå¿ èŠããããŸãã ãããè¡ãã«ã¯ãTextWatcherã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšãããŸããEditTextã§å€æŽãçºçãããã³ã«ããã®ã€ã³ã¿ãŒãã§ã€ã¹ã®beforeTextChangedãonTextChangedãafterTextChangedã¡ãœãããé çªã«åŒã³åºãããäœãã©ãã§å€æŽãããããå€æã§ããŸãã
private boolean mIgnoreNextTextChange = false; private int mCurrentPos; private String mOldStr = null; private String mNewStr = null; // ... public void ignoreNextTextChange(boolean ignore) { mIgnoreNextTextChange = ignore; } public void beforeTextChanged(CharSequence s, int start, int count, int after){ if (mIgnoreNextTextChange) { return; } mOldStr = null; mCurrentPos = start; if (s.length() > 0 && count > 0) { mOldStr = s.subSequence(start, start + count).toString(); } } public void onTextChanged(CharSequence s, int start, int before, int count) { if (mIgnoreNextTextChange) { return; } mNewStr = null; if (s.length() > 0 && count > 0) { mNewStr = s.subSequence(start, start + count).toString(); } } public void afterTextChanged(Editable s) { // ... }
setTextïŒCharSequenceïŒãä»ããŠãšãã£ã¿ãŒã«æåã«ããã¹ããã€ã³ã¹ããŒã«ãããšãTextWatcherãããã«é¢ããéç¥ãåãåããããããã°ã©ã ã«ããããã¹ãã®ã€ã³ã¹ããŒã«ã¯æ¬¡ã®ããã«ãªããŸãã
mEditTextWatcher.ignoreNextTextChange(true); mEditText.setText(builder); mEditTextWatcher.ignoreNextTextChange(false);
mOldStrå€æ°ãšmNewStrå€æ°ã¯ãããããå€ãè¡ãšæ°ããè¡ãæ ŒçŽããŸããmCurrentPosã¯ãå€æŽãçºçããäœçœ®ã瀺ããŸãã ããšãã°ããŠãŒã¶ãŒã10æ¡ç®ã«ãaããšããæåãè¿œå ããå Žåã
mOldStr = null; mNewStr = "a"; mCurrentPos = 10;
ãã ããããããªãã¥ã¢ã³ã¹ããããŸã-èªåä¿®æ£ã«ããããã¹ããæ¿å ¥ããå Žåããããã®å€ã«ã¯åèªã®å é ãå«ãŸããå ŽåããããŸãã ããšãã°ãããã¹ãããããã¹ãããšããåèªã§å§ãŸãããŠãŒã¶ãŒã3çªç®ã®æåããsãã«çœ®ãæããå ŽåãIMEã¯ãã®å€æŽã次ã®ããã«å ±åã§ããŸãã
mOldStr = "Tex"; mNewStr = "Tes"; mCurrentPos = 0;
ãã®å Žåãè¡ã®å é ããåãæååãã«ããããå¿ èŠããããŸãã
æçµçã«ãTextWatcherã䜿çšããŠãæ£ç¢ºã«äœãèµ·ãã£ãã®ããæ確ã«å€æã§ããŸããããã¹ãã眮æãåé€ããŸãã¯è¿œå ãããŸããã ãŠãŒã¶ãŒãããã¹ãããã®äœçœ®ã«è¿œå ããããæ¢åã®ããã¹ãã®äžéšããããã¡ãŒã®ããã¹ãã§çœ®ãæããå Žåãã«ãŒãœã«äœçœ®ã«ããå±æ§ãè¿œå ãããããã¹ãã«é©çšããå¿ èŠããããŸãã ãããè¡ãã«ã¯ã空ã«ãªã£ããªããžã§ã¯ãïŒs.getSpanStartïŒspanïŒ== s.getSpanEndïŒspanïŒïŒãé€å€ããããšãå¿ããã«ãã«ãŒãœã«äœçœ®ã«ãããã¹ãŠã®Spannableãªããžã§ã¯ããæ€çŽ¢ããSpannableãªããžã§ã¯ãèªäœãåé€ããŠã€ã³ã©ã€ã³å±æ§ã®ã¿ã§ãã£ã«ã¿ãªã³ã°ããŸãïŒå€ªåãæäœãªã©ïŒã ããã«ãããŒã«ããŒã§ãŠãŒã¶ãŒãéžæããã¹ã¿ã€ã«ïŒmTempAttributesïŒã«å¯Ÿå¿ããå±æ§ãè¿œå ãããŸãã
public void afterTextChanged(Editable s) { // ... Object[] spans = s.getSpans(mCurrentPos, mCurrentPos, Object.class); Map<Object, TextAttribute> spanAttrMap = new LinkedHashMap<>(); for (Object span : spans) { TextAttribute attr = AttributeManager.attributeForSpan(span); if (attr != null) { spanAttrMap.put(span, attr); } } if (!TextUtils.isEmpty(mOldStr)) { Iterator<Map.Entry<Object, TextAttribute>> iterator = spanAttrMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Object, TextAttribute> entry = iterator.next(); Object span = entry.getKey(); TextAttribute attr = entry.getValue(); // ... if (s.getSpanStart(span) == s.getSpanEnd(span)) { s.removeSpan(span); iterator.remove(); } } } // ... Set<TextAttribute> attributes = new HashSet<>(); if (!TextUtils.isEmpty(mNewStr)) { // determine all inline attributes at current position for (Map.Entry<Object, TextAttribute> entry : spanAttrMap.entrySet()) { TextAttribute attr = entry.getValue(); if (AttributeManager.isInlineAttribute(attr)) { attributes.add(attr); } } } if (mCallbacks != null) { mCallbacks.onTextChanged(mCurrentPos, mOldStr, mNewStr, attributes); } }
ãã®çµæãå€æŽãçºçããäœçœ®ãããããã®äœçœ®ã®å€ãããã¹ããšæ°ããããã¹ããããã³æ°ããããã¹ãã«é©çšããå¿ èŠãããã€ã³ã©ã€ã³å±æ§ãèªèãããŸãã ãã®åŸãè¿œå ã®åŠçãè¿œå ã§ããŸãã ããšãã°ããŠãŒã¶ãŒããªã¹ãã®æåŸã®é ç®ã®æåŸã«æ¹è¡ãæ¿å ¥ããå Žåããªã¹ãã®çŸåšã®ã«ãŒãœã«äœçœ®ã«æ°ããé ç®ãæ¿å ¥ããŠãªã¹ããç¶ç¶ã§ããŸãã æçµçã«ãå€æŽã®ãªã¹ãããããã®ããŒã¿ããã³ã³ãã€ã«ããããµãŒããŒã«éä¿¡ãããŸãã
ãšãã£ã¿ãŒã§å€æŽã远跡ããå Žåããã¹ãŠã®ããã©ã«ãã¹ã¿ã€ã«ã«ã©ãããŒã䜿çšããããšããå§ãããŸãã ããšãã°ãUnderlineSpanã®ä»£ããã«ãUnderlineSpanãç¶æ¿ããCustomUnderlineSpanã¯ã©ã¹ã䜿çšããŸãããã¡ãœããã¯ãªãŒããŒã©ã€ããããŸããã ãã®ã¢ãããŒãã«ãããã¯ã©ã¹ã¯EditTextã«ãã£ãŠäœ¿çšãããã¹ã¿ã€ã«ããããããã®ãã¹ã¿ã€ã«ãäžæã«åé¢ã§ããŸãã ããšãã°ããªãŒãã³ã¬ã¯ããæå¹ã«ãªã£ãŠããå Žåãåèªã®ç·šéæã«EditTextã¯UnderlineSpanã¹ã¿ã€ã«ãè¿œå ããèŠèŠçã«ç·šéæã«åèªã«äžç·ãåŒãããŸãã
APIã®ç°ãªãããŒãžã§ã³ãšã®äºææ§ã«ã€ããŠ
Android KitKatããåã®APIããŒãžã§ã³ã§ã¯ãç·šéæã«ã¹ãããã«ããã¹ãã®ãªãŒããŒã¬ã€ã«åé¡ããããŸãã TextViewã®ããŒããŠã§ã¢ã¢ã¯ã»ã©ã¬ãŒã·ã§ã³ãç¡å¹ã«ããããšã§è§£æ±ºããŸãïŒããããããããä¿®æ£ããä»ã®æ¹æ³ããããŸã-ã³ã¡ã³ãã®ææ¡ã¯å€§æè¿ã§ãïŒã
mEditText.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
ãã ãããã®ãã©ãŒã ã§ã¯ãViewå šäœãã¡ã¢ãªã«ã¬ã³ããªã³ã°ãããããïŒãæç»ãã£ãã·ã¥ã«åãŸããªãã»ã©å€§ãããã¥ãŒãïŒãTextViewèªäœãã¹ã¯ããŒã«ã§ããããã«ããå¿ èŠããããããTextViewãScrollViewã«é 眮ã§ããŸããã
mEditText.setVerticalScrollBarEnabled(true); mEditText.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
ãããã«
WebViewã§ã®ãšãã£ã¿ãŒã®å®è£ ã«èŠãã¿ããã®ã¢ãããŒãã®è¡ãè©°ãŸãã«æ°ä»ããç§ãã¡ã¯ãå ±åããã¹ãç·šéã®å°é£ã§ã¯ãããããªãèå³æ·±ãã¿ã¹ã¯ã解決ãããã€ãã£ãã³ã³ããŒãã³ããéçºããããšã«æåããŸããã ããã«ãããã¢ããªã±ãŒã·ã§ã³ã®äœ¿ãããããåäžãããŠãŒã¶ãŒã®çç£æ§ãåäžããŸããã çµæã¯ãGoogle Playããã¢ããªã±ãŒã·ã§ã³ãããŠã³ããŒãããããšã§æšå®ã§ããŸãã
