2014年11月18日火曜日

Android Enter キーでフォーカスを移動するのは android:nextFocusDown ? android:nextFocusForward ?

singleLine の EditText で、Enter キーが押されたときに次のフィールドにフォーカスが移動すると使いやすいですよね。
ある程度は勝手にフォーカスが移動するようになるのですが、細かい部分は自分で指定しないと思い通りに移動してくれません。

例えば、次のように EditText が LinearLayout で縦に並んでいる場合、edit_text1 で Enter キーを押すと、edit_text2 へ移動してくれます。
  1. <LinearLayout  
  2.   android:layout_width="match_parent"  
  3.   android:layout_height="wrap_content"  
  4.   android:orientation="vertical">  
  5.   
  6.   <EditText  
  7.     android:id="@+id/edit_text1"  
  8.     android:layout_width="match_parent"  
  9.     android:layout_height="wrap_content"  
  10.     android:inputType="text" />  
  11.   
  12.   <EditText  
  13.     android:id="@+id/edit_text2"  
  14.     android:layout_width="match_parent"  
  15.     android:layout_height="wrap_content"  
  16.     android:inputType="text" />  
  17.   
  18.   <EditText  
  19.     android:id="@+id/edit_text3"  
  20.     android:layout_width="match_parent"  
  21.     android:layout_height="wrap_content"  
  22.     android:inputType="text" />  
  23.   
  24. </LinearLayout>  


一方、次のように、edit_text1 と edit_text2 が横に並び、その下に edit_text3 がある場合、edit_text1 で Enter キーを押すと edit_text3 に移動します。
  1. <LinearLayout  
  2.   android:layout_width="match_parent"  
  3.   android:layout_height="wrap_content"  
  4.   android:orientation="vertical">  
  5.   
  6.   <LinearLayout  
  7.     android:layout_width="match_parent"  
  8.     android:layout_height="wrap_content"  
  9.     android:orientation="horizontal">  
  10.   
  11.     <EditText  
  12.       android:id="@+id/edit_text1"  
  13.       android:layout_width="0dp"  
  14.       android:layout_height="wrap_content"  
  15.       android:layout_weight="1"  
  16.       android:inputType="text" />  
  17.   
  18.     <EditText  
  19.       android:id="@+id/edit_text2"  
  20.       android:layout_width="0dp"  
  21.       android:layout_height="wrap_content"  
  22.       android:layout_weight="1"  
  23.       android:inputType="text" />  
  24.   
  25.   </LinearLayout>  
  26.   
  27.   <EditText  
  28.     android:id="@+id/edit_text3"  
  29.     android:layout_width="match_parent"  
  30.     android:layout_height="wrap_content"  
  31.     android:inputType="text" />  
  32.   
  33. </LinearLayout>  


edit_text1 で Enter キーを押したときに edit_text2 に移動させるには、方法が2つあります。

1. android:nextFocusDown を指定する
  1. <EditText  
  2.   android:id="@+id/edit_text1"  
  3.   android:layout_width="0dp"  
  4.   android:layout_height="wrap_content"  
  5.   android:layout_weight="1"  
  6.   android:inputType="text"  
  7.   android:nextFocusDown="@+id/edit_text2" />  


2. android:nextFocusForward と android:imeOptions を指定する
  1. <EditText  
  2.   android:id="@+id/edit_text1"  
  3.   android:layout_width="0dp"  
  4.   android:layout_height="wrap_content"  
  5.   android:layout_weight="1"  
  6.   android:inputType="text"  
  7.   android:nextFocusForward="@+id/edit_text2"  
  8.   android:imeOptions="actionNext" />  


次へ、なんだから android:nextFocusForward だろうと思いきや、こちらは android:imeOptions(他にも android:imeActionLabel とか android:imeActionId などの指定でも可)を指定するという workaround が必要になります。バグじゃないかと思うんだけど。。。
ちなみに、Tab を押したときには android:nextFocusForward で指定された id の View に移動します。


