• KeyboardUtil【软键盘弹出后输入框上移一定的高度】


    版权声明:本文为HaiyuKing原创文章,转载请注明出处!

    前言

    演示获取软键盘高度并保存,然后根据输入框的原有位置是否被软键盘挡住了,如果被挡住了则将整体页面上移一定的高度,当软键盘隐藏的时候再下移回来的功能。

    效果图

    代码分析

    KeyboardUtil:显示、隐藏软键盘,以及保存软键盘的高度值;

    KeyboardSharedPreferences:SharedPreferences存储工具类;

    ViewAnimationUtil:上移、下移的动画效果;

    首先,获取软键盘的高度值并保存【当点击输入框,弹出软键盘的时候会进行保存,具体逻辑见代码】

    //计算整体view需要移动的高度值(总高度 - 可见区域高度 + top(标题栏高度) = 隐藏区域高度(软键盘高度值))
            ((RelativeLayout)rootLayout).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                public void onGlobalLayout() {
                    //当保存的高度值小于300的时候执行
                    // 【当APP第一次打开的时候,这里的代码也会执行,那么此时keyboardHeight==0,那么会继续执行下面代码,但是keyboardHeight计算后的值一般会小于300,所以此时不能保存keyboardHeight!!!】
                    // 【当触摸输入框的时候,不管输入框在软键盘上方还是下方,此时keyboardHeight计算后的值>300】【也就是弹出系统软键盘后整体view向上移动的距离(rect.bottom值变小了),也就可以理解为系统软键盘的高度】
                    if(keyboardHeight < 300) {
                        Rect rect = new Rect();
                        rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域
                        Log.e(TAG, "{onGlobalLayout}rootLayout.getRootView().getHeight()=" + rootLayout.getRootView().getHeight());//【移动前的rootLayout的bottom】
                        Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】
                        Log.e(TAG, "{onGlobalLayout}rect.top=" + rect.top);//【标题栏的高度值】
                        keyboardHeight = rootLayout.getRootView().getHeight() - rect.bottom + rect.top;
                        Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);//
                        if (keyboardHeight > 300) {
                            KeyboardUtil.saveKeyboardHeight(MainActivity.this, keyboardHeight);
                        }
                    }else {//方案一
                        Rect rect = new Rect();
                        rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域
                        Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】
                        Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);//
                        if(rect.bottom != keyboardHeight){//代表软键盘隐藏了,当软键盘显示的时候,rect.bottom == keyboardHeight
                            downEditRect();
                        }
                    }
                }
            });

    然后,输入框添加触摸事件监听,用于上移的动画

    //输入框触摸的监听事件
            edt_user.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    //【注意:edt_user.getBottom()是指系统软键盘弹出来后的输入框的bottom值,,缺少顶部标题栏的区域高度,而且连续点击后值不变】
                    Log.i(TAG, "{initViews}edt_user.getBottom()=" + edt_user.getBottom());
                    //计算相对于Windows的坐标
                    int[] locationW = new int[2];
                    edt_user.getLocationInWindow(locationW);
                    Log.i(TAG, "{onTouch}locationW=" + locationW[0] +";" + locationW[1]);
                    //计算相对于Screen的坐标
                    int[] locationS = new int[2];
                    edt_user.getLocationOnScreen(locationS);
                    Log.i(TAG, "{onTouch}locationS=" + locationS[0] +";" + locationS[1]);
                    Log.i(TAG, "{onTouch}edt_user.getMeasuredHeight()=" + edt_user.getMeasuredHeight());//输入框的高度
    
                    int edtBottom = locationW[1] + edt_user.getMeasuredHeight();//输入框的底部的Y坐标值== topY + Height;
                    showEditRect(edt_user,edtBottom);
                    return false;
                }
            });

    最后,监听软键盘隐藏、整体页面添加点击事件,实现下移动画【监听软键盘隐藏是在上面的addOnGlobalLayoutListener方法中实现的

    //整个界面区域的触摸事件
            rootLayout.setOnTouchListener(new View.OnTouchListener() {
                public boolean onTouch(View v, MotionEvent event) {
                    Log.v(TAG, "{initialize}rootLayout=onTouch");
                    //隐藏自定义的软键盘区域
                    hideEditRect();
                    return true;
                }
            });

    使用步骤

    一、项目组织结构图

    注意事项:

    1、  导入类文件后需要change包名以及重新import R文件路径

    2、  Values目录下的文件(strings.xml、dimens.xml、colors.xml等),如果项目中存在,则复制里面的内容,不要整个覆盖

    二、导入步骤

    将keyboard包复制到项目中

    package com.why.project.keyboardutildemo.keyboard;
    
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.res.Resources;
    import android.util.Log;
    import android.view.View;
    import android.view.inputmethod.InputMethodManager;
    
    import com.why.project.keyboardutildemo.R;
    
    
    /**
     * Used 软键盘的操作类(显示、隐藏软件盘、保存软键盘的高度值——用于控制输入框的上升)
     */
    public class KeyboardUtil {
    
        /**最后一次保存的键盘高度*/
        private static int LAST_SAVE_KEYBOARD_HEIGHT = 0;
    
        /**输入法软键盘区域的最大高度*/
        private static int MAX_PANEL_HEIGHT = 0;
        /**输入法软键盘区域的最小高度*/
        private static int MIN_PANEL_HEIGHT = 0;
    
        /**显示软键盘*/
        public static void showKeyboard(View view) {
            if(view != null){
                view.requestFocus();
                InputMethodManager inputManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                if (inputManager != null) {
                    inputManager.showSoftInput(view, 0);
                }
            }
        }
    
        /**隐藏软键盘
         * 第一个参数中的windowToken应当是之前请求显示软键盘的View的windowToken,也就是执行showSoftInput()时第一个参数中的View的windowToken。
         * 但是实际情况是,用任意一个当前布局中的已经加载的View的windowToken都可以隐藏软键盘,哪怕这个View被设置为INVISIBLE或GONE。
         * 因此,如果不知道之前是谁请求显示的软键盘,可以随便传入一个当前布局中存在的View的windowToken。
         * 特别的,可以传入一个Activity的顶层View的windowToken,即getWindow().getDecorView().getWindowToken(),来隐藏当前Activity中显示的软键盘,
         * 而不用管之前调用showSoftInput()的究竟是哪个View。*/
        public static void hideKeyboard(Activity mActivity) {
            InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
            if (imm != null) {
                imm.hideSoftInputFromWindow(mActivity.getWindow().getDecorView().getWindowToken(), 0);
            }
        }
    
        /**隐藏软键盘*/
        public static void hideKeyboard(View view) {
            InputMethodManager imm = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            view.clearFocus();
            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    
        /**是否正在显示软键盘*/
        public static boolean isShowKeyboard(Context context, View view)
        {
            boolean bool = false;
            InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
            if (imm.hideSoftInputFromWindow(view.getWindowToken(), 0))
            {
                imm.showSoftInput(view, 0);
                bool = true;
            }
            return bool;
        }
    
        /**保存软键盘的高度值*/
        public static boolean saveKeyboardHeight(Context context, int keyboardHeight) {
    
            Log.d("KeyboardUtil", "keyboardHeight="+keyboardHeight);
            if(keyboardHeight <= 300 || LAST_SAVE_KEYBOARD_HEIGHT == keyboardHeight) {
                return false;
            }
            LAST_SAVE_KEYBOARD_HEIGHT = keyboardHeight;
    
            return KeyboardSharedPreferences.save(context, keyboardHeight);
        }
        
        /**获取软键盘的高度值*/
        public static int getKeyboardHeight(Context context) {
            if(LAST_SAVE_KEYBOARD_HEIGHT == 0) {
                LAST_SAVE_KEYBOARD_HEIGHT = KeyboardSharedPreferences.get(context, getMinPanelHeight(context.getResources()));
            }
            return LAST_SAVE_KEYBOARD_HEIGHT;
        }
    
        /**获取面板的最大高度值-自定义的*/
        private static int getMaxPanelHeight(Resources res) {
            if(MAX_PANEL_HEIGHT == 0) {
                MAX_PANEL_HEIGHT = res.getDimensionPixelSize(R.dimen.keyboard_content_panel_max_height);
            }
            return MAX_PANEL_HEIGHT;
        }
    
        /**获取面板的最小高度值-自定义的*/
        private static int getMinPanelHeight(Resources res) {
            if(MIN_PANEL_HEIGHT == 0) {
                MIN_PANEL_HEIGHT = res.getDimensionPixelSize(R.dimen.keyboard_content_panel_min_height);
            }
            return MIN_PANEL_HEIGHT;
        }
    }
    KeyboardUtil.java
    package com.why.project.keyboardutildemo.keyboard;
    
    import android.content.Context;
    import android.content.SharedPreferences;
    
    /**
     * Used SharedPreferences存储软键盘的高度值
     */
    public class KeyboardSharedPreferences {
        /**存储文件名*/
        private static final String FILE_NAME = "KeyboardSharedPreferences";
        /**存储Key值*/
        private static final String KEY_KEYBORD_HEIGHT = "keyboardHeight";
    
        private static volatile SharedPreferences SP;
    
        /**实例化SharedPreferences*/
        private static SharedPreferences with(Context context) {
            if(SP == null) {
                synchronized(KeyboardSharedPreferences.class) {
                    if(SP == null) {
                        SP = context.getSharedPreferences(FILE_NAME, 0);
                    }
                }
            }
            return SP;
            
        }
        /**存储软键盘的高度*/
        public static boolean save(Context context, int keyboardHeight) {
            return with(context).edit().putInt(KEY_KEYBORD_HEIGHT, keyboardHeight).commit();
        }
        /**读取存储软键盘的高度(带默认值)*/
        public static int get(Context context, int defaultHeight) {
            return with(context).getInt(KEY_KEYBORD_HEIGHT, defaultHeight);
        }
    
    }
    KeyboardSharedPreferences.java
    package com.why.project.keyboardutildemo.keyboard;
    
    import android.animation.Animator;
    import android.animation.AnimatorListenerAdapter;
    import android.animation.AnimatorSet;
    import android.animation.ObjectAnimator;
    import android.view.View;
    
    /**
     * Used 界面上升下降动画效果工具类(全)
     */
    public class ViewAnimationUtil {
        
        private static ViewAnimationListener mViewAnimationListener = null;
        public ViewAnimationUtil() {
        }
    
        /**区域的上升动画效果*/
        public static void editAreaAnimator(View view, float translationFromY, float translationToY, float scalingFromRatio, float scalingToRatio, final boolean doEnd) {
            ObjectAnimator animTY = ObjectAnimator.ofFloat(view, "translationY", new float[] {translationFromY, translationToY});
            ObjectAnimator animTSX = ObjectAnimator.ofFloat(view, "scaleX", new float[] {scalingFromRatio, scalingToRatio});
            ObjectAnimator animTSY = ObjectAnimator.ofFloat(view, "scaleY", new float[] {scalingFromRatio, scalingToRatio});
            AnimatorSet editAreaSet = new AnimatorSet();
            int EDIT_DURATION = 500;
            editAreaSet.setDuration((long)EDIT_DURATION);
            editAreaSet.playTogether(new Animator[] {animTY, animTSX, animTSY});
            editAreaSet.addListener(new AnimatorListenerAdapter() {
    
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    if((doEnd) && mViewAnimationListener != null) {
                        mViewAnimationListener.endAnimation();
                    }
                }
            });
            editAreaSet.start();
        }
    
    
        public static void setViewAnimationListener(ViewAnimationListener mmViewAnimationListener) {
            mViewAnimationListener = mmViewAnimationListener;
        }
    
        public static abstract interface ViewAnimationListener
        {
            public abstract void endAnimation();
        }
    }
    ViewAnimationUtil.java

    在dimens.xml文件中添加以下代码

    <resources>
        <!-- Default screen margins, per the Android Design guidelines. -->
        <dimen name="activity_horizontal_margin">16dp</dimen>
        <dimen name="activity_vertical_margin">16dp</dimen>
    
        <!-- *********KeyboardUtil相关********* -->
        <dimen name="keyboard_content_panel_max_height">120dp</dimen>
        <dimen name="keyboard_content_panel_min_height">0dp</dimen>
    
    </resources>

    三、使用方法

    输入框的背景

    input_box_send.9.png

    布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/layoutroot"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    
        <!--  当之有一个EditText或者AutoCompleteTextView的时候,进入画面时是默认得到焦点的。 要想去除焦点,可以在auto之前加一个0像素的layout,并设置他先得到焦点。 -->
        <LinearLayout
            android:layout_width="0px"
            android:layout_height="0px"
            android:focusable="true"
            android:focusableInTouchMode="true"/>
    
        <LinearLayout
            android:id="@+id/centerLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="200dp">
    
            <EditText
                android:id="@+id/edt_user"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:inputType="text"
                android:hint="请输入用户名"
                android:lines="1"
                android:background="@drawable/input_box_send"
                android:layout_margin="5dp"
                />
    
            <EditText
                android:id="@+id/edt_send"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:hint="请输入密码"
                android:lines="1"
                android:imeOptions="actionGo"
                android:inputType="textPassword"
                android:background="@drawable/input_box_send"
                android:layout_margin="5dp"
                />
    
        </LinearLayout>
    
    </RelativeLayout>

    其中第一个输入框的原有位置在弹起的软键盘上方,第二个输入框的原有位置在弹起的软键盘下方。

    activity中使用如下

    package com.why.project.keyboardutildemo;
    
    import android.graphics.Rect;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.ViewTreeObserver;
    import android.widget.EditText;
    import android.widget.RelativeLayout;
    
    import com.why.project.keyboardutildemo.keyboard.KeyboardUtil;
    import com.why.project.keyboardutildemo.keyboard.ViewAnimationUtil;
    
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = MainActivity.class.getSimpleName();
    
        private RelativeLayout rootLayout;//整体View
    
        private EditText edt_send;//输入框view
        private EditText edt_user;//输入框view
    
        /**区域是否上升了*/
        private boolean editAreaIsUp = false;
        /**软键盘的高度值*/
        private int keyboardHeight = 0;
        /**需要上升的高度值*/
        private int textBottom = 0;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initViews();
        }
    
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
        }
    
        private void initViews() {
            rootLayout = (RelativeLayout) findViewById(R.id.layoutroot);
            edt_send = (EditText) findViewById(R.id.edt_send);
            edt_user = (EditText) findViewById(R.id.edt_user);
    
            //计算整体view需要移动的高度值(总高度 - 可见区域高度 + top(标题栏高度) = 隐藏区域高度(软键盘高度值))
            ((RelativeLayout)rootLayout).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                public void onGlobalLayout() {
                    //当保存的高度值小于300的时候执行
                    // 【当APP第一次打开的时候,这里的代码也会执行,那么此时keyboardHeight==0,那么会继续执行下面代码,但是keyboardHeight计算后的值一般会小于300,所以此时不能保存keyboardHeight!!!】
                    // 【当触摸输入框的时候,不管输入框在软键盘上方还是下方,此时keyboardHeight计算后的值>300】【也就是弹出系统软键盘后整体view向上移动的距离(rect.bottom值变小了),也就可以理解为系统软键盘的高度】
                    if(keyboardHeight < 300) {
                        Rect rect = new Rect();
                        rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域
                        Log.e(TAG, "{onGlobalLayout}rootLayout.getRootView().getHeight()=" + rootLayout.getRootView().getHeight());//【移动前的rootLayout的bottom】
                        Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】
                        Log.e(TAG, "{onGlobalLayout}rect.top=" + rect.top);//【标题栏的高度值】
                        keyboardHeight = rootLayout.getRootView().getHeight() - rect.bottom + rect.top;
                        Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);//
                        if (keyboardHeight > 300) {
                            KeyboardUtil.saveKeyboardHeight(MainActivity.this, keyboardHeight);
                        }
                    }else {//方案一
                        Rect rect = new Rect();
                        rootLayout.getWindowVisibleDisplayFrame(rect);//rect指可见区域
                        Log.e(TAG, "{onGlobalLayout}rect.bottom=" + rect.bottom);//【移动后的rootLayout的bottom】
                        Log.e(TAG, "{onGlobalLayout}keyboardHeight=" + keyboardHeight);//
                        if(rect.bottom != keyboardHeight){//代表软键盘隐藏了,当软键盘显示的时候,rect.bottom == keyboardHeight
                            downEditRect();
                        }
                    }
                }
            });
            //整个界面区域的触摸事件
            rootLayout.setOnTouchListener(new View.OnTouchListener() {
                public boolean onTouch(View v, MotionEvent event) {
                    Log.v(TAG, "{initialize}rootLayout=onTouch");
                    //隐藏自定义的软键盘区域
                    hideEditRect();
                    return true;
                }
            });
    
            //输入框触摸的监听事件
            edt_user.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    //【注意:edt_user.getBottom()是指系统软键盘弹出来后的输入框的bottom值,,缺少顶部标题栏的区域高度,而且连续点击后值不变】
                    Log.i(TAG, "{initViews}edt_user.getBottom()=" + edt_user.getBottom());
                    //计算相对于Windows的坐标
                    int[] locationW = new int[2];
                    edt_user.getLocationInWindow(locationW);
                    Log.i(TAG, "{onTouch}locationW=" + locationW[0] +";" + locationW[1]);
                    //计算相对于Screen的坐标
                    int[] locationS = new int[2];
                    edt_user.getLocationOnScreen(locationS);
                    Log.i(TAG, "{onTouch}locationS=" + locationS[0] +";" + locationS[1]);
                    Log.i(TAG, "{onTouch}edt_user.getMeasuredHeight()=" + edt_user.getMeasuredHeight());//输入框的高度
    
                    int edtBottom = locationW[1] + edt_user.getMeasuredHeight();//输入框的底部的Y坐标值== topY + Height;
                    showEditRect(edt_user,edtBottom);
                    return false;
                }
            });
    
            //输入框触摸的监听事件
            edt_send.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    //【注意:edt_send.getBottom()是指系统软键盘弹出来后的输入框的bottom值,缺少顶部标题栏的区域高度,而且连续点击后值不变】
                    Log.i(TAG, "{initViews}edt_send.getBottom()=" + edt_send.getBottom());
                    //计算相对于Windows的坐标
                    int[] locationW = new int[2];
                    edt_send.getLocationInWindow(locationW);
                    Log.i(TAG, "{initViews}locationW1=" + locationW[0] +";" + locationW[1]);
                    //计算相对于Screen的坐标
                    int[] locationS = new int[2];
                    edt_send.getLocationOnScreen(locationS);
                    Log.i(TAG, "{initViews}locationS1=" + locationS[0] +";" + locationS[1]);
    
                    int edtBottom = locationW[1] + edt_send.getMeasuredHeight();//输入框的底部的Y坐标值== topY + Height;
                    showEditRect(edt_send,edtBottom);
    
                    return false;
                }
            });
        }
    
    
        /**
         * 显示自定义的软键盘区域
         * @param view - 输入框view
         * @param bottom  输入框的bottom【弹出系统软键盘后的值】*/
        public void showEditRect(final View view, final int bottom) {
    
            //实现编辑区域的上升动画效果
            view.postDelayed(new Runnable() {
                public void run() {
                    Log.w(TAG, "(showEditRect)bottom="+bottom);
                    Log.w(TAG, "(showEditRect)keyboardHeight="+keyboardHeight);
                    if(keyboardHeight != 0 && bottom - keyboardHeight > 0){//为什么需要判断bottom - keyboardHeight > 0??因为当已经弹出软键盘后继续点击输入框的时候,就不需要在上移了,而可以通过bottom值变小了来解决继续上移的问题。
                        textBottom = view.getMeasuredHeight();
    
                        Log.w(TAG, "(showEditRect)textBottom="+textBottom);
                        makeEditAreaUpAndSmall(((float)textBottom));
                    }
                }
            }, 300);
    
        }
    
        /**隐藏自定义软键盘区域*/
        private void hideEditRect(){
            KeyboardUtil.hideKeyboard(this);
            downEditRect();
        }
        /**下移*/
        private void downEditRect(){
            if(textBottom > 0) {
                makeEditAreaOriginal((float)textBottom);
                textBottom = 0;
            }
        }
    
        /**编辑区域上升的动画效果:指定高度*/
        public void makeEditAreaUpAndSmall(float to)
        {
            if (!this.editAreaIsUp)
            {
                ViewAnimationUtil.editAreaAnimator(rootLayout, 0.0F, -to, 1.0F, 1.0F,false);
                this.editAreaIsUp = true;
            }
        }
        /**编辑区域回到原始的动画效果*/
        public void makeEditAreaOriginal(float from)
        {
            if(this.editAreaIsUp) {
                ViewAnimationUtil.editAreaAnimator(rootLayout, -from, 0.0F, 1.0F, 1.0F,false);
                this.editAreaIsUp = false;
            }
        }
    }

    混淆配置

    参考资料

    暂时空缺

    项目demo下载地址

    https://github.com/haiyuKing/KeyboardUtilDemo

  • 相关阅读:
    jQuery 语法
    HTML DOM Document 对象
    JavaScript
    JavaScript Cookies
    JavaScript 计时事件
    九度OJ 1352 和为S的两个数字
    九度0J 1374 所有员工年龄排序
    九度OJ 1373 整数中1出现的次数(从1到n整数中1出现的次数)
    九度OJ 1370 数组中出现次数超过一半的数字
    九度OJ 1361 翻转单词顺序
  • 原文地址:https://www.cnblogs.com/whycxb/p/9288419.html
Copyright © 2020-2023  润新知