Android 登陆页面仿拉钩动效,你总会需要它!
nchok
8年前
<p>哈哈,看到这个标题是不是JH一紧,你可能会说我就没遇到过,但是现在没遇到不代表就遇不到,毕竟设计也是变幻莫测,只有你想不到的,没有你不能实现的,说的这么吊,到底是啥效果?没错就是一个小小的登录页面,大家都有拉勾app吧,看拉勾的登录页做的很是平滑动画,而且带动画效果,所以就有了类似拉勾登录效果,如图:</p> <p><img src="https://simg.open-open.com/show/c9be77528e73edc61fa88f3c12968ccb.gif"></p> <p>登录动画</p> <p><a href="/misc/goto?guid=4959746108129882582" rel="nofollow,noindex">github地址</a></p> <p>虽然是个简单的页面,但是涵盖的东西不算少啊,很纳闷为何谷歌一直不提供简单,方便,准确的键盘监听事件?惆怅啊,所以我们只能自己从侧面监听键盘事件了,我们可以监听最外层布局的变化来判断键盘是不是弹起了。闲话不多说,上车吧。</p> <p>布局文件,大家都能看懂吧。</p> <pre> <code class="language-xml"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:clipToPadding="true" android:background="@color/color_ffffff" android:orientation="vertical"> <ImageView android:id="@+id/logo" android:layout_width="100dp" android:layout_height="100dp" android:layout_centerHorizontal="true" android:layout_gravity="center" android:background="@null" android:layout_marginTop="80dp" android:scaleType="centerCrop" android:src="@drawable/skin_about_brand" /> <ScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:fillViewport="true" android:layout_marginLeft="15dp" android:layout_marginRight="15dp" android:scrollbarThumbVertical="@android:color/transparent" android:scrollbars="vertical" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true"> <LinearLayout android:id="@+id/content" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="55dp" android:layout_marginTop="200dp" android:gravity="center_vertical" android:orientation="horizontal" android:paddingLeft="13dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="15dp" android:src="@drawable/ic_mobile_flag" /> <EditText android:id="@+id/et_mobile" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@null" android:hint="@string/hint_login_username" android:inputType="textVisiblePassword" android:maxLength="11" android:singleLine="true" android:text="" android:textColor="@color/color_999999" android:textColorHint="@color/color_999999" android:textSize="14dp" /> <ImageView android:id="@+id/iv_clean_phone" android:layout_width="40dp" android:layout_height="fill_parent" android:scaleType="centerInside" android:src="@drawable/ic_clear" android:visibility="gone" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1px" android:background="@color/color_eeeeee" /> <LinearLayout android:layout_width="match_parent" android:layout_height="55dp" android:gravity="center_vertical" android:orientation="horizontal" android:paddingLeft="13dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="15dp" android:src="@drawable/ic_password_flag" /> <EditText android:id="@+id/et_password" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@null" android:hint="@string/hint_login_password" android:inputType="textPassword" android:maxLength="30" android:singleLine="true" android:text="" android:textColor="@color/color_999999" android:textColorHint="@color/color_999999" android:textSize="14dp" /> <ImageView android:id="@+id/clean_password" android:layout_width="40dp" android:layout_height="fill_parent" android:scaleType="centerInside" android:src="@drawable/ic_clear" android:visibility="gone" /> <ImageView android:id="@+id/iv_show_pwd" android:layout_width="40dp" android:layout_height="fill_parent" android:scaleType="centerInside" android:src="@drawable/pass_gone" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1px" android:background="@color/color_eeeeee" /> <Button android:id="@+id/btn_login" android:layout_width="match_parent" android:layout_height="45dp" android:layout_marginBottom="10dp" android:layout_marginTop="21dp" android:background="@drawable/bg_btn_login_selected" android:text="@string/login" android:textColor="@color/color_ffffff" android:textSize="18dp" /> <LinearLayout android:layout_width="match_parent" android:orientation="horizontal" android:layout_height="wrap_content"> <TextView android:id="@+id/regist" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:layout_marginBottom="10dp" android:text="注册新用户" android:layout_weight="1" android:textColor="@color/color_b0b8b2" android:textSize="14dp" /> <TextView android:id="@+id/forget_password" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:layout_marginBottom="10dp" android:layout_marginLeft="21dp" android:text="@string/login_forget_pwd" android:textColor="@color/color_b0b8b2" android:textSize="14dp" /> </LinearLayout> </LinearLayout> </ScrollView> <LinearLayout android:id="@+id/service" android:layout_width="match_parent" android:orientation="horizontal" android:gravity="center" android:padding="10dp" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="联系客服" android:textColor="@color/color_b0b8b2" android:textSize="14dp" /> <View android:layout_width="1dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:background="@color/color_eeeeee" android:layout_height="match_parent"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="关于我们" android:textColor="@color/color_b0b8b2" android:textSize="14dp" /> </LinearLayout> </RelativeLayout></code></pre> <p>我们要想监听键盘事件,首先我们想得到的是键盘弹起的时候我们可以去搞点事情,键盘搜起的时候我们再去搞点事情,知道这些还不够,我们还要知道键盘弹起了多少,以及需要平移多少的距离。我们都知道我们的一个页面弹起键盘的时候这个页面的根布局会回调他的监听方法:addOnLayoutChangeListener( );当键盘弹起的时候,我们的布局是变化了,因此会执行这个回调方法,但是前提是必须设置我们的Activity的windowSoftInputMode属性为adjustResize。</p> <p>我们想让布局整体平移的距离也就是弹起时候处于最底部的view距离顶部的高度减去我们键盘的高度。现在认为只要控件将Activity向上推的高度超过了1/3屏幕高,就认为软键盘弹起</p> <pre> <code class="language-java">scrollView.addOnLayoutChangeListener(new ViewGroup.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { /* old是改变前的左上右下坐标点值,没有old的是改变后的左上右下坐标点值 现在认为只要控件将Activity向上推的高度超过了1/3屏幕高,就认为软键盘弹起*/ if (oldBottom != 0 && bottom != 0 && (oldBottom - bottom > keyHeight)) { Log.e("wenzhihao", "up------>"+(oldBottom - bottom)); if ((btn_login.getTop()-(oldBottom - bottom))>0){ int dist = btn_login.getTop()-(oldBottom - bottom); ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(content, "translationY", 0.0f, -dist); mAnimatorTranslateY.setDuration(300); mAnimatorTranslateY.setInterpolator(new LinearInterpolator()); mAnimatorTranslateY.start(); service.setVisibility(View.INVISIBLE); } zoomIn(logo, (oldBottom - bottom) - keyHeight); } else if (oldBottom != 0 && bottom != 0 && (bottom - oldBottom > keyHeight)) { Log.e("wenzhihao", "down------>"+(bottom - oldBottom)); ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(content, "translationY", content.getTranslationY(), 0); mAnimatorTranslateY.setDuration(300); mAnimatorTranslateY.setInterpolator(new LinearInterpolator()); mAnimatorTranslateY.start(); service.setVisibility(View.VISIBLE); //键盘收回后,logo恢复原来大小,位置同样回到初始位置 zoomOut(logo, (bottom - oldBottom) - keyHeight); } } }); //btn_login是登录按钮</code></pre> <p>这样我们发现是可以实现效果了,但是我想全屏显示,懵比了,发现全屏的时候不回调这个方法了,怎么办?又是查资料一看原来这个也是一个bug,但是有解决方案,AndroidBug5497Workaround。也是谷歌提供的?直接拷贝过来,会发现其实他的作用就是让Activity最外层的根布局,当有布局变化时去响应这个变化mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener();</p> <pre> <code class="language-java">package com.wzh.study.login; import android.app.Activity; import android.graphics.Rect; import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; public class AndroidBug5497Workaround { // For more information, see https://code.google.com/p/android/issues/detail?id=5497 // To use this class, simply invoke assistActivity() on an Activity that already has its content view set. public static void assistActivity (Activity activity) { new AndroidBug5497Workaround(activity); } private View mChildOfContent; private int usableHeightPrevious; private FrameLayout.LayoutParams frameLayoutParams; private AndroidBug5497Workaround(Activity activity) { FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); mChildOfContent = content.getChildAt(0); mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { public void onGlobalLayout() { possiblyResizeChildOfContent(); } }); frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); } private void possiblyResizeChildOfContent() { int usableHeightNow = computeUsableHeight(); if (usableHeightNow != usableHeightPrevious) { int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); int heightDifference = usableHeightSansKeyboard - usableHeightNow; if (heightDifference > (usableHeightSansKeyboard/4)) { // keyboard probably just became visible frameLayoutParams.height = usableHeightSansKeyboard - heightDifference; } else { // keyboard probably just became hidden frameLayoutParams.height = usableHeightSansKeyboard; } mChildOfContent.requestLayout(); usableHeightPrevious = usableHeightNow; } } private int computeUsableHeight() { Rect r = new Rect(); mChildOfContent.getWindowVisibleDisplayFrame(r); return (r.bottom - r.top); } }</code></pre> <p>使用方式,如果我们设置了全屏,就去加载它,不设置不管:</p> <pre> <code class="language-java">if(isFullScreen(this)){ AndroidBug5497Workaround.assistActivity(this); } ... public boolean isFullScreen(Activity activity) { return (activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN)==WindowManager.LayoutParams.FLAG_FULLSCREEN; }</code></pre> <p>接下来就看具体动画事件了,键盘弹起来的时候整体向上平移,LOGO缩小,键盘收起的时候整体下移,并且LOGO恢复原来大小。这里用到的都是属性动画,只有属性动画我们才可以实现真正平移效果。</p> <p>我看网上很多人使用addOnLayoutChangeListener()去监听键盘事件,但是这个方法回调的太频繁,比如本例特效,输入框后面有文字时候显示清除的图标,如果用这个方法那么也会执行一次,可能会影响你的动画,当然你也可以去记录第一次的高度让他不会走逻辑,但是我觉得也不是很靠谱,虽然我这个方法也不是很棒 ๑乛◡乛๑~。</p> <p>最后贴上源码:</p> <pre> <code class="language-java">package com.wzh.study.login; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Activity; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.LinearInterpolator; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import com.wzh.study.R; /** * Created by WZH on 2017/3/25. */ public class OtherLoginAct extends FragmentActivity implements View.OnClickListener { private ImageView logo; private ScrollView scrollView; private EditText et_mobile; private EditText et_password; private ImageView iv_clean_phone; private ImageView clean_password; private ImageView iv_show_pwd; private Button btn_login; private TextView forget_password; private int screenHeight = 0;//屏幕高度 private int keyHeight = 0; //软件盘弹起后所占高度 private float scale = 0.6f; //logo缩放比例 private View service,content; private int height = 0 ; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_other_login); if(isFullScreen(this)){ AndroidBug5497Workaround.assistActivity(this); } initView(); initListener(); } public boolean isFullScreen(Activity activity) { return (activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN)==WindowManager.LayoutParams.FLAG_FULLSCREEN; } private void initView() { logo = (ImageView) findViewById(R.id.logo); scrollView = (ScrollView) findViewById(R.id.scrollView); et_mobile = (EditText) findViewById(R.id.et_mobile); et_password = (EditText) findViewById(R.id.et_password); iv_clean_phone = (ImageView) findViewById(R.id.iv_clean_phone); clean_password = (ImageView) findViewById(R.id.clean_password); iv_show_pwd = (ImageView) findViewById(R.id.iv_show_pwd); btn_login = (Button) findViewById(R.id.btn_login); forget_password = (TextView) findViewById(R.id.forget_password); service = findViewById(R.id.service); content = findViewById(R.id.content); screenHeight = this.getResources().getDisplayMetrics().heightPixels; //获取屏幕高度 keyHeight = screenHeight / 3;//弹起高度为屏幕高度的1/3 } private void initListener() { iv_clean_phone.setOnClickListener(this); clean_password.setOnClickListener(this); iv_show_pwd.setOnClickListener(this); et_mobile.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (!TextUtils.isEmpty(s) && iv_clean_phone.getVisibility() == View.GONE) { iv_clean_phone.setVisibility(View.VISIBLE); } else if (TextUtils.isEmpty(s)) { iv_clean_phone.setVisibility(View.GONE); } } }); et_password.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (!TextUtils.isEmpty(s) && clean_password.getVisibility() == View.GONE) { clean_password.setVisibility(View.VISIBLE); } else if (TextUtils.isEmpty(s)) { clean_password.setVisibility(View.GONE); } if (s.toString().isEmpty()) return; if (!s.toString().matches("[A-Za-z0-9]+")) { String temp = s.toString(); Toast.makeText(OtherLoginAct.this, R.string.please_input_limit_pwd, Toast.LENGTH_SHORT).show(); s.delete(temp.length() - 1, temp.length()); et_password.setSelection(s.length()); } } }); /** * 禁止键盘弹起的时候可以滚动 */ scrollView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } }); scrollView.addOnLayoutChangeListener(new ViewGroup.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { /* old是改变前的左上右下坐标点值,没有old的是改变后的左上右下坐标点值 现在认为只要控件将Activity向上推的高度超过了1/3屏幕高,就认为软键盘弹起*/ if (oldBottom != 0 && bottom != 0 && (oldBottom - bottom > keyHeight)) { Log.e("wenzhihao", "up------>"+(oldBottom - bottom)); if ((btn_login.getTop()-(oldBottom - bottom))>0){ int dist = btn_login.getTop()-(oldBottom - bottom); ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(content, "translationY", 0.0f, -dist); mAnimatorTranslateY.setDuration(300); mAnimatorTranslateY.setInterpolator(new LinearInterpolator()); mAnimatorTranslateY.start(); service.setVisibility(View.INVISIBLE); } zoomIn(logo, (oldBottom - bottom) - keyHeight); } else if (oldBottom != 0 && bottom != 0 && (bottom - oldBottom > keyHeight)) { Log.e("wenzhihao", "down------>"+(bottom - oldBottom)); ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(content, "translationY", content.getTranslationY(), 0); mAnimatorTranslateY.setDuration(300); mAnimatorTranslateY.setInterpolator(new LinearInterpolator()); mAnimatorTranslateY.start(); service.setVisibility(View.VISIBLE); //键盘收回后,logo恢复原来大小,位置同样回到初始位置 zoomOut(logo, (bottom - oldBottom) - keyHeight); } } }); } /** * 缩小 * @param view */ public void zoomIn(final View view, float dist) { view.setPivotY(view.getHeight()); view.setPivotX(view.getWidth() / 2); AnimatorSet mAnimatorSet = new AnimatorSet(); ObjectAnimator mAnimatorScaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, scale); ObjectAnimator mAnimatorScaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, scale); ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(view, "translationY", 0.0f, -dist); mAnimatorSet.play(mAnimatorTranslateY).with(mAnimatorScaleX); mAnimatorSet.play(mAnimatorScaleX).with(mAnimatorScaleY); mAnimatorSet.setDuration(300); mAnimatorSet.start(); } /** * f放大 * @param view */ public void zoomOut(final View view, float dist) { view.setPivotY(view.getHeight()); view.setPivotX(view.getWidth() / 2); AnimatorSet mAnimatorSet = new AnimatorSet(); ObjectAnimator mAnimatorScaleX = ObjectAnimator.ofFloat(view, "scaleX", scale, 1.0f); ObjectAnimator mAnimatorScaleY = ObjectAnimator.ofFloat(view, "scaleY", scale, 1.0f); ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(view, "translationY", view.getTranslationY(), 0); mAnimatorSet.play(mAnimatorTranslateY).with(mAnimatorScaleX); mAnimatorSet.play(mAnimatorScaleX).with(mAnimatorScaleY); mAnimatorSet.setDuration(300); mAnimatorSet.start(); } @Override public void onClick(View v) { int id = v.getId(); switch (id) { case R.id.iv_clean_phone: et_mobile.setText(""); break; case R.id.clean_password: et_password.setText(""); break; case R.id.iv_show_pwd: if (et_password.getInputType() != InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { et_password.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); iv_show_pwd.setImageResource(R.drawable.pass_visuable); } else { et_password.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); iv_show_pwd.setImageResource(R.drawable.pass_gone); } String pwd = et_password.getText().toString(); if (!TextUtils.isEmpty(pwd)) et_password.setSelection(pwd.length()); break; } } }</code></pre> <p>如果有什么问题欢迎指出,我将给出例子地址,包含另一种实现方式就是用scrollview滑动到最底部的方式来实现平移效果~</p> <p> </p> <p><strong>项目主页:<a href="http://www.open-open.com/lib/view/home/1490748738209">http://www.open-open.com/lib/view/home/1490748738209</a></strong></p>