解决ScrollView与软键盘与Toolbar与EditText的问题,ToolBar被挤上去,ScrollView不能滑动

转载请注明原创出处,谢谢!
  • GitHub: @Ricco

主要看第二种需求

解决问题:
  • 弹出软键盘后,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_main.xml



    
    
    
    

    

        
        
            
            

                              ···

            
        
    

    
    

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与EditText的问题,ToolBar被挤上去,ScrollView不能滑动)