2012年10月31日水曜日

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.     } 

0 件のコメント:

コメントを投稿