解説

キモのメソッドは TextView.onCreateInputConnection() と TextView.onEditorAction() です。
4.x はそれぞれ微妙にコードが違うのですが、4.4.2 (API Level 19)のコードで説明します。

ちょっと長いですが、android:singleLine="true" や android:inputType="text" が指定されている EditText では、focusSearch(FOCUS_DOWN) で次に移動できる View を探します(ここでは FOCUS_DOWN なのよね...)。

移動できる View があって、IME_ACTION が明示的に指定されていない場合、IME_ACTION として EditorInfo.IME_ACTION_NEXT が指定されます。
  1. @Override  
  2. public InputConnection onCreateInputConnection(EditorInfo outAttrs) {  
  3.     if (onCheckIsTextEditor() && isEnabled()) {  
  4.         mEditor.createInputMethodStateIfNeeded();  
  5.         outAttrs.inputType = getInputType();  
  6.         if (mEditor.mInputContentType != null) {  
  7.             outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;  
  8.             outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;  
  9.             outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;  
  10.             outAttrs.actionId = mEditor.mInputContentType.imeActionId;  
  11.             outAttrs.extras = mEditor.mInputContentType.extras;  
  12.         } else {  
  13.             outAttrs.imeOptions = EditorInfo.IME_NULL;  
  14.         }  
  15.         if (focusSearch(FOCUS_DOWN) != null) {  
  16.             outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;  
  17.         }  
  18.         if (focusSearch(FOCUS_UP) != null) {  
  19.             outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;  
  20.         }  
  21.         if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)  
  22.                 == EditorInfo.IME_ACTION_UNSPECIFIED) {  
  23.             if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {  
  24.                 // An action has not been set, but the enter key will move to  
  25.                 // the next focus, so set the action to that.  
  26.                 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;  
  27.             } else {  
  28.                 // An action has not been set, and there is no focus to move  
  29.                 // to, so let's just supply a "done" action.  
  30.                 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;  
  31.             }  
  32.             if (!shouldAdvanceFocusOnEnter()) {  
  33.                 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;  
  34.             }  
  35.         }  
  36.         if (isMultilineInputType(outAttrs.inputType)) {  
  37.             // Multi-line text editors should always show an enter key.  
  38.             outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;  
  39.         }  
  40.         outAttrs.hintText = mHint;  
  41.         if (mText instanceof Editable) {  
  42.             InputConnection ic = new EditableInputConnection(this);  
  43.             outAttrs.initialSelStart = getSelectionStart();  
  44.             outAttrs.initialSelEnd = getSelectionEnd();  
  45.             outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());  
  46.             return ic;  
  47.         }  
  48.     }  
  49.     return null;  
  50. }  


次に onEditorAction() です。

ict という変数の null チェックをしているのがポイントです。この変数が null じゃない場合、まず EditorActionListener の処理をします。
EditorActionListener がセットされているかをチェックし、セットされている場合は EditorActionListener.onEditorAction() を呼び、戻り値が true ならそこで処理を終了します。

EditorActionListener がセットされていない、またはセットされていても戻り値が false の場合は、actionCode に応じた処理をします。
actionCode が IME_ACTION_NEXT の場合 focusSearch(FOCUS_FORWARD) で View を探して、対応する View があればそれに対して requestFocus() を呼びます(ここは FOCUS_FORWARD)。
IME_ACTION_DONE の場合はキーボードを閉じます。

ict が null のときや、上記に対応する View がない場合などは、その下の処理が行われます。つまり、KeyEvent.KEYCODE_ENTER の KeyEvent.ACTION_DOWN と KeyEvent.ACTION_UP を ViewRootImpl.dispatchKeyFromIme() で割り当てます。
これにより、onKeyDown() と onKeyUp() が呼ばれることになります。

