Calco
Calco

Reputation: 1460

Hiding ‘Bottom Navigation Bar’ whilst keyboard is present - Android

I have a small demo chat UI application. This application has a bottom navigation bar. I need the bottom navigation bar to hide when the keyboard appears.

Here is an example of the chat UI

As you can see when you click in the EditText element, the keyboard appears but the bottom navigation bar stays visible. I have tried methods such as this measurement method, but the UI elements flicker like this.

Is there a proper way to hide the bottom navigation bar when the keyboard is visible?

EDIT: In the below activity you can see where I set the keyboard listener to adjust the position of UI elements when the keyboard is determined as being visible.

This is my activity code, uses setKeyboardListener method from the above link and set it in onCreateView:

package uk.cal.codename.projectnedry.TeamChatFragment;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Layout;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.roughike.bottombar.BottomBar;

import java.util.ArrayList;

import butterknife.BindView;
import butterknife.ButterKnife;
import uk.cal.codename.projectnedry.R;
import uk.cal.codename.projectnedry.TeamChatFragment.ListAdapter.TeamChatListAdapter;
import uk.demo.cal.genericmodelviewpresenter.GenericMvp.GenericMvpFragment;

import static android.view.View.GONE;

/**
 * A simple {@link Fragment} subclass.
 * Activities that contain this fragment must implement the
 * {@link TeamChatView.OnFragmentInteractionListener} interface
 * to handle interaction events.
 * Use the {@link TeamChatView#newInstance} factory method to
 * create an instance of this fragment.
 */
public class TeamChatView extends GenericMvpFragment implements TeamChatContract.RequiredViewOps {

    private OnFragmentInteractionListener mListener;
    @BindView(R.id.teamChatList)
    RecyclerView mTeamChatRecyclerView;
    @BindView(R.id.teamChatSendButton)
    ImageButton mTeamChatSendButton;
    @BindView(R.id.messageTextInput)
    EditText mMessageTextInput;
    TeamChatListAdapter mTeamChatListAdapter;
    TeamChatListAdapter.ClickListener mTeamChatListClickListener;
    private ArrayList<String> mTestMessageList;

    public interface OnKeyboardVisibilityListener {
        void onVisibilityChanged(boolean visible);
    }

