Android 开发相关文章收集Android软键盘Android

Android输入法遮挡问题的解决思路

2017-03-22  本文已影响1921人  imflyn

Github Demo:https://github.com/imflyn/InputManagerHelper



我们在做登录界面时经常会遇到上图的情况,输入法不但遮住了登录按钮而且第二个输入框也挡住了一部分。
网上很多文章把android:windowSoftInputMode="adjustPan"android:windowSoftInputMode="adjustResize"这两行代码一帖,很多同学把代码复制过来,再到manifest文件里黏贴上去,试了一遍又一遍。发现完全没有效果啊!!!
所以一定要先搞清楚windowSoftInputMode中各个属性的作用。各个属性的作用官方文档定义的很清楚,不再做具体解释,不懂的同学一定仔细看明白。

Android原生的效果无法满足,我们只能自己写实现来解决问题。

“adjustResize” 始终调整 Activity 主窗口的尺寸来为屏幕上的软键盘腾出空间。

这一句话说当android:windowSoftInputMode="adjustResize"时,window会调整尺寸,也就是说布局大小会改变并且window中的视图树会重新绘制,那么根据这个思路就诞生了两种解决方案。
1.自定义Layout,监听布局的大小改变,动态调整布局的位置
2.布局重绘时监听View树的变化,知道软键盘弹出的时机,依此来动态调整布局
</br>
有了初步的思路,那么我们再来看看该如何知道布局需要调整的距离。


从图中我们可以看到登录按钮被输入法遮挡以后大概的位置,那么为了让登录按钮能够完全显示,就需要布局整体向上位移,移动的高度就是登录按钮底部到键盘最顶端的位置。
确定了思路我们就可以具体着手实现功能了。
1.自定义Layout,监听布局的大小改变

首先需要继承RelativeLayout自定义Layout,监听onSizeChanged方法:

public class KeyboardListenLayout extends RelativeLayout {

    private onSizeChangedListener mChangedListener;

    public KeyboardListenLayout(Context context) {
        super(context);
    }

    public KeyboardListenLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardListenLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (null != mChangedListener && 0 != oldw && 0 != oldh) {
            boolean showKeyboard = h < oldh;
            mChangedListener.onChanged(showKeyboard, h, oldh);
        }
    }

    public void setOnSizeChangedListener(onSizeChangedListener listener) {
        mChangedListener = listener;
    }

    public interface onSizeChangedListener {
        void onChanged(boolean showKeyboard, int h, int oldh);
    }
}

在XML中引用自定义的layout,这里需要注意的是,如果你的界面中使用了Toolbar,一定不能把KeyboardListenLayout放在最外层 ,因为如果放最外层,调整布局时布局向上移动会把Toolbar挤出整个界面的可见范围内。

<LinearLayout 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:background="@android:color/white"
    android:orientation="vertical">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            android:minHeight="?attr/actionBarSize"
            app:elevation="1dp" />
    </android.support.design.widget.AppBarLayout>

    <com.flyn.inputmanagerhelper.view.KeyboardListenLayout
        android:id="@+id/layout_keyboard"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/iv_top"
                android:layout_width="120dp"
                android:layout_height="120dp"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="60dip"
                android:scaleType="fitXY"
                android:src="@drawable/timg" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="40dip"
                android:layout_marginEnd="36dip"
                android:layout_marginStart="36dip"
                android:layout_marginTop="48dip">

                <EditText
                    android:id="@+id/et_account"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_centerVertical="true"
                    android:layout_marginEnd="8dip"
                    android:layout_marginStart="8dip"
                    android:background="@null"
                    android:imeOptions="actionNext"
                    android:textColor="@color/textColorPrimary"
                    android:textSize="14sp"
                    tools:text="13712345678" />

                <View
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:layout_alignParentBottom="true"
                    android:background="@color/dividerColor" />
            </RelativeLayout>

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="40dip"
                android:layout_marginEnd="36dip"
                android:layout_marginStart="36dip"
                android:layout_marginTop="8dip">


                <EditText
                    android:id="@+id/et_password"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_centerVertical="true"
                    android:layout_marginEnd="8dip"
                    android:layout_marginStart="8dip"
                    android:background="@null"
                    android:imeOptions="actionDone"
                    android:textColor="@color/textColorPrimary"
                    android:textSize="14sp"
                    tools:text="123456" />

                <View
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:layout_alignParentBottom="true"
                    android:background="@color/dividerColor" />
            </RelativeLayout>


            <Button
                android:id="@+id/tv_login"
                android:layout_width="match_parent"
                android:layout_height="80dip"
                android:layout_marginEnd="36dip"
                android:layout_marginStart="36dip"
                android:layout_marginTop="24dip"
                android:background="@color/colorPrimary"
                android:text="登录"
                android:textColor="@android:color/white"
                android:textSize="14sp" />
        </LinearLayout>
    </com.flyn.inputmanagerhelper.view.KeyboardListenLayout>