ict が null かどうかによらず actionCode に対応した処理部分を呼ぶようにするべきなんじゃないかなと思いました。。。
  1. public void onEditorAction(int actionCode) {  
  2.     final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;  
  3.     if (ict != null) {  
  4.         if (ict.onEditorActionListener != null) {  
  5.             if (ict.onEditorActionListener.onEditorAction(this,  
  6.                     actionCode, null)) {  
  7.                 return;  
  8.             }  
  9.         }  
  10.   
  11.         // This is the handling for some default action.  
  12.         // Note that for backwards compatibility we don't do this  
  13.         // default handling if explicit ime options have not been given,  
  14.         // instead turning this into the normal enter key codes that an  
  15.         // app may be expecting.  
  16.         if (actionCode == EditorInfo.IME_ACTION_NEXT) {  
  17.             View v = focusSearch(FOCUS_FORWARD);  
  18.             if (v != null) {  
  19.                 if (!v.requestFocus(FOCUS_FORWARD)) {  
  20.                     throw new IllegalStateException("focus search returned a view " +  
  21.                             "that wasn't able to take focus!");  
  22.                 }  
  23.             }  
  24.             return;  
  25.   
  26.         } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {  
  27.             View v = focusSearch(FOCUS_BACKWARD);  
  28.             if (v != null) {  
  29.                 if (!v.requestFocus(FOCUS_BACKWARD)) {  
  30.                     throw new IllegalStateException("focus search returned a view " +  
  31.                             "that wasn't able to take focus!");  
  32.                 }  
  33.             }  
  34.             return;  
  35.   
  36.         } else if (actionCode == EditorInfo.IME_ACTION_DONE) {  
  37.             InputMethodManager imm = InputMethodManager.peekInstance();  
  38.             if (imm != null && imm.isActive(this)) {  
  39.                 imm.hideSoftInputFromWindow(getWindowToken(), 0);  
  40.             }  
  41.             return;  
  42.         }  
  43.     }  
  44.   
  45.     ViewRootImpl viewRootImpl = getViewRootImpl();  
  46.     if (viewRootImpl != null) {  
  47.         long eventTime = SystemClock.uptimeMillis();  
  48.         viewRootImpl.dispatchKeyFromIme(  
  49.                 new KeyEvent(eventTime, eventTime,  
  50.                 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 00,  
  51.                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,  
  52.                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE  
  53.                 | KeyEvent.FLAG_EDITOR_ACTION));  
  54.         viewRootImpl.dispatchKeyFromIme(  
  55.                 new KeyEvent(SystemClock.uptimeMillis(), eventTime,  
  56.                 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 00,  
  57.                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,  
  58.                 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE  
  59.                 | KeyEvent.FLAG_EDITOR_ACTION));  
  60.     }  
  61. }  


