Hide soft keyboard

Скрытие soft keyboard

При тапе EditText открывается soft keyboard, однако автоматически она не закрывается. Если не закрыть клавиатуру программно, она будет показана на всех остальных фрагментах. При работе с эмулятором, можно упустить этот момент, т.к. в эмуляторе soft keyboard открывается отдельно.

Анализ реализации hide soft keyboard


  • скрытие при потере фокуса
  • скрытие при onPause, onStop
  • скрытие при нажатии на другие элементы экрана
  • скрытие при scroll контента
  • скрытие при onClick спец. кнопке на клаве (в edit text)

Обзор open sourse

Alkee

Управление клавиатурой сделано в виде расширения фрагмента. Клавиатура скрывается после добавления задачи.

FragmentExtensions.kt


/**
 * Hides the opened soft keyboard from the [Fragment].
 */
fun Fragment.showKeyboard() {
    val imm = context?.getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager
    imm?.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
}

/**
 * Hides the opened soft keyboard from the [Fragment].
 */
fun Fragment.hideKeyboard() {
    val imm = context?.getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager
    imm?.hideSoftInputFromWindow(view?.windowToken, 0)
}

Открытие клавиатуры при запуске фрагмента

CategoryAddFragment

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Timber.d("onViewCreated()")

        openSoftKeyboard()
        initComponents()
}
    
private fun openSoftKeyboard() {
        edittext_categorynew_description.requestFocus()
        showKeyboard()
}

Скрытие клавиатуры после ввода задачи

Cделано вместе с задержкой для плавной анимации.

TaskListFragment

private fun onInsertTask(description: String) {
        hideKeyboard()
        withDelay(INSERT_DELAY) {        viewModel.addTask(description)}
}

companion object {
        private const val INSERT_DELAY = 200L
    }    

HandlerExtensions.kt

 /**
 * Runs a code block with a given delay.
 *
 * @param delay the delay (in milliseconds) until the code block will be executed
 * @param func the function to be executed
 */
fun withDelay(delay: Long, func: () -> Unit) {
    Handler().postDelayed({ func() }, delay)
}

SimpleNote

Скрытие клавитуры при переключение viewPager. Клавиатура появляется в onResume, если есть фокус на content и скрывается в onPause. NoteEditorFragment.

  mViewPager.addOnPageChangeListener(
                new NoteEditorViewPager.OnPageChangeListener() {
                    @Override
                    public void onPageSelected(int position) {
                        if (position == INDEX_TAB_PREVIEW) {
                            DisplayUtils.hideKeyboard(mViewPager);
                        }

Скрытие и показ клавиатуры

    @Override
    public void onResume() {
        super.onResume();
        if (mContentEditText != null) {
            mContentEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, PrefUtils.getFontSize(requireContext()));

            if (mContentEditText.hasFocus()) {
                showSoftKeyboard();
            }
        }
    }

    private void showSoftKeyboard() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                if (getActivity() == null) {
                    return;
                }

                InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                if (inputMethodManager != null) {
                    inputMethodManager.showSoftInput(mContentEditText, 0);
                }
            }
        }, 100);
    }

    @Override
    public void onPause() {
        super.onPause();  // Always call the superclass method first
        mIsPaused = true;

        // Hide soft keyboard if it is showing...
        DisplayUtils.hideKeyboard(mContentEditText);
        saveNote();
        AppLog.add(Type.SCREEN, "Paused (NoteEditorFragment)");
    }

        /**
     * Hides the keyboard for the given {@link View}.  Since no {@link InputMethodManager} flag is
     * used, the keyboard is forcibly hidden regardless of the circumstances.
     */
      public static void hideKeyboard(@Nullable final View view) {
        if (view == null) {
            return;
        }

        InputMethodManager inputMethodManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

        if (inputMethodManager != null) {
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    }

### Tasks Скрытие клавиатуры onMenuItemClick, onClickFab, closeSearch

      override fun onMenuItemClick(item: MenuItem): Boolean {
        AndroidUtilities.hideKeyboard(activity)

В новом фрагменте.

  private var showKeyboard = false
    
  override fun onCreate(savedInstanceState: Bundle?) {

        if (savedInstanceState == null) {
            showKeyboard = model.isNew && isNullOrEmpty(model.title)
        }
  }
    
  override fun onResume() {
        super.onResume()

        if (showKeyboard) {
            binding.title.requestFocus()
            val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            imm.showSoftInput(binding.title, InputMethodManager.SHOW_IMPLICIT)
        }
    }
    

### Lawnchair Скрытие клавиатуры onScroll и перенос фокуса на list_results

   listResults.addOnScrollListener(object : RecyclerView.OnScrollListener() {

   override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                if (dy != 0) {
                    hideKeyboard()
                }
            }
        })
        
  private fun hideKeyboard() {
        val view = currentFocus ?: return
        val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(view.windowToken, 0)

        list_results.requestFocus()
    }

Тестирование Espresso

Добавляем метод для проверки открытия клавиатуры в базовый экран.

fun isKeyboardOpen(): Boolean {
val checkKeyboardCmd = "dumpsys input_method | grep mInputShown"

      try {
          return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
              .executeShellCommand(checkKeyboardCmd).contains("mInputShown=true")
      } catch (e: IOException) {
          throw RuntimeException("Keyboard check failed", e)
      }
}

Примеры тестов

    @Test
    fun hideKeyBoardWhenClickOnCatalogBack() {
//  Given
        val screen = navigateToListDetailedEdit()
        screen.enterText("example")
        screen.clickOnPlus()
        sleep(500)
//  When
        screen.clickOnOCatalogOpen()
        sleep(500)
//   Then
        assertFalse(screen.isKeyboardOpen())

        device.pressBack()
        assertFalse(screen.isKeyboardOpen())
    }

Ручное тестирование

  • на genumotion нужно вручную показывать клавиатуру
  • методы onBackPressed (кнопка на эмуляторе) закрывает клавиатуру по умолчанию
  • при нажатии на back arrow на toolbar, автоматически не закрывает

Анализ поведения программ

  show onBackPressed loosing focus open other fragment
Simple note after tap hide show hide
Google keep after tap hide show по-разному
TickTick after tap hide hide hide
Alkee onViewCreated hide hide hide
Сообщения при запуске hide show по-разному
  • Сообщения: keyboard show -> display other a screen -> on back -> display keyboard
  • Google keep: keyboard show -> display bottom sheet-> on back -> display keyboard

Выводы по использованию

  • Для быстрого ввода информации, удобнее когда клавиатура не скрывается.

  • Если форма предполагает одну форму для редактирования, то логичнее клавиатуру отрывать сразу, не после тапа.

  • On back сначала закрывается клавиатура
  • Hide keyboard при открытии диалогов, переходе на другие фрагменты