歡迎光臨
每天分享高質量文章

Android 簡單沉浸式彈出輸入框

作者:Gxinyu

連結:https://www.jianshu.com/p/630cbdcedc85

前言

最近公司專案在寫IM聊天室功能,剛開始使用dialog方式,讓dialog居底部顯示,但是專案中需要文字和表情切換髮送訊息,但是因為軟鍵盤本來就是一種特殊的dialog,dialog具有優先順序,軟鍵盤的優先順序總是高於我們平時用到的dialog,所以出現顯示消失的時候,因為軟鍵盤與自定義的輸入彈出框消失顯示時機問題,導致閃爍問題。

網上還有一種方式是在activity的底部直接新增一個view的方式,透過約束其上的佈局來解決閃爍問題,但是這種情況輸入彈窗會把整個佈局頂上去,感覺體驗不是特別好。

所以決定去看看其他APP的效果,發現頭條和簡書的彈出輸入框都是都不錯,如下圖所示:但是頭條的彈出輸入框在彈出時,系統狀態列會變色,感覺影響體驗,簡書的彈出輸入框是在底部直接新增view的方式,只不過是增加了遮罩層。如何做到效果和dialog一致,點選外部和傳回鍵收起,然後還能切換文字和表情輸入,另外不會引起系統狀態列變色,。

頭條.png
簡書輸入框.png

佈局新增View

因為要實現當輸入框彈出的時候一是不能讓原來的內容頂上去,二是輸入框內容要改寫在當前內容的上層。所以不能採取直接在activity的佈局底部直接填加view的方式,也不能是dialog的形式,所以只能新增到activity的頂層佈局,activity的頂層佈局是decorView,然後新增到底部,操作軟鍵盤的隱藏顯示來調取彈出輸入框,顯示軟鍵盤的時候新增,彈出框消失的時候移除。

新增到DecorView

/**
* 新增
*/
if (mContext instanceof Activity) {
    Activity activity = (Activity) mContext;
     final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
     final ViewGroup content = (ViewGroup) decorView.findViewById(android.R.id.content);
     if (getParent() == null) {
           content.addView(this);
     }
}

從DecorView移除

//移除掉當前的view
        ViewParent parent = getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(this);
        }

註意事項:因為彈出框是在下方顯示,所以新增到DecorView的頂級View要位於DecorView底部


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    //新增到底部
    android:layout_gravity="bottom"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical">
LinearLayout>

 

點選外部消失

如果點選輸入框外面,取消輸出框體驗更好。為此我們監聽當前彈出框的onTouchEvent事件:

/**
     * 處理點選範圍,如果在不在就取消
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Rect rect = new Rect();
        getInputView().getGlobalVisibleRect(rect);
        if (!WindowUtil.touchIsInRect(event.getX(), event.getY(), rect)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = event.getX();
                    downY = event.getY();
                    downTime = System.currentTimeMillis();
                    break;
                case MotionEvent.ACTION_UP:
                    float dx = event.getX() - downX;
                    float dy = event.getY() - downY;
                    float distance = (float) Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
                    if (distance                         //點選外部就消失
                        onInputListener.onCanceledOnTouchOutside();
                    }
                    downX = 0;
                    downY = 0;
                    downTime = 0;
                    break;
            }
        }
        return true;
    }

觸點說明:
1、WindowUtil.touchIsInRect(event.getX(), event.getY(), rect) 檢測當前觸點是否在彈出框之中
2、onTouchEvent傳回值設定為true,響應事件

點選傳回鍵消失

 getInputView().setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
                    if (onInputListener != null) {
                        onInputListener.onBackPressed();
                    }
                    return true;
                }
                return false;
            }
        });

傳回值一定要為true,否則失效.

如果你設定了這個方法,但是發現沒有響應,是因為其他地方搶佔了焦點,可能是edittext或者是軟鍵盤,這裡說明一下軟鍵盤也是一種dialog,dialog預設搶佔焦點,所有新增如下方法:

View inputView = getInputView();
            inputView.requestFocus();
            inputView.setFocusable(true);
            inputView.setFocusableInTouchMode(true);

當前彈出框搶佔焦點之後,點選back鍵就會取消軟鍵盤,由於軟鍵盤消失有動畫,需要時間,所以有兩種方式可以取消彈出框,第一種傳送延遲訊息的方式,這種方式由於軟鍵盤彈出時間不確定並且如果多次主動彈出會造成訊息衝突,如果採用這種方式,儘量去使用防止重覆點選監聽,第二種是定義中間變數,再監聽軟鍵盤消失賦值為消失狀態,這時候是消失狀態但不是銷毀狀態。

/**
 * 輸入框的四種狀態
 */
public static final int INITIALIZE = 0;
public static final int SHOWING = 1;
public static final int DISMISSED= 2;
public static final int DISMISS_OUTSIDE = 100;
public static final int DISMISS_BACKKEY = 101;
public static final int DISMISS_KEYBOARD = 103;
private int currentState = INITIALIZE;

說明:因為之前為了響應傳回鍵和touch事件,強制獲取焦點,當彈出框消失的時候主動把焦點傳回給當前頁面,如果不這麼做,可能會遇到其他問題,比如RecyclerView滾動事件等。

     clearFocus();
     View contentView = ((Activity) getContext()).findViewById(android.R.id.content);
     contentView.setFocusable(true);
     contentView.setFocusableInTouchMode(true);

樣式切換

檢視其它APP,有時候會切換到表情頁面,有些APP的軟鍵盤表情頁面跟軟鍵盤高度不一致導致有錯位的感覺,如果表情頁面跟軟鍵盤高度一致體驗會好很多,所以我在初始化的時候設定表情頁面的高度。

        rlSpecialContent.setVisibility(INVISIBLE);
        ViewGroup.LayoutParams linearParams = (ViewGroup.LayoutParams) rlSpecialContent.getLayoutParams();
        int lastHeight = linearParams.height;
        if (bottomHeight != lastHeight) {
            //不重覆設定
            linearParams.height = bottomHeight;
            rlSpecialContent.setLayoutParams(linearParams);
        }

說明:佈局中關於表情頁面設定Gone,減少繪製,但是顯示彈出框的時候一定要給表情頁面佔位rlSpecialContent.setVisibility(INVISIBLE);並且之後切換的過程中也是如此,不能設定為Gone,否則有閃動錯位效果,影響體驗。

其他

由於本人比較懶,個人覺得如果彈出框加上動畫可能更好一些,不過畢竟動畫非常消耗資源,暫且就沒加,可以自行新增,由於軟鍵盤方式和各個手機廠商對於Rom的修改,不知道在其他型號手機上是否會出現未知問題,大家如果遇到問題,及時反饋給我,在此感謝大家閱讀本文。

最後附上程式碼
https://github.com/Gxinyu/FineInput

已同步到看一看
贊(0)

分享創造快樂