I'm designing a login/signup interface. The idea is like this:
I've got two EditText views: one for username, the other for password, like this:
[USERNAME]
[PASSWORD]
[SIGN IN BUTTON]
<EditText
android:layout_width="270dp"
android:layout_height="@dimen/ui_element_height"
android:id="@+id/usernameField"
android:background="@drawable/field_start_screen"
android:layout_gravity="center_horizontal"
android:paddingLeft="@dimen/field_padding_left"
android:drawableLeft="@drawable/ic_user"
android:hint="@string/hint_username"/>
I've got another EditText, whose code is exactly the same as the one above, but with another property:
android:visibility="gone"
That's because, when the user wants to sign up, that "gone" field will appear. Up to now, the layout would be something like this:
(USERNAME)
(PASSWORD)
(HIDDEN REPEAT PASSWORD)
(SIGN IN BUTTON)
The real thing here is that I've got a TextView prompting the user to sign up. When he touches that text, I want the two EditText (Username and password), to translate a few pixels to the top, so the newer EditText (The repeat password field) can occupy the Password field.
The distance both EditText have to translate is exactly the height of the edittext. In other words, the repeat password field has to be EXACTLY in the same place the password field was, and right above the repeat password field.
Here's what my View.OnclickListener does:
ObjectAnimator animationUsernameField = ObjectAnimator.ofFloat(usernameField, "TranslationY", 0, -usernameField.getHeight());
ObjectAnimator animationPasswordField = ObjectAnimator.ofFloat(passwordField, "TranslationY", 0, -passwordField.getHeight());
ObjectAnimator animationRepeatPassword = ObjectAnimator.ofFloat(repeatPasswordField, "Alpha", 0.0f, 1.0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
animationUsernameField,
animationPasswordField,
animationRepeatPassword
);
animatorSet.setDuration(1000);
animatorSet.start();
repeatPasswordField.setVisibility(View.VISIBLE);
I'm also applying an alpha effect to the repeat password field.
Because when I touch the SignUp, the thing is like this:
(USERNAME)
(PASSWORD)
ANNOYING SPACE
(REPEAT PASSWORD)
(SIGN UP BUTTON)
The annoying space is exactly the place where Password should be. I mean, password is translated *2 * height*, when I need it translated height (because repeat password is in the original space where password was before touching the textview)
Everything is in a unique LinearLayout
Can anyone help me with this problem? What am I doing wrong? I tried a lot of things but always end up in the same situation, with that annoying space between the Password field and the repeat password field.
I think I didn't forget any details. If you need more info, please tell me. Thank you a lot!!
First of all I'd like to explain why you're not getting the expected result.
When you translate a view, you're not actually changing the layout. This means that when you show the "Repeat Password" field, that field is supposed to be where it's being displayed, because as far as the layout is concerned, the two other views hasn't moved at all. The reason the layout does not update when using a translation / scale or other animation, is because recalculating the layout on every animation frame, would slow down the animation.
My solution might not be exactly what you're looking for, as it does assume that you have the possibility of adding a bit of empty space above your form. The reason for tihs is because you specifically require that the "Repeat Password" field is reveal in place of the "Password" field, pushing it upwards, rather than revealing it below the field.
Create a new class called RevealInPlaceAnimation
This class handles the actual animation, making the code in your Activity
or Fragment
cleaner.
package net.njensen.stackoverflow;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by Nicklas Jensen on 03/05/15.
*/
public class RevealInPlaceAnimation {
private final View mContainerView;
private final View mRevealView;
private final AnimatorSet mAnimations;
public RevealInPlaceAnimation(View containerView, View revealView) {
mContainerView = containerView;
mRevealView = revealView;
mAnimations = new AnimatorSet();
mAnimations.addListener(getAnimationListener());
}
public void setDuration(long duration) {
mAnimations.setDuration(duration);
}
public void setInterpolator(TimeInterpolator interpolator) {
mAnimations.setInterpolator(interpolator);
}
public void addListener(Animator.AnimatorListener listener) {
mAnimations.addListener(listener);
}
public void removeListener(Animator.AnimatorListener listener) {
mAnimations.removeListener(listener);
}
public void start() {
mAnimations.playTogether(getContainerViewAnimation(), getRevealViewAnimation());
mAnimations.start();
}
private float getExpectedRevealViewHeight() {
return mRevealView.getHeight();
}
private float getRevealViewMarginTop() {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mRevealView.getLayoutParams();
return params.topMargin;
}
private float getRevealViewOffset() {
return getExpectedRevealViewHeight() + getRevealViewMarginTop();
}
private ValueAnimator getContainerViewAnimation() {
float translationY = -getRevealViewOffset();
return ObjectAnimator.ofFloat(mContainerView, "translationY", 0.0f, translationY);
}
private ValueAnimator getRevealViewAnimation() {
return ObjectAnimator.ofFloat(mRevealView, "alpha", 0.0f, 1.0f);
}
private Animator.AnimatorListener getAnimationListener() {
return new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mRevealView.setVisibility(View.VISIBLE);
}
};
}
}
Now you'll need to setup your layout like so:
Wrap the new layout (step 1) and the "Repeat Password" field in a RelativeLayout, and set the below attributes on the "Repeat Password" field.
android:layout_alignBottom="@+id/of_the_new_layout"
android:visibility="invisible"
android:alpha="0.0"
You will need to set android:layout_paddingTop
on the RelativeLayout, equal or greater than the height of the "Repeat Password" field + any top margin it might have.
android:paddingBottom
equal to the same value you've set on the RelativeLayout, to even out the padding.
android:clipToPadding="false"
on the RelativeLayout (step 2).Now your layout should look something like this:
<ScrollView 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivityFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="48dp"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="48dp"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/username_password_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:layout_marginTop="8dp"
android:hint="Password"/>
</LinearLayout>
<EditText
android:id="@+id/et_repeat_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/username_password_container"
android:layout_marginTop="8dp"
android:inputType="textPassword"
android:hint="Repeat Password"
android:visibility="invisible"
android:alpha="0.0"/>
</RelativeLayout>
<Button
android:id="@+id/btn_register"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Register"/>
</LinearLayout>
</ScrollView>
The only thing left to do is trigger the animation. Here's an example:
package net.njensen.stackoverflow;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.EditText;
/**
* Created by Nicklas Jensen on 03/05/15.
*/
public class MainActivityFragment extends Fragment {
private EditText mEtUsername;
private EditText mEtPassword;
private EditText mEtRepeatPassword;
private Button mBtnRegister;
private View mUsernamePasswordContainer;
private final View.OnClickListener mDisplayRepeatPasswordListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isRepeatPasswordVisible()) {
showRepeatPasswordAnimated();
}
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mEtUsername = (EditText) view.findViewById(R.id.et_username);
mEtPassword = (EditText) view.findViewById(R.id.et_password);
mEtRepeatPassword = (EditText) view.findViewById(R.id.et_repeat_password);
mBtnRegister = (Button) view.findViewById(R.id.btn_register);
mUsernamePasswordContainer = view.findViewById(R.id.username_password_container);
mBtnRegister.setOnClickListener(mDisplayRepeatPasswordListener);
}
private boolean isRepeatPasswordVisible() {
return mEtRepeatPassword.getVisibility() == View.VISIBLE;
}
private int getRepeatPasswordAnimationDuration() {
return getResources().getInteger(android.R.integer.config_mediumAnimTime);
}
private void showRepeatPasswordAnimated() {
RevealInPlaceAnimation animation = new RevealInPlaceAnimation(mUsernamePasswordContainer, mEtRepeatPassword);
animation.setInterpolator(new DecelerateInterpolator());
animation.setDuration(getRepeatPasswordAnimationDuration());
animation.start();
}
}
If you want to improve upon the solution, here's a suggestion:
android:paddingTop
on the wrapper RelativeLayout dynamically when the view is drawn, to ensure the correct padding.