onKeyUp() での KeyEvent.KEYCODE_ENTER の処理は onEditorAction() と同じような感じなのですが、focusSearch() & requestFocus() する対象が FOCUS_DOWN です(こっちは FOCUS_DOWN !)
  1. @Override  
  2. public boolean onKeyUp(int keyCode, KeyEvent event) {  
  3.     if (!isEnabled()) {  
  4.         return super.onKeyUp(keyCode, event);  
  5.     }  
  6.   
  7.     if (!KeyEvent.isModifierKey(keyCode)) {  
  8.         mPreventDefaultMovement = false;  
  9.     }  
  10.   
  11.     switch (keyCode) {  
  12.         ...  
  13.   
  14.         case KeyEvent.KEYCODE_ENTER:  
  15.             if (event.hasNoModifiers()) {  
  16.                 if (mEditor != null && mEditor.mInputContentType != null  
  17.                         && mEditor.mInputContentType.onEditorActionListener != null  
  18.                         && mEditor.mInputContentType.enterDown) {  
  19.                     mEditor.mInputContentType.enterDown = false;  
  20.                     if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(  
  21.                             this, EditorInfo.IME_NULL, event)) {  
  22.                         return true;  
  23.                     }  
  24.                 }  
  25.   
  26.                 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0  
  27.                         || shouldAdvanceFocusOnEnter()) {  
  28.                     /* 
  29.                      * If there is a click listener, just call through to 
  30.                      * super, which will invoke it. 
  31.                      * 
  32.                      * If there isn't a click listener, try to advance focus, 
  33.                      * but still call through to super, which will reset the 
  34.                      * pressed state and longpress state.  (It will also 
  35.                      * call performClick(), but that won't do anything in 
  36.                      * this case.) 
  37.                      */  
  38.                     if (!hasOnClickListeners()) {  
  39.                         View v = focusSearch(FOCUS_DOWN);  
  40.   
  41.                         if (v != null) {  
  42.                             if (!v.requestFocus(FOCUS_DOWN)) {  
  43.                                 throw new IllegalStateException(  
  44.                                         "focus search returned a view " +  
  45.                                         "that wasn't able to take focus!");  
  46.                             }  
  47.   
  48.                             /* 
  49.                              * Return true because we handled the key; super 
  50.                              * will return false because there was no click 
  51.                              * listener. 
  52.                              */  
  53.                             super.onKeyUp(keyCode, event);  
  54.                             return true;  
  55.                         } else if ((event.getFlags()  
  56.                                 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {  
  57.                             // No target for next focus, but make sure the IME  
  58.                             // if this came from it.  
  59.                             InputMethodManager imm = InputMethodManager.peekInstance();  
  60.                             if (imm != null && imm.isActive(this)) {  
  61.                                 imm.hideSoftInputFromWindow(getWindowToken(), 0);  
  62.                             }  
  63.                         }  
  64.                     }  
  65.                 }  
  66.                 return super.onKeyUp(keyCode, event);  
  67.             }  
  68.             break;  
  69.     }  
  70.   
  71.     ...  
  72. }  


つまり、ict が null になってしまうときは android:nextFocusDown に指定したものに、null にならないときは android:nextFocusForward に指定したものに移動します。
ややこし。。。
明示的に android:imeOptions="actionNext" を指定するのは、この ict が null にならないようにするためです。 (ict が null にならないようにする方法は他にもあって、android:imeActionLabel を指定するとかいくつかあります。)


2014年11月17日月曜日

Android CheckBox 画像の正しいカスタマイズ方法

各テーマでのチェックボックスのスタイルは android:checkboxStyle で指定されています。
以下は 4.0.3 のコードですが、この部分は 3.0 で Theme.Holo が追加されて以降特に変わっていません。

http://tools.oesf.biz/android-4.0.3_r1.0/xref/frameworks/base/core/res/res/values/themes.xml
  1. <style name="Theme">  
  2.   ...  
  3.   <item name="checkboxStyle">@android:style/Widget.CompoundButton.CheckBox</item>  
  4.   ...  
  5. </style>  
  6. ...  
  7. <style name="Theme.Holo">  
  8.   ...  
  9.   <item name="checkboxStyle">@android:style/Widget.Holo.CompoundButton.CheckBox</item>  
  10.   ...  
  11. </style>  
  12. ...  
  13. <style name="Theme.Holo">  
  14.   ...  
  15.   <item name="checkboxStyle">@android:style/Widget.Holo.CompoundButton.CheckBox</item>  
  16.   ...  
  17. </style>  
  18. ...  
  19. <style name="Theme.Holo.Light" parent="Theme.Light">  
  20.   ...  
  21.   <item name="checkboxStyle">@android:style/Widget.Holo.Light.CompoundButton.CheckBox</item>  
  22.   ...  
  23. </style>  


Widget.Holo.CompoundButton.CheckBox と Widget.Holo.Light.CompoundButton.CheckBox を見ると、Widget.CompoundButton.CheckBox をそのまま継承しているだけです。

