平行动画视图

时间:2015-05-03 15:25:56

标签: java android xml android-animation objectanimator

我正在设计登录/注册界面。这个想法是这样的:

XML

我有两个EditText视图:一个用于用户名,另一个用于密码,如下所示:

[USERNAME]
[PASSWORD]

[登录按钮]

两个元素的XML:

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

隐藏的EditText

我有另一个EditText,其代码与上面的代码完全相同,但有另一个属性:

android:visibility="gone"

那是因为,当用户想要注册时,会出现“已消失”字段。 到目前为止,布局将是这样的:

(用户名)
(密码)
(HIDDEN REPEAT PASSWORD)

(登录按钮)

问题

这里的真实情况是我有一个TextView提示用户注册。当他触摸该文本时,我希望两个EditText(用户名和密码)将几个像素转换为顶部,因此较新的EditText(重复密码字段)可以占用密码字段。

EditText必须翻译的距离正是edittext的高度。换句话说,重复密码字段必须在密码字段所在的同一位置完全,并且在重复的正上方密码字段

以下是我的 View.OnclickListener 所做的事情:

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);

我也在对重复密码字段应用alpha效果。

一切都搞砸了

因为当我触摸SignUp时,事情是这样的:

(用户名)
(密码)
恼人的空间
(REPEAT PASSWORD)

(注册按钮)

烦人的空间正是密码所在的地方。我的意思是,密码被翻译为* 2 *高度*,当我需要它翻译 height 时(因为重复密码是在触摸textview之前密码所在的原始空间)

另一件重要的事情

一切都在一个独特的LinearLayout

任何人都可以帮我解决这个问题吗?我究竟做错了什么?我尝试了很多东西,但总是遇到相同的情况,密码字段和重复密码字段之间的空间很大。

我想我没有忘记任何细节。如果您需要更多信息,请告诉我。 非常感谢!!

1 个答案:

答案 0 :(得分:0)

首先,我想解释为什么你没有得到预期的结果。

翻译视图时,实际上并未更改布局。这意味着当您显示“重复密码”字段时,该字段应该是显示的位置,因为就布局而言,其他两个视图根本没有移动。使用平移/缩放或其他动画时布局不更新的原因是因为重新计算每个动画帧上的布局会降低动画速度。

解决方案

我的解决方案可能不是您正在寻找的,因为它假设您可以在表单上方添加一些空白空间。 tihs的原因是因为您特别要求显示“重复密码”字段代替“密码”字段,将其向上推,而不是在字段下方显示它。

创建一个名为 RevealInPlaceAnimation

的新类

此类处理实际动画,使ActivityFragment中的代码更清晰。

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);
            }
        };
    }

}

现在你需要设置你的布局:

  1. 将表单元素包装在他们自己的LinearLayout中的“重复密码”字段上方。
  2. 在RelativeLayout中包装新布局(步骤1)和“重复密码”字段,并在“重复密码”字段中设置以下属性。

    • android:layout_alignBottom="@+id/of_the_new_layout"
    • android:visibility="invisible"
    • android:alpha="0.0"
  3. 您需要在RelativeLayout上设置android:layout_paddingTop,等于或大于“重复密码”字段的高度+它可能具有的任何上边距。

  4. 如果您想使用以屏幕为中心的表单,您需要将android:paddingBottom设置为等于您在RelativeLayout上设置的相同值,以均匀填充。
    • 听起来你正在使用顶部对齐的LinearLayout,在这种情况下你不应该设置它。
  5. 最后,在RelativeLayout上设置android:clipToPadding="false"(步骤2)。
  6. 现在你的布局应该是这样的:

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

    唯一要做的就是触发动画。这是一个例子:

    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();
        }
    }
    

    改进

    如果你想改进解决方案,这里有一个建议:

    1. 在绘制视图时动态地在包装器RelativeLayout上设置android:paddingTop,以确保填充正确。