美文网首页Android开发Android技术知识Android开发经验谈
解决ScrollView与软键盘与Toolbar与EditTex

解决ScrollView与软键盘与Toolbar与EditTex

作者: 卖臭豆腐的王致和 | 来源:发表于2018-12-06 14:18 被阅读13次
    转载请注明原创出处,谢谢!

    主要看第二种需求

    解决问题:
    • 弹出软键盘后,ToolBar被挤上去
    • 弹出软键盘后,ScrollView不能滑动
    项目用到的技术:
    • 软键盘监听,设置
    • 对Activity的xml布局进行重绘
    • 状态栏高度获取

    感谢《Blankj/AndroidUtilCode》
    感谢《android全屏/沉浸式状态栏下,各种键盘挡住输入框解决办法》

    ps:最近在写一个界面,界面里面有非常多的EdItText,多到超出了手机的界面,当一个activity里要放的子控件比较多,就需要用都ScrollView,当用上ScrollView开始出现各种坑。比如弹出软键盘后,ToolBar被挤上去,ScrollView不能滑动等。

    而需求一般分为俩种。

    • 第一种:当EditText获得焦点的时候,弹出软键盘,当手指触摸非软键盘部分的时候,软键盘消失,开始上下滑动界面

    这种实现只需要重写Activity的dispatchTouchEvent方法即可。

    dispatchTouchEvent方法中的KeyboardUtils是使用AndroidUtilCode的KeyboardUtils.java中的方法。

        /**
         * 触摸事件
         */
        @Override
        public boolean dispatchTouchEvent(final MotionEvent ev) {
            try {
                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                    View v = getCurrentFocus();
                    if (KeyboardUtils.isShouldHideInput(v, ev)) {
                        KeyboardUtils.hideSoftInput(this);
                    }
                    return super.dispatchTouchEvent(ev);
                }
                if (getWindow().superDispatchTouchEvent(ev)) {
                    return true;
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
            return onTouchEvent(ev);
        }
    
    • 第二种 如果你仔细观察第一种的界面效果,会发现,从一个EditText点击另一个EditText,软键盘会有明显的闪烁,这是由于,dispatchTouchEvent方法发现触摸会让软键盘消失,当EditText得到焦点的时候,软键盘又显示。所以造成了闪烁。

    所以第二种的需求就是,滑动ScrollView的时候,让软键盘不会消失

    如果你没有进行特殊的设置,会发现上面提到的俩个问题,当软键盘弹出的时候,Toolbar显示有问题,ScrollView不能向上滑动。所以我的代码主要解决这2个问题。

    AndroidManifest.xml

     <activity
                android:name=".MainActivity"
                android:screenOrientation="portrait"
                android:windowSoftInputMode="adjustPan" />
    

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <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:orientation="vertical"
        tools:context=".MainActivity">
        <!--状态栏-->
        <View
            android:id="@+id/view"
            android:layout_width="match_parent"
            android:layout_height="28dp"
            android:background="#FFFFFF" />
        <!--ToolBar-->
        <include layout="@layout/tool_bar" />
    
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
    
            <!--默认获取焦点,不弹出软键盘-->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:focusable="true"
                android:orientation="vertical">
                <!--许许多多的其他控件,超过屏幕高度-->
                <EditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content" />
    
                                  ···
    
                <EditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content" />
            </LinearLayout>
        </ScrollView>
    
        <!--提交按钮-->
        <Button
            android:id="@+id/button"
            android:text="提交"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
    

    MainActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
        // 省略findViewById
        SoftHideKeyBoardUtil.assistActivity(this);
        KeyboardUtils.registerSoftInputChangedListener(this, new KeyboardUtils.OnSoftInputChangedListener() {
            @Override
            public void onSoftInputChanged(int height) {
                if (height > 0) {
                    button.setVisibility(View.GONE); // button是xml布局中的提交按钮
                } else {
                    button.setVisibility(View.VISIBLE);
                }
            }
        });
        int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        int statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            statusBarHeight = 0;
        }
        AppBarLayout.LayoutParams layoutParams = new AppBarLayout.LayoutParams(AppBarLayout.LayoutParams.MATCH_PARENT, statusBarHeight);
        view.setLayoutParams(layoutParams); // view是xml布局中的状态栏
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            view.setBackgroundColor(Color.BLACK);
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        KeyboardUtils.unregisterSoftInputChangedListener(this);
    }
    

    SoftHideKeyBoardUtil,基本上没有改变代码,只是加了状态栏高度的代码,原博客的代码,没有状态栏高度,一直是0。。。

    /**
     * 解决键盘档住输入框
     */
    public class SoftHideKeyBoardUtil {
        public static void assistActivity(Activity activity) {
            new SoftHideKeyBoardUtil(activity);
        }
    
        private View mChildOfContent;
        private int usableHeightPrevious;
        private FrameLayout.LayoutParams frameLayoutParams;
        // 为适应华为小米等手机键盘上方出现黑条或不适配
        private int contentHeight; // 获取setContentView本来view的高度
        private boolean isfirst = true; // 只用获取一次
        private int statusBarHeight; // 状态栏高度
    
        private SoftHideKeyBoardUtil(Activity activity) {
            int resourceId = activity.getResources().getIdentifier("status_bar_height", "dimen", "android");
            statusBarHeight = activity.getResources().getDimensionPixelSize(resourceId);
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                statusBarHeight = 0;
            }
            // 1、找到Activity的最外层布局控件,它其实是一个DecorView,它所用的控件就是FrameLayout
            FrameLayout content = activity.findViewById(android.R.id.content);
            // 2、获取到setContentView放进去的View
            mChildOfContent = content.getChildAt(0);
            // 3、给Activity的xml布局设置View树监听,当布局有变化,如键盘弹出或收起时,都会回调此监听
            mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                // 4、软键盘弹起会使GlobalLayout发生变化
                public void onGlobalLayout() {
                    if (isfirst) {
                        contentHeight = mChildOfContent.getHeight(); // 兼容华为,小米等机型
                        isfirst = false;
                    }
                    // 5、当前布局发生变化时,对Activity的xml布局进行重绘
                    possiblyResizeChildOfContent();
                }
            });
            // 6、获取到Activity的xml布局的放置参数
            frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
        }
    
        // 获取界面可用高度,如果软键盘弹起后,Activity的xml布局可用高度需要减去键盘高度
        private void possiblyResizeChildOfContent() {
            // 1、获取当前界面可用高度,键盘弹起后,当前界面可用布局会减少键盘的高度
            int usableHeightNow = computeUsableHeight();
            // 2、如果当前可用高度和原始值不一样
            if (usableHeightNow != usableHeightPrevious) {
                // 3、获取Activity中xml中布局在当前界面显示的高度
                int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
                // 4、Activity中xml布局的高度-当前可用高度
                int heightDifference = usableHeightSansKeyboard - usableHeightNow;
                // 5、高度差大于屏幕1/4时,说明键盘弹出
                if (heightDifference > (usableHeightSansKeyboard / 4)) {
                    // 6、键盘弹出了,Activity的xml布局高度应当减去键盘高度
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
                    } else {
                        frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
                    }
                } else {
                    frameLayoutParams.height = contentHeight;
                }
                // 7、 重绘Activity的xml布局
                mChildOfContent.requestLayout();
                usableHeightPrevious = usableHeightNow;
            }
        }
    
        private int computeUsableHeight() {
            Rect r = new Rect();
            mChildOfContent.getWindowVisibleDisplayFrame(r);
            // 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度
            return (r.bottom - r.top);
        }
    }
    

    KeyboardUtils 专门为实现第二种简化的AndroidUtilCode

    /**
     * 软键盘监听
     */
    public final class KeyboardUtils {
    
        private static int sDecorViewInvisibleHeightPre;
        private static OnGlobalLayoutListener onGlobalLayoutListener;
        private static OnSoftInputChangedListener onSoftInputChangedListener;
        private static int sDecorViewDelta = 0;
    
        private KeyboardUtils() {
            throw new UnsupportedOperationException("工具类,不要实例化。。。");
        }
    
        /**
         * 注册软键盘监听
         */
        public static void registerSoftInputChangedListener(final Activity activity, final OnSoftInputChangedListener listener) {
            final int flags = activity.getWindow().getAttributes().flags;
            if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {
                activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
            }
            final FrameLayout contentView = activity.findViewById(android.R.id.content);
            sDecorViewInvisibleHeightPre = getDecorViewInvisibleHeight(activity);
            onSoftInputChangedListener = listener;
            onGlobalLayoutListener = new OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (onSoftInputChangedListener != null) {
                        int height = getDecorViewInvisibleHeight(activity);
                        if (sDecorViewInvisibleHeightPre != height) {
                            onSoftInputChangedListener.onSoftInputChanged(height);
                            sDecorViewInvisibleHeightPre = height;
                        }
                    }
                }
            };
            contentView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
        }
    
        /**
         * 取消软键盘监听
         */
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        public static void unregisterSoftInputChangedListener(final Activity activity) {
            final View contentView = activity.findViewById(android.R.id.content);
            contentView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
            onSoftInputChangedListener = null;
            onGlobalLayoutListener = null;
        }
    
        private static int getDecorViewInvisibleHeight(final Activity activity) {
            final View decorView = activity.getWindow().getDecorView();
            if (decorView == null) return sDecorViewInvisibleHeightPre;
            final Rect outRect = new Rect();
            decorView.getWindowVisibleDisplayFrame(outRect);
            int delta = Math.abs(decorView.getBottom() - outRect.bottom);
            if (delta <= getNavBarHeight()) {
                sDecorViewDelta = delta;
                return 0;
            }
            return delta - sDecorViewDelta;
        }
    
        private static int getNavBarHeight() {
            Resources res = Resources.getSystem();
            int resourceId = Resources.getSystem().getIdentifier("navigation_bar_height", "dimen", "android");
            if (resourceId != 0) {
                return res.getDimensionPixelSize(resourceId);
            } else {
                return 0;
            }
        }
    
        ///////////////////////////////////////////////////////////////////////////
        //                                  interface                            //
        ///////////////////////////////////////////////////////////////////////////
        public interface OnSoftInputChangedListener {
            // 如果软键盘是关闭状态,height为0,软键盘弹出状态,height为软键盘高度
            void onSoftInputChanged(int height);
        }
    }
    
    为什么需要监听软键盘?

    当我们打开局面的时候,activity的底部,应该显示提交按钮,或者其他控件,这些View是定死在页面底部的。当软键盘显示的时候,提交按钮或者其他控件应该做一个view.setVisibility(View.GONE)操作,放置提交按钮或者其他控件,显示到软键盘上面。

    如果你想软键盘弹出的时候,底部的提交按钮或者其他控件不显示在软键盘上面,还要可以随着ScrollView的向下滑动滑出底部的提交按钮或者其他控件,你就需要在ScrollView中再写一套提交按钮或者其他控件。

    也就是所页面中同时存在2套提交按钮或者其他控件,一套在ScrollView外面,用于当用户第一次打开的时候,给用户一个直观交互,另一套在ScrollView里面,用于当用户点击EditText的时候,向下滑动ScrollView的时候,显示提交按钮或者其他控件,从而造成一个提交按钮或者其他控件一直都在的效果。

    所以这就是需要监听软键盘打开关闭的原因。在默认状态下,ScrollView外面的提交按钮或者其他控件显示,里面的隐藏,当软键盘打开的时候,ScrollView外面的提交按钮或者其他控件隐藏,而外面的显示。

    要注意的是,为了解决问题,我们对Activity的xml布局进行了重绘,所以布局可能出现一些小的偏差。

    相关文章

      网友评论

        本文标题:解决ScrollView与软键盘与Toolbar与EditTex

        本文链接:https://www.haomeiwen.com/subject/jkoncqtx.html