美文网首页安卓
安卓 点击非输入控件隐藏软键盘

安卓 点击非输入控件隐藏软键盘

作者: 树蜂 | 来源:发表于2018-10-26 09:42 被阅读0次

前面做简单分析,后面附完整代码。

需求:
如标题,安卓app中点击非输入控件时,假若软键盘是打开的,则自动隐藏软键盘。

在activity中重写dispatchTouchEvent(MotionEvent ev)函数

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        if (ev.getAction() == MotionEvent.ACTION_DOWN)
        {
            View v = getCurrentFocus();
            hideSoftInput(v, ev);

            return super.dispatchTouchEvent(ev);
        }
        // 必不可少,否则所有的组件都不会有TouchEvent了
        if (activity.getWindow().superDispatchTouchEvent(ev))
        {
            return true;
        }
        return activity.onTouchEvent(ev);
    }

    private void hideSoftInput(View v, MotionEvent ev)
    {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null && imm.isActive())
        {
            if (isShouldHideInput(v, ev))
            {
                if (v == null)
                {
                    v = getWindow().getDecorView();
                }
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }

    /**
     * 判断是否要隐藏软键盘。
     * 除了EditText外,点击了其他地方,都要隐藏。
     * @param v
     * @param event
     * @return
     */
    public boolean isShouldHideInput(View v, MotionEvent event)
    {
        if (v != null && (v instanceof EditText))
        {
            return !isPointInView(v, (int)event.getX(), (int)event.getY());
        }
        return true;
    }

    /**
     * 判断位置(x, y)是否在view的显示范围内
     * @param view
     * @param x
     * @param y
     * @return
     */
    public boolean isPointInView(View view, int x, int y)
    {
        int[] location = new int[2];
        // 点击事件获取的坐标为相对于window(如果activity是全屏的,Screen和Window一样)
        //        view.getLocationOnScreen(location);
        view.getLocationInWindow(location);
        int left = location[0];
        int top = location[1];
        int right = left + view.getMeasuredWidth();
        int bottom = top + view.getMeasuredHeight();
        if (
                y>=top && y<=bottom
                        && x>=left && x<=right
                )
        {
            return true;
        }
        return false;
    }

该代码正常可以使用。
但如果屏幕中有多个EditText,当软键盘已开启,此时点击另外EditText时,软键盘会先隐藏再出现。主要是如下代码

View v = getCurrentFocus();
hideSoftInput(v, ev);

获取的 View v 仍在上一个 EditText中。

----------------------------- 你爱管不管的分割线 -----------------------------

为了不出现以上现象,同时对代码做封装,添加两个类
类一: ViewGetter.java

import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;

/**
 * @desc 通过点击的坐标(x, y)获取相应的View
 * @auth jsf
 * @time 2018/10/24 16:39
 */
public class ViewGetter
{
    private Activity activity;
    public ViewGetter(Activity pActivity)
    {
        activity = pActivity;
    }

    /**
     * 通过点击的坐标(x, y)获取相应的View
     * @param x
     * @param y
     * @return
     */
    public View getView(int x, int y)
    {
        if (activity == null)
        {
            return null;
        }
        View root = activity.getWindow().getDecorView();
        return findViewByXY(root, x, y);
    }
    
    /**
     * 遍历所有view,返回触摸位置(x, y)在某个view的显示范围内的view
     * @param view
     * @param x
     * @param y
     * @return
     */
    private View findViewByXY(View view, int x, int y)
    {
        if (view instanceof ViewGroup)
        {
            ViewGroup v = (ViewGroup)view;
            for (int i=0; i<v.getChildCount();i++)
            {
                View targetView = findViewByXY(v.getChildAt(i), x, y);
                if (targetView != null)
                {
                    return targetView;
                }
            }
        }
        else
        {
            if (isPointInView(view, x, y))
            {
                return view;
            }
        }
        return null;
    }


    /**
     * 判断位置(x, y)是否在view的显示范围内
     * @param view
     * @param x
     * @param y
     * @return
     */
    public boolean isPointInView(View view, int x, int y)
    {
        int[] location = new int[2];
        // 点击事件获取的坐标为相对于window(如果activity是全屏的,Screen和Window一样)
        //        view.getLocationOnScreen(location);
        view.getLocationInWindow(location);
        int left = location[0];
        int top = location[1];
        int right = left + view.getMeasuredWidth();
        int bottom = top + view.getMeasuredHeight();
        if (
                y>=top && y<=bottom
                        && x>=left && x<=right
                )
        {
            return true;
        }
        return false;
    }
}

类二: HideSoftInputByTouch.java

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;

/**
 * @desc
 * @auth jsf
 * @time 2018/10/24 17:10
 */
public class HideSoftInputByTouch
{
    private final String TAG = HideSoftInputByTouch.class.getSimpleName();
    private Activity activity;
    IDispatchTouchEvent dispatchTouchEvent;
    ViewGetter viewGetter;

    /**
     * 通过注入的方式,调用 Activity 父类的 dispatchTouchEvent 函数
     */
    public interface IDispatchTouchEvent
    {
        boolean dispatchTouchEvent(MotionEvent ev);
    }

    public void setDispatchTouchEvent(IDispatchTouchEvent dispatchTouchEvent)
    {
        this.dispatchTouchEvent = dispatchTouchEvent;
    }

    public HideSoftInputByTouch(Activity pActivity)
    {
        activity = pActivity;
        viewGetter = new ViewGetter(activity);
    }
    
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        if (ev.getAction() == MotionEvent.ACTION_DOWN)
        {
            // 如果通过 getCurrentFocus() 获取,假设原先焦点已在EditText上,再次点击时,会获取到原先焦点所在的EditText
            // 因此会先隐藏软键盘,然后再显示,体验不好。
            // 所以获取点击所在位置的view
            //            View v = getCurrentFocus();
            View v = viewGetter.getView((int)ev.getX(), (int)ev.getY());
            hideSoftInput(v, ev);

            // 正常可以使用,
            // 但用该函数,假如activity的主题为Theme.AppCompat.Light.NoActionBar,
            // 并设置了Activity为对话框形式,且可通过 setFinishOnTouchOutside(true); 设置点击对话框外部关闭activity,
            // 则可点击无效
//            return activity.getWindow().superDispatchTouchEvent(ev);
            if (dispatchTouchEvent != null)
            {
                return dispatchTouchEvent.dispatchTouchEvent(ev);
            }
        }
        // 必不可少,否则所有的组件都不会有TouchEvent了
        if (activity.getWindow().superDispatchTouchEvent(ev))
        {
            return true;
        }
        return activity.onTouchEvent(ev);
    }

    private void hideSoftInput(View v, MotionEvent ev)
    {
        InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null && imm.isActive())
        {
            if (isShouldHideInput(v, ev))
            {
                if (v == null)
                {
                    v = activity.getWindow().getDecorView();
                }
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }

    /**
     * 判断是否要隐藏软键盘。
     * 除了EditText外,点击了其他地方,都要隐藏。
     * @param v
     * @param event
     * @return
     */
    public boolean isShouldHideInput(View v, MotionEvent event)
    {
        if (v != null && (v instanceof EditText))
        {
            return !viewGetter.isPointInView(v, (int)event.getX(), (int)event.getY());
        }
        return true;
    }
}

使用方法:
1、设置接口HideSoftInputByTouch.IDispatchTouchEvent();
2、重写 dispatchTouchEvent(MotionEvent ev) 函数。

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;

import com.gree.gongyi.washer.client.tools.HideSoftInputByTouch;

/**
 * @desc 点击EditText外的区域,隐藏软键盘
 * @auth jsf
 * @time 2018/10/24 11:26
 */
public class TestActivity extends AppCompatActivity
{
    HideSoftInputByTouch hideSoftInputByTouch;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        hideSoftInputByTouch = new HideSoftInputByTouch(this);
        hideSoftInputByTouch.setDispatchTouchEvent(new HideSoftInputByTouch.IDispatchTouchEvent()
        {
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev)
            {
                return HideSoftInputActivity.super.dispatchTouchEvent(ev);
            }
        });
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return hideSoftInputByTouch.dispatchTouchEvent(ev);
    }
}

为了调用 Activity 父类 的 dispatchTouchEvent(ev),使用接口进行注入,不清楚是否有其他更便捷的方式,还请指出,感谢!

为了更好的了解点击安卓屏幕后发生了些什么事,建议看下下面的文章
图解 Android 事件分发机制
安卓事件分发学习之dispatchTouchEvent方法

结束。

相关文章

网友评论

    本文标题:安卓 点击非输入控件隐藏软键盘

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