http://tools.oesf.biz/android-4.0.3_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#1003
  1. <style name="Widget.Holo.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">  
  2. </style>  
  3.   
  4. <style name="Widget.Holo.Light.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">  
  5. </style>  
つまり、Theme であろうが Theme.Holo であろうが Widget.CompoundButton.CheckBox が使われるということです。


このスタイルですが、4.1 と 4.2 で微妙に変わります。

4.1まで

http://tools.oesf.biz/android-4.1.2_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#345
  1. <style name="Widget.CompoundButton.CheckBox">  
  2.     <item name="android:background">@android:drawable/btn_check_label_background</item>  
  3.     <item name="android:button">?android:attr/listChoiceIndicatorMultiple</item>  
  4. </style>  

4.2以降

http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#345
  1. <style name="Widget.CompoundButton.CheckBox">  
  2.     <item name="android:button">?android:attr/listChoiceIndicatorMultiple</item>  
  3. </style>  
4.1 までは Widget.CompoundButton.CheckBox で android:background が指定されていますが、4.2 以降ではなくなっています。


実は 4.2 で paddingLeft の使い方が変わりました。Rtl に対応するためだと思われます。

4.1 までは左端からテキストの間が paddingLeft でした。そのため、チェックボックス画像分の paddingLeft を指定するために、背景画像が使われていました。
一方、4.2 からはチェックボックス画像とテキストの間が paddingLeft になりました。



チェックボックスをカスタマイズする場合

この違いのため、チェックボックスをカスタマイズする場合は注意が必要です。

例えばチェックボックス画像を 32 x 32 dp で作成し、画像とテキストの間を 4dp あけたいとしたら、次のように values/dimens.xml と values-v17/dimens.xml を用意する必要があります。

values/dimens.xml
  1. <resources>  
  2.     <!-- 32 + 4 = 36 -->  
  3.     <dimen name="check_button_padding_left">36dp</dimen>  
  4. </resources>  
values-v17/dimens.xml
  1. <resources>  
  2.     <dimen name="check_button_padding_left">4dp</dimen>  
  3. </resources>  



チェックボックスの画像を変えるには、checkboxStyle に指定するスタイルで android:button に drawable を指定するか、テーマで android:listChoiceIndicatorMultiple に drawable を指定します。

values/styles.xml
  1. <resources>  
  2.   
  3.     <style name="AppTheme" parent="Theme.Holo.Light">  
  4.         <item name="android:checkboxStyle">@style/CheckBoxStyle</item>  
  5.     </style>  
  6.   
  7.     <style name="CheckBoxStyle" parent="android:Widget.CompoundButton.CheckBox">  
  8.         <item name="android:button">@drawable/my_custom_checkbox</item>  
  9.         <item name="android:paddingLeft">@dimen/check_button_padding_left</item>  
  10.     </style>  
  11. </resources>  
or
  1. <resources>  
  2.   
  3.     <style name="AppTheme" parent="Theme.Holo.Light">  
  4.         <item name="android:listChoiceIndicatorMultiple">@drawable/my_custom_checkbox</item>  
  5.         <item name="android:checkboxStyle">@style/CheckBoxStyle</item>  
  6.     </style>  
  7.   
  8.     <style name="CheckBoxStyle" parent="android:Widget.CompoundButton.CheckBox">  
  9.         <item name="android:paddingLeft">@dimen/check_button_padding_left</item>  
  10.     </style>  
  11. </resources>  





おまけ : CompoundButton.java の変更について

onDraw() については、Rtl のとき右端に描画されるようになっているだけで、paddingLeft の扱いは変わっていません。

4.1.2

