How to have the soft keyboard cover a view, yet cause other views to take the available space?
Asked Answered
S

2

2

Background

Suppose I have a view that works as the background (MapFragment in my case), and some other views that are the content of the activity.

The problem

Focusing an EditText, it shows the soft keyboard, causing all views to change their sizes, including the background view.

This means that showing the soft keyboard causes the background to "jump" and re-layout itself.

This is especially problematic in my case, as I use MapFragment as the background. That's because I can think of a workaround to detect the keyboard size, and show only the upper area of the background view, but there is little control of the MapFragment in how it looks and work.

Here's a demo of the problem:

enter image description here

Here's a sample xml, used inside the sample of the mapFragment:

basic_demo.xml

<FrameLayout
    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">

    <fragment
        android:id="@+id/map"
        class="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <EditText
        android:id="@android:id/edit"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="20dp"
        android:background="#66ffffff"
        android:digits="0123456789()-+*"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:gravity="center_vertical|left"
        android:imeOptions="actionSearch|flagNoExtractUi"
        android:inputType="phone"
        android:singleLine="true"
        android:textColorHint="#aaa"
        android:textSize="18dp"
        tools:hint="Search Phones ..."/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginBottom="30dp"
        android:background="#66ffffff"
        android:text="This should always be shown, at the bottom"/>
</FrameLayout>

What I've found

I know that we can set the "windowSoftInputMode" flag in the manifest, but this is an all-or-nothing solution. It affects all of the views, and not a single one.

The question

How can I let the soft keyboard change the layout of all views except specific ones?

Is it even possible?


EDIT: looking at "gio"'s solution, it works. Here's a more optimized way to do it:

    View view=...
    mContainerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        final Rect outGlobalRect = new Rect(), outWindowRect = new Rect();

        @Override
        public void onGlobalLayout() {
            mContainerView.getGlobalVisibleRect(outGlobalRect);
            mContainerView.getWindowVisibleDisplayFrame(outWindowRect);
            int marginBottom = outGlobalRect.bottom - outWindowRect.bottom;
            final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) view.getLayoutParams();
            if (layoutParams.bottomMargin == marginBottom)
                return;
            layoutParams.setMargins(0, 0, 0, marginBottom);
            view.requestLayout();
        }
    });
Smithson answered 18/1, 2016 at 9:23 Comment(6)
put this inside onCreateview() of your fragment ,getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);Launalaunce
@UmaKanth I don't understand. getWindow should work on what exactly?Smithson
it's same solution "all-or-nothing"Macdermot
@Macdermot that's what i thought.Smithson
@androiddeveloper could you provide screenshots of your screen? issue and expectedMacdermot
@Macdermot Updated question. in the screenshot, it shows that the background got to be what I need, but the views at the bottom (a TextView in this sample) got hidden by the keyboard, as it covers them. Setting windowSoftInputMode will affect all the views, which means it can't be the solution (but maybe a part of the solution).Smithson
M
1

It's possible according your initial requirements. Idea is to change bottom margin of needed view. Value will be calculated as difference between shown and real height of root view. I wrapped TextView into FrameLayout for case if you need to control more views there.

xml layout

<FrameLayout
android:id="@+id/ac_mp_container"
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">

<fragment
    android:id="@+id/ac_mp_map"
    android:name="com.google.android.gms.maps.SupportMapFragment"

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tivogi.so.MapsActivity" />

<EditText
    android:layout_width="100dp"
    android:layout_height="wrap_content" />

<FrameLayout
    android:id="@+id/ac_mp_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:padding="16dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This should always be shown, at the bottom" />

</FrameLayout>

activity class

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

private View mContainerView;
private GoogleMap mMap;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_maps);
    // Obtain the SupportMapFragment and get notified when the map is ready to be used.
    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.ac_mp_map);
    mapFragment.getMapAsync(this);
    mContainerView = findViewById(R.id.ac_mp_container);
    mContainerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            View view = findViewById(R.id.ac_mp_view);
            int marginBottom;
            Rect outGlobalRect = new Rect();
            Rect outWindowRect = new Rect();
            DisplayMetrics metrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(metrics);
            mContainerView.getGlobalVisibleRect(outGlobalRect);
            mContainerView.getWindowVisibleDisplayFrame(outWindowRect);
            marginBottom = outGlobalRect.bottom - outWindowRect.bottom;
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM);
            layoutParams.setMargins(0, 0, 0, marginBottom);
            view.setLayoutParams(layoutParams);
        }
    });

}

/**
 * Manipulates the map once available.
 * This callback is triggered when the map is ready to be used.
 * This is where we can add markers or lines, add listeners or move the camera. In this case,
 * we just add a marker near Sydney, Australia.
 * If Google Play services is not installed on the device, the user will be prompted to install
 * it inside the SupportMapFragment. This method will only be triggered once the user has
 * installed Google Play services and returned to the app.
 */
@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    // Add a marker in Sydney and move the camera
    LatLng sydney = new LatLng(-34, 151);
    mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
    mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
}

}

Result:

enter image description here

Macdermot answered 18/1, 2016 at 11:53 Comment(8)
I've now tested it, and it seems to work well. Does it have any disadvantages? Will it work on all Android versions ? How did you find this solution? Why did you create DisplayMetrics , if it's not used anywhere?Smithson
It was brainstorm based on some assumptions, you can remove unused declaration (from one of attempt to solve). Disadvantages - not sure, all Android - need to be testedMacdermot
Thank you. I'll now tick this answer.Smithson
I've found out that on some cases this mechanism doesn't work. Not sure when, but it has something to do with orientation changes.Smithson
@androiddeveloper maybe you use android:configChanges="orientation" for that activity?Macdermot
I used "keyboardHidden" for the configChanged, and "user" for screenOrientation . I don't want to avoid re-creating the activity because I've already handled orientation changes well...Smithson
I hope, you understand that it's impossible task to help you with that, because "sometime", "something" and at "some code"Macdermot
I know. I have no idea why it occurs. I can't reproduce it in the small POC I've created for this question, and the app isn't open sourced so I can't just give it to you. I have found out though, that when this bug occurs, the getResources().getDisplayMetrics().heightPixels do change. I can also say that I use this library : github.com/Flipboard/bottomsheet , similar to what I did here: github.com/AndroidDeveloperLB/ThreePhasesBottomSheetSmithson
J
0

This is a little bit off topic from what you guys have been discussing, but I had the same issue with the Google Map layout being re adjusted when user clicks on an EditText on that fragment layout. My problem was that the GoogleMap was a fragment of another view (ViewPager). In order to fix it, I had to programmatically add getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN) to the ViewPager Class.

Layout Hierarchy: Activity --> ViewPager --> GoogleMap Fragment. Code added to ViewPager on the onCreate method.

Jacky answered 4/2, 2016 at 18:15 Comment(3)
As I've already written, this affects all views, and not just the map.Smithson
I'm a switch case with 4 tabs inside my view pager class and I applied the code just to the case where I wanted to have the background view not re adjusting and had them adjust on the rest of the other switch cases.Jacky
The question is how to do it for specific views, and not all of the views. This is about the same as the deleted answer of "Karthik".Smithson

© 2022 - 2024 — McMap. All rights reserved.