    public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
        final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);

        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            private boolean wasOpened;

            private final int DefaultKeyboardDP = 100;

            // From @nathanielwolf answer...  Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
            private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);

            private final Rect r = new Rect();

            @Override
            public void onGlobalLayout() {
                // Convert the dp to pixels.
                int estimatedKeyboardHeight = (int) TypedValue
                        .applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, activityRootView.getResources().getDisplayMetrics());

                // Conclude whether the keyboard is shown or not.
                activityRootView.getWindowVisibleDisplayFrame(r);
                int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
                boolean isShown = heightDiff >= estimatedKeyboardHeight;

                if (isShown == wasOpened) {
                    Log.d("Keyboard state", "Ignoring global layout change...");
                    return;
                }

                wasOpened = isShown;
                listener.onVisibilityChanged(isShown);
            }
        });
    }

    public TeamChatView() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment TeamChatView.
     */
    public static TeamChatView newInstance() {
        TeamChatView fragment = new TeamChatView();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(TeamChatPresenter.class, TeamChatModel.class, savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        final View view = inflater.inflate(R.layout.fragment_team_chat_view, container, false);
        this.mUnbinder = ButterKnife.bind(this, view);

        mTestMessageList = new ArrayList<>();
        this.mTeamChatListAdapter = new TeamChatListAdapter(mTestMessageList);
        this.mTeamChatRecyclerView.setAdapter(this.mTeamChatListAdapter);
        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
        this.mTeamChatRecyclerView.setLayoutManager(linearLayoutManager);

        this.mTeamChatSendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(!String.valueOf(mMessageTextInput.getText()).equals("")) {
                    getSpecificImpOfGenericPresenter().sendMessage(String.valueOf(mMessageTextInput.getText()));
                    mMessageTextInput.setText("");
                    mTeamChatRecyclerView.smoothScrollToPosition(mTestMessageList.size());
                }
            }
        });

        setKeyboardListener(new OnKeyboardVisibilityListener(){
            @Override
            public void onVisibilityChanged(boolean visible) {
                RelativeLayout contentFrame = (RelativeLayout) getActivity().findViewById(R.id.content_company_navigation);
                BottomBar lowerNavigationBar = (BottomBar) getActivity().findViewById(R.id.bottomBar);
                if (visible) { // if more than 100 pixels, its probably a keyboard...
                    lowerNavigationBar.setVisibility(GONE);
                    contentFrame.setPadding(0, 0, 0, 0);
                    mTeamChatRecyclerView.smoothScrollToPosition(mTestMessageList.size());
                } else {
                    contentFrame.setPadding(0, 0, 0, convertDpToPixel(60, getContext()));
                    mTeamChatRecyclerView.smoothScrollToPosition(mTestMessageList.size());
                    lowerNavigationBar.setVisibility(View.VISIBLE);
                }
            }
        });
        return view;
    }

    /**
     * This method converts dp unit to equivalent pixels, depending on device density.
     *
     * @param dp A value in dp (density independent pixels) unit. Which we need to convert into pixels
     * @param context Context to get resources and device specific display metrics
     * @return A float value to represent px equivalent to dp depending on device density
     */
    public static int convertDpToPixel(float dp, Context context){
        Resources resources = context.getResources();
        DisplayMetrics metrics = resources.getDisplayMetrics();
        int px = (int) (dp * ((float)metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
        return px;
    }

    public void addToTestMessageList(String str){
        this.mTestMessageList.add(str);
        this.mTeamChatListAdapter.notifyDataSetChanged();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
       // getView().getViewTreeObserver().removeGlobalOnLayoutListener(test);
    }

    @Override
    public TeamChatPresenter getSpecificImpOfGenericPresenter() {
        return (TeamChatPresenter) this.mPresenter;
    }
}

This is my XML layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="uk.cal.codename.projectnedry.TeamChatFragment.TeamChatView">

    <android.support.v7.widget.RecyclerView
        android:layout_above="@+id/chatViewMessageEntryLayout"
        android:id="@+id/teamChatList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:isScrollContainer="false"
        android:paddingTop="10dp"
        android:scrollbars="vertical" />    

    <RelativeLayout
        android:id="@+id/chatViewMessageEntryLayout"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <View
            android:id="@+id/chatViewMessageEntrySeperator"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#e3e3e8" />

        <EditText
            android:id="@+id/messageTextInput"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:layout_below="@+id/chatViewMessageEntrySeperator"
            android:layout_toLeftOf="@+id/teamChatSendButton"
            android:background="@android:color/transparent"
            android:hint="Enter message"
            android:inputType="textCapSentences|textMultiLine"
            android:maxLength="1000"
            android:maxLines="4"
            android:paddingLeft="10dp" />

        <ImageButton
            android:id="@+id/teamChatSendButton"
            android:layout_width="50dp"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:layout_gravity="center"
            android:background="#00B9EF"
            android:src="@drawable/ic_send_white_24dp" />

    </RelativeLayout>

</RelativeLayout>

Upvotes: 57

Views: 61443

Answers (14)

Greta Radisauskaite
Greta Radisauskaite

Reputation: 276

For API 21+:

Firstly, set "android:windowSoftInputMode" for the activity to "adjustResize" in AndroidManifest.xml

Secondly, in your acticity's onCreate method register the following listener:

window.decorView.setOnApplyWindowInsetsListener { view, insets ->
    val insetsCompat = toWindowInsetsCompat(insets, view)
    val isImeVisible = insetsCompat.isVisible(WindowInsetsCompat.Type.ime())
    // below line, do the necessary stuff:
    dataBinding.bottomNavigation.visibility = if (isImeVisible) View.GONE else View.VISIBLE
    view.onApplyWindowInsets(insets)
}

Upvotes: 4

iCantC
iCantC

Reputation: 3180

Actively listen for the Keyboard(IME) open/close events and show/hide bottom navigation accordingly.

We can make use of the WindowInsetsCompat API which makes this job effortless.

Step 1: Make sure you have migrated to AndroidX

Step 2: In your Fragment inside onCreateView add the listener for Keyboard(IME) events


ViewCompat.setOnApplyWindowInsetsListener(window.decorView.rootView) { _, insets ->
 
   //This lambda block will be called, every time keyboard is opened or closed

    
    val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
    if(imeVisible){ 
     //Now show-hide bottom navigation accordingly 
    } 

    insets
}

This is all you need🎉

For more info on window insets, you can check out this in-depth article

NOTE:
Earlier detecting Keyboard open/close events was not an easy task. Android Devs resorted to all types of hacks to accomplish this. But after decades of requests, prayers and rants Google finally came up with WindowInsets API. Thank you, Google🙏🏻

Upvotes: 9

Nadeem Aslam
Nadeem Aslam

Reputation: 123

That answer might be helpful for people still looking for solution.

Bottom Navigation bar moves up with keyboard

Upvotes: 1

Bilal Aslam
Bilal Aslam

Reputation: 9

That solution works for me and it also doesn't show overlapped bottom navbar just add this line to your activity:

android:windowSoftInputMode="adjustResize|adjustPan"

Upvotes: -1

Ali Raza
Ali Raza

Reputation: 720

<activity android:name=".Activities.OrderSceenWithOrder" 
   android:windowSoftInputMode="adjustPan"
    >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Upvotes: 1

Anushree
Anushree

Reputation: 98

 private boolean keyboardListenersAttached = false;
private ViewGroup rootLayout;


       @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.fragment_settings, container, false);


    rootLayout = (ViewGroup) view.findViewById(R.id.settings_layout);
    attachKeyboardListeners();

    return view;
}



    private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new 
    ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int heightDiff = rootLayout.getRootView().getHeight() - 
        rootLayout.getHeight();
        if (getActivity() != null) {
            int contentViewTop = 
           getActivity().getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
            LocalBroadcastManager broadcastManager = 
            LocalBroadcastManager.getInstance(getContext());
            Rect r = new Rect();
            rootLayout.getWindowVisibleDisplayFrame(r);
            int screenHeight = rootLayout.getRootView().getHeight();

            // r.bottom is the position above soft keypad or device button.
            // if keypad is shown, the r.bottom is smaller than that before.
            int keypadHeight = screenHeight - r.bottom;
            if (keypadHeight > screenHeight * 0.15) {
                onHideKeyboard();

                Intent intent = new Intent("KeyboardWillHide");
                broadcastManager.sendBroadcast(intent);
            } else {
                int keyboardHeight = heightDiff - contentViewTop;
                onShowKeyboard(keyboardHeight);

                Intent intent = new Intent("KeyboardWillShow");
                intent.putExtra("KeyboardHeight", keyboardHeight);
                broadcastManager.sendBroadcast(intent);
            }
        }else {

        }
    }
};

