2012年10月12日金曜日

Android TextView (EditText) の文字選択処理をカスタマイズする

EditText (もしくは TextView で android:textIsSelectable="true" を指定した場合)に文字列をロングタップして起動する ActionMode をカスタマイズすることができます。

TextView の setCustomSelectionActionModeCallback() で ActionMode.Callback を指定することで、既存のメニューを削除したり、新しいメニューを追加したりすることができます。

  1. EditText editText = (EditText) findViewById(R.id.editText1);  
  2. editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {  
  3.       
  4.     @Override  
  5.     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {  
  6.         // TODO Auto-generated method stub  
  7.         return false;  
  8.     }  
  9.       
  10.     @Override  
  11.     public void onDestroyActionMode(ActionMode mode) {  
  12.         // TODO Auto-generated method stub  
  13.           
  14.     }  
  15.       
  16.     @Override  
  17.     public boolean onCreateActionMode(ActionMode mode, Menu menu) {  
  18.         // TODO Auto-generated method stub  
  19.         return false;  
  20.     }  
  21.       
  22.     @Override  
  23.     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {  
  24.         // TODO Auto-generated method stub  
  25.         return false;  
  26.     }  
  27. });  


setCustomSelectionActionModeCallback() で渡した ActionMode.Callback は TextView の mCustomSelectionActionModeCallback で保持されます。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/widget/TextView.java#10075
  1.   350     private Callback mCustomSelectionActionModeCallback;  
  2.   
  3. 10075     public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {  
  4. 10076         mCustomSelectionActionModeCallback = actionModeCallback;  
  5. 10077     }  
  6. 10078   
  7. 10079     /** 
  8. 10080      * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 
  9. 10081      * 
  10. 10082      * @return The current custom selection callback. 
  11. 10083      */  
  12. 10084     public ActionMode.Callback getCustomSelectionActionModeCallback() {  
  13. 10085         return mCustomSelectionActionModeCallback;  
  14. 10086     }  


1. ActionMode を起動しない

onCreateActionMode() で false を返すと、ロングタップしても ActionMode が起動しなくなります。

  1. EditText editText = (EditText) findViewById(R.id.editText1);  
  2. editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {  
  3.   
  4.     ...              
  5.       
  6.     @Override  
  7.     public boolean onCreateActionMode(ActionMode mode, Menu menu) {  
  8.         return false;  
  9.     }  
  10. });  
TextView の ActionMode である SelectionActionModeCallback の onCreateActionMode() の中で mCustomSelectionActionModeCallback の onCreateActionMode() を呼び出し、その戻り値が false の場合は false を返すようになっているからです。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/widget/TextView.java#10238
  1.    10182     private class SelectionActionModeCallback implements ActionMode.Callback {  
  2.    10183   
  3.    10184         @Override  
  4.    10185         public boolean onCreateActionMode(ActionMode mode, Menu menu) {  
  5. ...  
  6.    10237   
  7.    10238             if (mCustomSelectionActionModeCallback != null) {  
  8.    10239                 if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {  
  9.    10240                     // The custom mode can choose to cancel the action mode  
  10.    10241                     return false;  
  11.    10242                 }  
  12.    10243             }  
  13. ...  
  14.    10251         }  


2. 既存のメニュー項目を削除する

デフォルトのメニュー項目のそれぞれの ID は

SelectAll : android.R.id.selectAll
Cut : android.R.id.cut
Copy : android.R.id.copy;
Paste : android.R.id.paste

です。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/widget/TextView.java#9042
  1. 9041     // Selection context mode  
  2. 9042     private static final int ID_SELECT_ALL = android.R.id.selectAll;  
  3. 9043     private static final int ID_CUT = android.R.id.cut;  
  4. 9044     private static final int ID_COPY = android.R.id.copy;  
  5. 9045     private static final int ID_PASTE = android.R.id.paste;  
例えば、Cut と Paste 機能を削除したい場合は removeItem() を使います。
  1. EditText editText = (EditText) findViewById(R.id.editText1);  
  2. editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {  
  3.   
  4.     ...  
  5.            
  6.     @Override  
  7.     public boolean onCreateActionMode(ActionMode mode, Menu menu) {  
  8.           
  9.         menu.removeItem(android.R.id.cut);  
  10.         menu.removeItem(android.R.id.paste);  
  11.           
  12.         return true;  
  13.     }  
  14. });  


3. メニューの機能を置き換える

メニューの項目はそのままで、タップされたときの処理を置き換えるには onActionItemClicked() で true を返します。もともとの処理も行ってほしい場合は false を返します。

例えば、MenuItem の id が android.R.id.selectAll のときに true を返すようにすると、全選択をタップしても何も起こらなくなります。

  1. EditText editText = (EditText) findViewById(R.id.editText1);  
  2. editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {  
  3.   
  4.     ...  
  5.   
  6.     @Override  
  7.     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {  
  8.         int id = item.getItemId();  
  9.         switch(id) {  
  10.             case android.R.id.selectAll:  
  11.                 // 独自の処理  
  12.                 return true;  
  13.         }  
  14.         return false;  
  15.     }  
  16. });  


4. 独自のメニュー項目を追加する

メニュー項目を追加するには onCreateActionMode で Menu.add() を使います。
残念ながら既存のメニュー項目の Order が 0 になっているため、任意の位置に追加することはできないようで、最後の位置に追加されます。さらに、Overflow menu に入ると、展開したときに EditText からフォーカスが外れて ActionMode が終了するという残念なことになります。

もう一つ残念なのが、メニュー項目をタップされたときに ActionMode を終了するための stopSelectionActionMode() というメソッドが private なため外部から呼べません(せめて protected にしてほしい)。
ただし、setText() し直すと選択が解除されるので ActionMode を終了することができます。