</LinearLayout>

为布局设置OnSizeChanged监听,并在layout大小发生变化时判断软键盘是弹出还是隐藏。

private void bindKeyboardListenLayout(final KeyboardListenLayout keyboardListenLayout, final View lastVisibleView) {
        keyboardListenLayout.setOnSizeChangedListener(new KeyboardListenLayout.onSizeChangedListener() {
            @Override
            public void onChanged(final boolean showKeyboard, final int h, final int oldh) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (showKeyboard) {
                            //oldh代表输入法未弹出前最外层布局高度,h代表当前最外层布局高度,oldh-h可以计算出布局大小改变后输入法的高度
                            //整个布局的高度-输入法高度=键盘最顶端处在布局中的位置,其实直接用h计算就可以,代码这么写为了便于理解
                            int keyboardTop = oldh - (oldh - h);
                            int[] location = new int[2];
                            lastVisibleView.getLocationOnScreen(location);
                            //登录按钮顶部在屏幕中的位置+登录按钮的高度=登录按钮底部在屏幕中的位置
                            int lastVisibleViewBottom = location[1] + lastVisibleView.getHeight();
                            //登录按钮底部在布局中的位置-输入法顶部的位置=需要将布局弹起多少高度
                            int reSizeLayoutHeight = lastVisibleViewBottom - keyboardTop;
                            //因为keyboardListenLayout的高度不包括外层的statusbar的高度和actionbar的高度
                            //所以需要减去status bar的高度
                            reSizeLayoutHeight -= getStatusBarHeight();
                            //如果界面里有actionbar并且处于显示状态则需要少减去actionbar的高度
                            if (null != (((AppCompatActivity) activity).getSupportActionBar()) && (((AppCompatActivity) activity).getSupportActionBar()).isShowing()) {
                                reSizeLayoutHeight -= getActionBarHeight();
                            }
                            //设置登录按钮与输入法之间间距
                            reSizeLayoutHeight += offset;
                            if (reSizeLayoutHeight > 0)
                                keyboardListenLayout.setPadding(0, -reSizeLayoutHeight, 0, 0);
                        } else {
                            //还原布局
                            keyboardListenLayout.setPadding(0, 0, 0, 0);
                        }
                    }
                }, 50);
            }
        });
    }
2.监听View树的变化