http://tools.oesf.biz/android-4.1.2_r1.0/xref/frameworks/base/core/java/android/widget/CompoundButton.java#228
  1. 227     @Override  
  2. 228     protected void onDraw(Canvas canvas) {  
  3. 229         super.onDraw(canvas);  
  4. 230   
  5. 231         final Drawable buttonDrawable = mButtonDrawable;  
  6. 232         if (buttonDrawable != null) {  
  7. 233             final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;  
  8. 234             final int height = buttonDrawable.getIntrinsicHeight();  
  9. 235   
  10. 236             int y = 0;  
  11. 237   
  12. 238             switch (verticalGravity) {  
  13. 239                 case Gravity.BOTTOM:  
  14. 240                     y = getHeight() - height;  
  15. 241                     break;  
  16. 242                 case Gravity.CENTER_VERTICAL:  
  17. 243                     y = (getHeight() - height) / 2;  
  18. 244                     break;  
  19. 245             }  
  20. 246   
  21. 247             buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);  
  22. 248             buttonDrawable.draw(canvas);  
  23. 249         }  
  24. 250     }  

4.2.0

http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/widget/CompoundButton.java#252
  1. 251     @Override  
  2. 252     protected void onDraw(Canvas canvas) {  
  3. 253         super.onDraw(canvas);  
  4. 254   
  5. 255         final Drawable buttonDrawable = mButtonDrawable;  
  6. 256         if (buttonDrawable != null) {  
  7. 257             final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;  
  8. 258             final int drawableHeight = buttonDrawable.getIntrinsicHeight();  
  9. 259             final int drawableWidth = buttonDrawable.getIntrinsicWidth();  
  10. 260   
  11. 261             int top = 0;  
  12. 262             switch (verticalGravity) {  
  13. 263                 case Gravity.BOTTOM:  
  14. 264                     top = getHeight() - drawableHeight;  
  15. 265                     break;  
  16. 266                 case Gravity.CENTER_VERTICAL:  
  17. 267                     top = (getHeight() - drawableHeight) / 2;  
  18. 268                     break;  
  19. 269             }  
  20. 270             int bottom = top + drawableHeight;  
  21. 271             int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;  
  22. 272             int right = isLayoutRtl() ? getWidth() : drawableWidth;  
  23. 273   
  24. 274             buttonDrawable.setBounds(left, top, right, bottom);  
  25. 275             buttonDrawable.draw(canvas);  
  26. 276         }  
  27. 277     }  
4.2 で padding の扱いが変わった理由は、getCompoundPaddingLeft() と getCompoundPaddingRight() を Override して padding を上書きするようになったからです。
  1. 227     @Override  
  2. 228     public int getCompoundPaddingLeft() {  
  3. 229         int padding = super.getCompoundPaddingLeft();  
  4. 230         if (!isLayoutRtl()) {  
  5. 231             final Drawable buttonDrawable = mButtonDrawable;  
  6. 232             if (buttonDrawable != null) {  
  7. 233                 padding += buttonDrawable.getIntrinsicWidth();  
  8. 234             }  
  9. 235         }  
  10. 236         return padding;  
  11. 237     }  
  12. 238   
  13. 239     @Override  
  14. 240     public int getCompoundPaddingRight() {  
  15. 241         int padding = super.getCompoundPaddingRight();  
  16. 242         if (isLayoutRtl()) {  
  17. 243             final Drawable buttonDrawable = mButtonDrawable;  
  18. 244             if (buttonDrawable != null) {  
  19. 245                 padding += buttonDrawable.getIntrinsicWidth();  
  20. 246             }  
  21. 247         }  
  22. 248         return padding;  
  23. 249     }  
このメソッドは TextView の onMeasure() などから呼ばれています。


2014年11月3日月曜日

Android Studio でよく使うコマンド

command + o : クラス検索で開く
command + shif + o : ファイル名検索で開く
command + e : Recent Files
command + n : Generate...
option + F7 : find usage
option + command + l : 整形
option + command + v : 左辺を挿入
ctrl + option + o : optimize imports
shift + command + @ : 左のタブに移動
shift + command + [ : 右のタブに移動
command + l : Go to Line
command + shift + delete : 前回の編集位置に飛ぶ