選択した文字が全角カナだったら半角カナにして先頭に "シャバドゥビタッチ" *1 をつけるようにしてみました。
(アイコンはがんばってトレースしました。)

R.id.replace は XML で定義しました。追加するメニューの ID は適当な数字ではなく、XML で定義しておくのがいいと思います。More Resource Type - ID

  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.   
  8.         final EditText editText = (EditText) findViewById(R.id.editText1);  
  9.         editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {  
  10.   
  11.             @Override  
  12.             public boolean onPrepareActionMode(ActionMode mode, Menu menu) {  
  13.                 return true;  
  14.             }  
  15.   
  16.             @Override  
  17.             public void onDestroyActionMode(ActionMode mode) {  
  18.             }  
  19.   
  20.             @Override  
  21.             public boolean onCreateActionMode(ActionMode mode, Menu menu) {  
  22.                 menu.removeItem(android.R.id.paste);  
  23.                 menu.removeItem(android.R.id.cut);  
  24.                 menu.removeItem(android.R.id.copy);  
  25.   
  26.                 MenuItem item = menu.add(Menu.NONE, R.id.replace, Menu.NONE, "Replace");  
  27.                 item.setIcon(R.drawable.ic_replace);  
  28.   
  29.                 return true;  
  30.             }  
  31.   
  32.             @Override  
  33.             public boolean onActionItemClicked(ActionMode mode, MenuItem item) {  
  34.   
  35.                 CharSequence text = editText.getText();  
  36.   
  37.                 int min = 0;  
  38.                 int max = text.length();  
  39.   
  40.                 if (editText.isFocused()) {  
  41.                     final int selStart = editText.getSelectionStart();  
  42.                     final int selEnd = editText.getSelectionEnd();  
  43.   
  44.                     min = Math.max(0, Math.min(selStart, selEnd));  
  45.                     max = Math.max(0, Math.max(selStart, selEnd));  
  46.                 }  
  47.   
  48.                 int id = item.getItemId();  
  49.                 switch (id) {  
  50.                     case R.id.replace:  
  51.                         CharSequence sub = text.subSequence(min, max);  
  52.                         editText.setText(text.subSequence(0, min) + "シャバドゥビタッチ" + convertKanaFull2Half(sub)  
  53.                                 + text.subSequence(max, text.length()));  
  54.                         return true;  
  55.                 }  
  56.                 return false;  
  57.             }  
  58.         });  
  59.     }  
  60.   
  61.     private static final char[] FULL_WIDTH_KANA = { 'ァ''ア''ィ''イ''ゥ''ウ''ェ''エ''ォ''オ''カ''ガ''キ',  
  62.             'ギ''ク''グ''ケ''ゲ''コ''ゴ''サ''ザ''シ''ジ''ス''ズ''セ''ゼ''ソ''ゾ''タ''ダ''チ''ヂ',  
  63.             'ッ''ツ''ヅ''テ''デ''ト''ド''ナ''ニ''ヌ''ネ''ノ''ハ''バ''パ''ヒ''ビ''ピ''フ''ブ''プ',  
  64.             'ヘ''ベ''ペ''ホ''ボ''ポ''マ''ミ''ム''メ''モ''ャ''ヤ''ュ''ユ''ョ''ヨ''ラ''リ''ル''レ',  
  65.             'ロ''ヮ''ワ''ヰ''ヱ''ヲ''ン''ヴ''ヵ''ヶ'};  
  66.   
  67.     private static final String[] HALF_WIDTH_KANA = { "ァ""ア""ィ""イ""ゥ""ウ""ェ""エ""ォ""オ""カ""ガ""キ",  
  68.             "ギ""ク""グ""ケ""ゲ""コ""ゴ""サ""ザ""シ""ジ""ス""ズ""セ""ゼ""ソ""ゾ""タ""ダ",  
  69.             "チ""ヂ""ッ""ツ""ヅ""テ""デ""ト""ド""ナ""ニ""ヌ""ネ""ノ""ハ""バ""パ""ヒ""ビ""ピ",  
  70.             "フ""ブ""プ""ヘ""ベ""ペ""ホ""ボ""ポ""マ""ミ""ム""メ""モ""ャ""ヤ""ュ""ユ""ョ""ヨ",  
  71.             "ラ""リ""ル""レ""ロ""ワ""ワ""イ""エ""ヲ""ン""ヴ""カ""ケ"};  
  72.   
  73.     private static final char FULL_WIDTH_FIRST = FULL_WIDTH_KANA[0];  
  74.     private static final char FULL_WIDTH_LAST = FULL_WIDTH_KANA[FULL_WIDTH_KANA.length - 1];  
  75.   
  76.     public static String convertKanaFull2Half(char c) {  
  77.         if (c >= FULL_WIDTH_FIRST && c <= FULL_WIDTH_LAST) {  
  78.             return HALF_WIDTH_KANA[c - FULL_WIDTH_FIRST];  
  79.         } else if(c == 'ー') {  
  80.             return "-";  
  81.         } else {  
  82.             return String.valueOf(c);  
  83.         }  
  84.     }  
  85.   
  86.     public static String convertKanaFull2Half(CharSequence cs) {  
  87.         StringBuffer sb = new StringBuffer();  
  88.         for (int i = 0; i < cs.length(); i++) {  
  89.             sb.append(convertKanaFull2Half(cs.charAt(i)));  
  90.         }  
  91.         return sb.toString();  
  92.     }  
  93. }  






*1 仮面ライダーウィザードでぐぐってください。





0 件のコメント:

コメントを投稿