巧妙地处理带头像的登录界面键盘遮挡问题
登录界面输入用户名或者密码键盘抬起导致的遮盖问题是个老问题了,网上有很多类似的解决方案。如果你恰好在登录界面还有个头像的话,如何优雅的处理这个头像呢?目前最新版本的手机QQ的处理也是比较醉人——直接在下方留出一大片白,这样子就避免了遮盖的问题(“ ”)。
但是本人总觉得不好看,这里本文将给出一个巧妙地处理头像和遮盖问题的例子。键盘隐藏和打开的效果图如下:
键盘弹出后总结起来主要是两点:
1. 输入区域包括登录按钮整体上抬
2. 原来的头像缩小到右上角
整体上抬
做到这个很简单,仅仅需要在AndroidManifest中对应的Activity的元素中加入windowSoftInputMode属性:
<activity android:name=".activity.LoginActivity" android:label="@string/login_button_text" android:launchMode="singleTask" android:windowSoftInputMode="stateHidden|stateUnchanged|adjustResize"/>
于是运行后跑起来发现登录界面变成了这样:
这显然不是我们想要的,虽然整体上抬了,但是登录按钮却依然被遮盖。输入完毕用户名密码后必须要先关闭输入法才能点击到登录按钮,等于多了一步操作。
那么到这里我们就想到了是否可以拿这个头像做文章?可以看到头像占据了很大的空间,那么是否可以在键盘抬起的时候我们调整头像的大小以及位置呢?答案是可以得,但是首先我们要做的是知道何时键盘打开或者关闭,其次再来改变这个头像。
监控键盘的状态
这个Activity的根布局是一个RelativeLayout (文章最后会给出完整的布局):
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/login_root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/shape_login_bg_start"> ... </RelativeLayout>
由于上面键盘抬起的时候那个checkbox依然可以点击到,所以我们这里做个测试,键盘分别是关闭和打开的时候,点击checkbox来计算下这个根布局RelativeLayout的高度是否在变化,添加的测试代码为:
@Bind(R.id.login_root) RelativeLayout mRoot; 。。。 pwdCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { L.d("root view height : " + mRoot.getHeight()); } });
依次在键盘隐藏和抬起的时候点击checkbox来查看下log:
11-25 14:27:28.710 5623-5623/com.qianmi.shine D/shine: root view height : 1860 11-25 14:27:31.240 5623-5623/com.qianmi.shine D/shine: root view height : 1033
发现这个RelativeLayout的高度在键盘隐藏和抬起的时候是在变化的,那么它的onSideChange这个函数肯定是一直在被调用的。所以我们可以自己继承一个RelativeLayout,同时复写它的onSizeChange函数,通过高度的变化来判断键盘是否打开,同时暴露出一个接口给Activity实现,让Activity在该接口中根据键盘的状态来改变头像的布局。这个自定义的继承自RelativeLayout的控件如下:
public class ResizeRelativeLayout extends RelativeLayout { public static final int HIDE = 0; public static final int SHOW = 1; private Handler mainHandler = new Handler(); public ResizeRelativeLayout(Context context) { super(context); // TODO Auto-generated constructor stub } public ResizeRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override protected void onSizeChanged(int w,final int h, int oldw,final int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); mainHandler.post(new Runnable() { @Override public void run() { if (oldh - h > 50){ keyBordStateListener.onStateChange(SHOW); } else { if(keyBordStateListener != null){ keyBordStateListener.onStateChange(HIDE); } } } }); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // TODO Auto-generated method stub super.onLayout(changed, l, t, r, b); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private KeyBordStateListener keyBordStateListener; public void setKeyBordStateListener(KeyBordStateListener keyBordStateListener) { this.keyBordStateListener = keyBordStateListener; } public interface KeyBordStateListener{ public void onStateChange(int state); } }
在onSizeChange中判断当前的height如果大于过去的height,即说明键盘是隐藏了;反之就是显示了。
Activity对应的修改如下:
@Bind(R.id.login_root) ResizeRelativeLayout mRoot; @Override public void onStateChange(int state) { switch (state) { case ResizeRelativeLayout.HIDE: //TODO when keyboard is hide break; case ResizeRelativeLayout.SHOW: //TODO when keyboard is show break; } } mRoot.setKeyBordStateListener(this);
头像的变化
有了键盘状态的监听后,头像变化的处理就变的随性了。为了简单起见,这里我们在根布局的右上角同样放置了一个ImageView,他和原来的头像ImageView显示同样的内容。当键盘弹起的时候显示右上角的头像而隐藏中间的大头像(注意一定要设置为Gone,这样子的话布局在onMeasure的时候才会把原来属于大头像的空间腾出来从而让下方的输入框和登录按钮向上抬起的更多);当键盘隐藏的时候则显示中间大头像而隐藏右上角的头像。
修改onStateChange为:
@Bind(R.id.card_small) CardView cardSmall; @Bind(R.id.card) CardView card; @Override public void onStateChange(int state) { switch (state) { case ResizeRelativeLayout.HIDE: card.setVisibility(View.VISIBLE); cardSmall.setVisibility(View.INVISIBLE); break; case ResizeRelativeLayout.SHOW: card.setVisibility(View.GONE); cardSmall.setVisibility(View.VISIBLE); break; } }
(这里需要注明的是,为了让ImageView带阴影,特意用了一个CardView来包含ImageView,因为CardView自带阴影效果,所以这里即对CardView进行可见性变化,具体见文末的布局完整文件;其次这里为了做出带圆角的ImageView,用了别的人一个控件,RoundedImageView,请参考https://github.com/vinc3m1/RoundedImageView)
如果需要做的带感的话,可以对中间的大头像做如下的属性动画(一定要是属性动画,因为这样才能真正的“移动”而腾出空间)而不用显示另一个右上角的小头像:
1. ImageView通过scaleX和scaleY来缩小size
2. ImageView通过translateX和translateY来移动到右上角
附完整的布局文件:
<?xml version="1.0" encoding="utf-8"?> <com.qianmi.shine.widget.ResizeRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/login_root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/shape_login_bg_start"> <android.support.v7.widget.CardView android:id="@+id/card_small" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:layout_margin="20dp" android:visibility="invisible" app:cardBackgroundColor="@color/transparent" app:cardCornerRadius="2dp" app:cardElevation="2dp"> <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/profile_small" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/login_btn_photo_l" android:scaleType="fitCenter" android:src="@drawable/photo_l" app:riv_corner_radius="2dp" /> </android.support.v7.widget.CardView> <LinearLayout android:layout_width="315dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:orientation="vertical"> <android.support.v7.widget.CardView android:id="@+id/card" android:layout_width="98dp" android:layout_height="98dp" android:layout_gravity="center_horizontal" android:layout_marginBottom="@dimen/login_input_height" app:cardBackgroundColor="@color/transparent" app:cardCornerRadius="2dp" app:cardElevation="2dp"> <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/profile" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/login_btn_photo_l" android:scaleType="fitCenter" android:src="@drawable/photo_l" app:riv_corner_radius="2dp" /> </android.support.v7.widget.CardView> <EditText android:id="@+id/et_login_username" android:layout_width="match_parent" android:layout_height="@dimen/login_input_height" android:layout_marginLeft="@dimen/login_margin_left" android:background="@drawable/shape_solid_transparent" android:drawableLeft="@drawable/login_icon_company" android:drawablePadding="@dimen/login_padding" android:hint="@string/login_input_hint_username" android:inputType="textPersonName" android:singleLine="true" android:textColor="@color/ColorPrimary" android:textColorHint="@color/text_hint_color" android:textCursorDrawable="@drawable/common_input_cursor" android:textSize="@dimen/login_input_text_size" /> <TextView android:id="@+id/line1" style="@style/common_login_horizontalLine_matchParent_normal" /> <EditText android:id="@+id/et_login_employ_name" android:layout_width="match_parent" android:layout_height="@dimen/login_input_height" android:layout_marginLeft="@dimen/login_margin_left" android:background="@drawable/shape_solid_transparent" android:drawableLeft="@drawable/login_icon_man" android:drawablePadding="@dimen/login_padding" android:hint="@string/login_input_hint_employ" android:inputType="textPersonName" android:singleLine="true" android:textColor="@color/ColorPrimary" android:textColorHint="@color/text_hint_color" android:textCursorDrawable="@drawable/common_input_cursor" android:textSize="@dimen/login_input_text_size" /> <TextView android:id="@+id/line2" style="@style/common_login_horizontalLine_matchParent_normal" /> <EditText android:id="@+id/et_login_pwd" android:layout_width="match_parent" android:layout_height="@dimen/login_input_height" android:layout_marginLeft="@dimen/login_margin_left" android:background="@drawable/shape_solid_transparent" android:drawableLeft="@drawable/login_icon_lock" android:drawablePadding="@dimen/login_padding" android:hint="@string/login_input_hint_pwd" android:inputType="textPassword" android:singleLine="true" android:textColor="@color/ColorPrimary" android:textColorHint="@color/text_hint_color" android:textCursorDrawable="@drawable/common_input_cursor" android:textSize="@dimen/login_input_text_size" /> <TextView android:id="@+id/line3" style="@style/common_login_horizontalLine_matchParent_normal" /> <CheckBox android:id="@+id/remember_pwd" style="@style/RememberPasswordCheckBox" android:layout_marginLeft="@dimen/login_margin_left" android:text="@string/login_remember_pwd" /> <Button android:id="@+id/btn_login" android:layout_width="match_parent" android:layout_height="46dp" android:layout_gravity="center_horizontal" android:layout_marginTop="32dp" android:background="@drawable/common_green_btn_selector" android:focusable="true" android:text="@string/login_button_text" android:textColor="@color/text_button_login" android:textSize="@dimen/login_btn_text_size" /> </LinearLayout> </com.qianmi.shine.widget.ResizeRelativeLayout>