1. 程式人生 > >KeyboardUtil【軟鍵盤彈出後輸入框上移一定的高度】

KeyboardUtil【軟鍵盤彈出後輸入框上移一定的高度】

together true 添加 存儲 oge 底部 putty 如果 original

版權聲明:本文為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

KeyboardUtil【軟鍵盤彈出後輸入框上移一定的高度】