Earlier there was no problem with soft input mode, but after including ConstraintLayout
, content of fragment doesn't move up when the keyboard appears.
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.pinspb.pinsupport">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
android:name="ru.pinspb.pinsupport.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="ru.pinspb.pinsupport.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application
android:name=".PinApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme.NoActionBar">
<activity
android:name=".auth.ui.HomeActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".front.ui.FrontActivity"
android:launchMode="singleTop" />
<activity
android:name=".chats.ui.InitChatActivity"
android:launchMode="singleTop"
android:windowSoftInputMode="stateHidden" />
</application>
</manifest>
Fragment
public class AuthFragment extends Fragment implements ValidationListener {
private static final String TAG = AuthFragment.class.toString();
// UI references.
@NotEmpty @Email @BindView(R.id.email) EditText email;
@NotEmpty @BindView(R.id.password) EditText password;
@BindView(R.id.auth_sign_in) Button signIn;
@BindView(R.id.remember_me) CheckBox remember;
@BindView(R.id.forgot) TextView forgot;
@BindView(R.id.error) TextView errorField;
@Inject @ApplicationContext
Context context;
private Validator validator;
private onAuthenticateEventListener authenticatableEventListener;
private String error = Constants.EMPTY_STRING;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
authenticatableEventListener = (onAuthenticateEventListener) activity;
} catch (ClassCastException e) {
e.printStackTrace();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View render = inflater.inflate(R.layout.fragment_auth, container, false);
ButterKnife.bind(this, render);
final View activityRootView = render.findViewById(R.id.activity_root);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
if (heightDiff > Helper.dpToPx(container.getContext(), 200)) { // if more than 200 dp, it's probably a keyboard...
Log.d(TAG, "heightDiff: " + heightDiff);
}
});
return render;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
errorField.setText(this.error);
if(this.error.equals(Constants.EMPTY_STRING)) {
errorField.setVisibility(View.GONE);
} else {
errorField.setVisibility(View.VISIBLE);
}
// Set up the login form.
password.setOnEditorActionListener((textView, id, keyEvent) -> {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
});
validator = new Validator(this);
validator.setValidationListener(this);
signIn.setOnClickListener(v -> validator.validate());
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() {
// Store values at the time of the login attempt.
String email = this.email.getText().toString();
String password = this.password.getText().toString();
Bundle bundle = new Bundle();
bundle.putString("email", email);
bundle.putString("password", password);
authenticatableEventListener.sendAuthRequest(bundle);
}
@Override
public void onValidationSucceeded() {
attemptLogin();
}
@Override
public void onValidationFailed(List<ValidationError> errors) {
for (ValidationError error : errors) {
Log.d(TAG, "onValidationFailed: " + error.getCollatedErrorMessage(context));
View view = error.getView();
String message = error.getCollatedErrorMessage(context);
// Display error messages ;)
if (view instanceof EditText) {
((EditText) view).setError(message);
} else {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
}
}
public void setErrors(String text) {
this.error = text;
}
public interface onAuthenticateEventListener {
void sendAuthRequest(Bundle params);
void showErrors(String error);
}
}
Layout
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:background="@color/bg"
android:id="@+id/activity_root">
<!-- Login progress -->
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@+id/activity_root"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toTopOf="@+id/activity_root"
tools:layout_constraintTop_creator="1"
app:layout_constraintRight_toLeftOf="@+id/activity_root"
tools:layout_constraintRight_creator="1"
app:layout_constraintBottom_toTopOf="@+id/activity_root"
tools:layout_constraintBottom_creator="1" />
<ImageView
android:layout_width="120dp"
android:layout_height="80dp"
android:id="@+id/logo"
app:srcCompat="@drawable/logo_pin_support"
android:contentDescription="@string/contentDiscription"
app:layout_constraintLeft_toLeftOf="@+id/activity_root"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toTopOf="@+id/activity_root"
android:layout_marginTop="56dp"
tools:layout_constraintTop_creator="1"
app:layout_constraintRight_toRightOf="@+id/activity_root"
tools:layout_constraintRight_creator="1" />
<EditText
android:id="@+id/email"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/auth.email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:drawablePadding="10dp"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:textSize="@dimen/auth.sizes"
android:autoLink="none"
android:focusableInTouchMode="true"
tools:ignore="RtlHardcoded"
app:layout_constraintLeft_toLeftOf="@+id/activity_root"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="@+id/error"
android:layout_marginTop="8dp"
app:layout_constraintRight_toRightOf="@+id/activity_root"
android:layout_marginEnd="16dp"
app:layout_constraintHorizontal_bias="0.56" />
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/auth.password"
android:imeActionId="@+id/login"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:drawablePadding="10dp"
android:textSize="@dimen/auth.sizes"
android:paddingTop="20dp"
android:paddingBottom="20dp"
tools:ignore="MissingConstraints,RtlHardcoded"
app:layout_constraintLeft_toLeftOf="@+id/email"
app:layout_constraintTop_toBottomOf="@+id/email"
app:layout_constraintRight_toRightOf="@+id/email"
app:layout_constraintHorizontal_bias="0.0" />
<TextView
android:text="@string/auth.title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="27sp"
android:textColor="@color/greyish_brown"
android:id="@+id/textView"
tools:ignore="MissingConstraints"
app:layout_constraintLeft_toLeftOf="@+id/activity_root"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="@+id/logo"
android:layout_marginTop="24dp"
app:layout_constraintRight_toRightOf="@+id/activity_root"
android:layout_marginEnd="16dp" />
<CheckBox
android:text="@string/auth.remember"
android:layout_width="0dp"
android:layout_height="32dp"
android:id="@+id/remember_me"
style="@android:style/Widget.Holo.Light.CompoundButton.CheckBox"
android:checked="true"
android:textSize="@dimen/auth.sizes"
android:textColor="@color/warm_grey"
app:layout_constraintLeft_toLeftOf="@+id/activity_root"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="@+id/password"
android:layout_marginTop="27dp"
app:layout_constraintRight_toRightOf="@+id/activity_root"
android:layout_marginEnd="16dp"
app:layout_constraintHorizontal_bias="0.0" />
<Button
android:text="@string/auth.submit"
android:layout_width="152dp"
android:layout_height="51dp"
android:id="@+id/auth_sign_in"
android:background="@drawable/round_button"
tools:ignore="MissingConstraints"
android:textColor="@color/white"
android:textSize="@dimen/auth.sizes"
app:layout_constraintLeft_toLeftOf="@+id/activity_root"
android:layout_marginStart="16dp"
app:layout_constraintTop_toBottomOf="@+id/remember_me"
android:layout_marginTop="46dp"
app:layout_constraintRight_toRightOf="@+id/activity_root"
android:layout_marginEnd="16dp" />
<TextView
android:text="@string/auth.forgot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/forgot"
android:textColor="@color/pinkish_grey"
app:layout_constraintLeft_toLeftOf="@+id/auth_sign_in"
app:layout_constraintTop_toBottomOf="@+id/auth_sign_in"
android:layout_marginTop="16dp"
app:layout_constraintRight_toRightOf="@+id/auth_sign_in" />
<TextView
android:text="error"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:id="@+id/error"
android:textColor="@color/lipstick"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@+id/textView"
app:layout_constraintTop_toBottomOf="@+id/textView"
android:layout_marginTop="16dp"
app:layout_constraintRight_toRightOf="@+id/textView" />
</android.support.constraint.ConstraintLayout>
Here is the pic for understanding:
How can I understand what is going on? I've used ViewTreeObserver before.
U.P.D.
My goal is
I expected content to move up when keyboard appears, but keyboard is overlapping it instead.
Everything actually works as intended with the way your layout is built -- margins are fixed distances, so your UI is simply too tall for the smaller screen. You would need to modify your layout to better adapt for a small layout -- either marking unnecessary views (e.g. the logo) as gone (ConstraintLayout will consider "gone" views as collapsed to a single point, in essence -- so the layout still works), or change some margins dimensions to a smaller value.
The usual way to build this is to use bias constraints or guidelines, instead of hard margins. Using bias or guidelines (in percent mode) would allow you to have more of a "spring"-like behaviour to react better to dimension changes. Typically a layout will be a mix of hard margins and bias / guidelines.
To sum up, your options are:
GONE
when detecting the keyboard