protected void onShowKeyboard(int keyboardHeight) {
    System.out.println("keyboard is shown");
    dashboardActivity.bottomNavigationView.setVisibility(View.VISIBLE);
    dashboardActivity.fab.setVisibility(View.VISIBLE);

}
protected void onHideKeyboard() {
    System.out.println("keyboard is hide");
    dashboardActivity.bottomNavigationView.setVisibility(View.INVISIBLE);
    dashboardActivity.fab.setVisibility(View.INVISIBLE);
}

protected void attachKeyboardListeners() {
    if (keyboardListenersAttached) {
        return;
    }

    rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);
    keyboardListenersAttached = true;
}

in Manifest.xml file add

<activity
        android:name=".Activity.DashboardActivity"
        android:windowSoftInputMode="adjustResize"

         />

Upvotes: 3

Praveen Kumar Verma
Praveen Kumar Verma

Reputation: 3460

The easiest implementation, Add AndroidManifest.xml in

<activity android:windowSoftInputMode="adjustPan"/>

hopefully this helps someone out. Enjoy !

Upvotes: 132

Rajnish Sharma
Rajnish Sharma

Reputation: 388

Add this onResume() in your fragment

@Override
public void onResume() {
    Objects.requireNonNull(getActivity()).getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
    super.onResume();
}

Hope this helps someone!

Upvotes: -1

Kasasira
Kasasira

Reputation: 403

Just add the attribute below to every activity in your AndroidManifest.xml file.

<activity
    android:name=".MainActivity"
    android:windowSoftInputMode="stateAlwaysHidden|adjustPan" />

Upvotes: 6

mahesh kumar
mahesh kumar

Reputation: 179

Add this line in onResume() of your Activity or Fragment.

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);

It is worked for me. Just try it once.

Upvotes: 10

Maksudul Hasan Raju
Maksudul Hasan Raju

Reputation: 865

you just add this code in your manifest like this way..

 <activity android:name=".MainActivity"
        android:windowSoftInputMode="adjustPan">

this works for me.. happy coding

Upvotes: 34

ajay singh
ajay singh

Reputation: 179

In my case, I am using DrawerLayout as a parent view with some layout content and A Navigation Bottom Bar. In Manifest file add "android:windowSoftInputMode="adjustPan" this TAG with Activity and this working fine.

Upvotes: 2

Calco
Calco

Reputation: 1460

I ended up using the height measuring method that seems to be the standard way of soft keyboard detection which is described in this answer. However, I used this library's implementation of it, as it is still the same ViewTreeObserver.OnGlobalLayoutListener method, implemented well, and allowed me to abstract the code out of my applications main codebase.

When this keyboard visibility listener is triggered, I then hide/show the bottom navigation bar (which I have explained here).

Upvotes: 16

tahsinRupam
tahsinRupam

Reputation: 6405

Add the attribute : android:windowSoftInputMode="adjustResize"" in your manifest inside activity tag:

 <activity
        android:name=".YourActivity"
        android:windowSoftInputMode="adjustResize"/>

NB: I suggest, You should use NestedScrollView as the parent layout.

Hope this helps.

Upvotes: 2

Related Questions