添加ViewTreeObserver的监听,并通过计算键盘高度可以知道键盘是弹出还是关闭状态

 private void bindLayout(final ViewGroup viewGroup, final View lastVisibleView ) {
        viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //获得屏幕高度
                        int screenHeight = viewGroup.getRootView().getHeight();
                        //r.bottom - r.top计算出输入法弹起后viewGroup的高度,屏幕高度-viewGroup高度=键盘高度
                        Rect r = new Rect();
                        viewGroup.getWindowVisibleDisplayFrame(r);
                        int keyboardHeight = screenHeight - (r.bottom - r.top);
                        //当设置layout_keyboard设置完padding以后会重绘布局再次执行onGlobalLayout()
                        //所以判断如果键盘高度未改变就不执行下去
                        if (keyboardHeight == lastKeyBoardHeight) {
                            return;
                        }
                        lastKeyBoardHeight = keyboardHeight;
                        if (keyboardHeight < 300) {
                            //键盘关闭后恢复布局
                            viewGroup.setPadding(0, 0, 0, 0);
                        } else {
                            //计算出键盘最顶端在布局中的位置
                            int keyboardTop = screenHeight - keyboardHeight;
                            int[] location = new int[2];
                            lastVisibleView.getLocationOnScreen(location);
                            //获取登录按钮底部在屏幕中的位置
                            int lastVisibleViewBottom = location[1] + lastVisibleView.getHeight();
                            //登录按钮底部在布局中的位置-输入法顶部的位置=需要将布局弹起多少高度
                            int reSizeLayoutHeight = lastVisibleViewBottom - keyboardTop;
                            //需要多弹起一个StatusBar的高度
                            reSizeLayoutHeight -= getStatusBarHeight();
                            //设置登录按钮与输入法之间间距
                            reSizeLayoutHeight += offset;
                            if (reSizeLayoutHeight > 0)
                                viewGroup.setPadding(0, -reSizeLayoutHeight, 0, 0);
                        }
                    }
                }, 50);
            }
        });
    }
3.在ScrollView和RecycleView中处理监听View树的变化

很多时候我们做填写表单的界面时用到ScrollView,甚至在RecycleView中也有输入框时,同样可以运用监听ViewTreeObserver的思路来实现ScrollView或RecycleView位置的调整。

private void bindViewGroup(final ViewGroup viewGroup) {
        viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {   
                        //获得屏幕高度
                        int screenHeight = viewGroup.getRootView().getHeight();
                        //r.bottom - r.top计算出输入法弹起后viewGroup的高度,屏幕高度-viewGroup高度即为键盘高度
                        Rect r = new Rect();
                        viewGroup.getWindowVisibleDisplayFrame(r);
                        int keyboardHeight = screenHeight - (r.bottom - r.top);
                        //当设置layout_keyboard设置完padding以后会重绘布局再次执行onGlobalLayout()
                        //所以判断如果键盘高度未改变就不执行下去
                        if (keyboardHeight == lastKeyBoardHeight) {
                            return;
                        }
                        lastKeyBoardHeight = keyboardHeight;
                        View view = activity.getWindow().getCurrentFocus();
                        if (keyboardHeight > 300 && null != view) {
                            if (view instanceof TextView) {
                                //计算出键盘最顶端在布局中的位置
                                int keyboardTop = screenHeight - keyboardHeight;
                                int[] location = new int[2];
                                view.getLocationOnScreen(location);
                                //获取登录按钮底部在屏幕中的位置
                                int viewBottom = location[1] + view.getHeight();
                                //比较输入框与键盘的位置关系,如果输入框在键盘之上的位置就不做处理
                                if (viewBottom <= keyboardTop)
                                    return;
                                //需要滚动的距离即为输入框底部到键盘的距离
                                int reSizeLayoutHeight = viewBottom - keyboardTop;
                                reSizeLayoutHeight -= getStatusBarHeight();
                                reSizeLayoutHeight += offset;
                                if (viewGroup instanceof ScrollView) {
                                    ((ScrollView) viewGroup).smoothScrollBy(0, reSizeLayoutHeight);
                                } else if (viewGroup instanceof RecyclerView) {
                                    ((RecyclerView) viewGroup).smoothScrollBy(0, reSizeLayoutHeight);
                                }
                            }
                        }
                    }
                }, 50);
            }
        });
    }

最后:

更详细的参考Demo在github中,我把调整高度的方法封装在一个类中,在Activity中只需要添加一行代码即可。

InputManagerHelper.attachToActivity(this).bind(viewGroup, view).offset(1);

如果有错误也希望大家能够指出,如果觉得能帮到你的话给个Star吧。

上一篇下一篇

猜你喜欢

热点阅读