Android: 實現表情輸入鍵盤的另外一種思路

終於有時間繼續寫博客了,上一篇博客中提到的使用「adjustPan」模式來實現表情輸入鍵盤的思路,在這一段時間的使用過程中出現了很多兼容性問題,各種機型(有無虛擬按鍵)、系統版本(高度返回值是否包含狀態欄)返回的軟鍵盤高度與實際的軟鍵盤高度不同,監聽的調用方式也不盡相同,最後導致界面出現錯位。起始回頭仔細想想,讓軟鍵盤覆蓋表情鍵盤這種辦法從代碼角度看確實很傻。。。不夠優雅。

研究了一段時間、翻看了一些開源項目的實現原理,特別是著名的「四次元」,給了我很大啓發,我重寫了一遍依賴庫,給北郵人論壇客戶端更新後,再沒有人反饋表情鍵盤有問題了,下面簡單談談實現的思路。

SoftInputMode 模式

首先,Android系統在界面上彈出軟鍵盤時會將整個Activity的高度壓縮,即默認的SoftInputMode是「AdjustResize」,直覺上表情鍵盤的高度應該設置得和軟鍵盤相同,顯示錶情鍵盤時的同時將軟鍵盤收起。這種思路比較直觀,比「AdjustPan」模式好的地方在於表情鍵盤只有兩種狀態(Visible/Gone),而不是三種(Visible/Invisible/Gone),處理起來邏輯上會更簡單。

表情鍵盤的高度

如果按照上面的思路直接寫,應該是這樣:

emotionButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mEmotionLayout.isShown()) {
            hideEmotionLayout();
        } else {
            showEmotionLayout();
        }
    }
});

代碼很簡單,幾乎沒有別的邏輯判斷,同樣的,我們在顯示錶情鍵盤之前,需要判斷一下軟鍵盤的高度,將其設置給表情鍵盤:

private void showEmotionLayout() {
    int softInputHeight = getSupportSoftInputHeight();
    if (softInputHeight == 0) {
        softInputHeight = sp.getInt(SHARE_PREFERENCE_TAG, 400);
    }
    hideSoftInput();
    mEmotionLayout.getLayoutParams().height = softInputHeight;
    mEmotionLayout.setVisibility(View.VISIBLE);
}
private void hideEmotionLayout(boolean showSoftInput) {
    if (mEmotionLayout.isShown()) {
        mEmotionLayout.setVisibility(View.GONE);
        if (showSoftInput) {
            showSoftInput();
        }
    }
}

界面跳動

但是運行起來會發現一個問題,點擊按鈕打開表情鍵盤時,輸入框會上下跳動一下。分析一下原因,點擊按鈕後,先收起了軟鍵盤,當前Activity的高度變高,輸入框回到了界面底部;再打開表情鍵盤時,輸入框又被頂上來,所有看起來點擊按鈕後輸入框會上下跳動一下。無論是先隱藏軟鍵盤還是先打開表情鍵盤都會有這個問題。

如果這時候去糾結隱藏軟鍵盤和打開表情鍵盤如何同步的話就會走進一個牛角尖,去處理不同機型之間的兼容性問題了。其實解決思路非常簡單,輸入框不是會上下跳麼,那固定它的位置不就好了?

舉個例子,如果整個界面的根佈局是LinearLayout,那麼一個控件的位置其實是由它上面所有控件的高度決定的,如果它上面所有控件的高度都不變化,那即使整個Activity的高度變化(開/關軟鍵盤)也不會影響這個控件的位置,也就不會發生跳動了。

我們假設有這樣兩個方法lockContentHeight()unlockContentHeight(),用來鎖定和解鎖輸入框上面的所有控件的高度,那麼點擊按鈕的監聽就應該這樣寫:

@Override
public void onClick(View v) {
    if (mEmotionLayout.isShown()) {
        lockContentHeight();
        hideEmotionLayout(true);
        unlockContentHeight();
    } else {
        if (isSoftInputShown()) {
            lockContentHeight();
            showEmotionLayout();
            unlockContentHeight();
        } else {
            showEmotionLayout();
        }
    }
}

爲了更好的表現形式,這裏的unlockContentHeight()可以替換成unlockContentHeightDelayed(),即延遲一會(例如200ms)再解鎖高度,留出播放軟鍵盤收回動畫的時間。

鎖定和解鎖高度

現在整個問題的關鍵就在於鎖定和解鎖控件的高度了,那麼如何實現lockContentHeight()unlockContentHeight()這兩個函數呢?我們仍以根佈局是LinearLayout爲例,一個典型的界面佈局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        />

    <include
        layout="@layout/reply_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

其中ListView的layout_height爲0dp、layout_weight爲1,這樣這個ListView就會自動充滿整個佈局,這裏ListView可以替換成任意控件(比如一個RelativeLayout,內部有更復雜的佈局)。

當你需要鎖定這個ListView的高度時:

private void lockContentHeight() {
    LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams();
    params.height = mContentView.getHeight();
    params.weight = 0.0F;
}

將weight置0,然後將height設置爲當前的height,在父控件(LinearLayout)的高度變化時它的高度也不再會變化。而解鎖高度時這樣做:

private void unlockContentHeightDelayed() {
    mEditText.postDelayed(new Runnable() {
        @Override
        public void run() {
            ((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;
        }
    }, 200L);
}

在上面的函數中解鎖高度其實只有一句話:LinearLayout.LayoutParams.weight = 1.0F;,在Java代碼裏動態更改LayoutParam的weight,會導致父控件重新onLayout(),從而達到改變控件的高度的目的。

總結

整體思路基本上就是:

  1. 點擊表情按鈕
  2. 鎖定內容高度
  3. 收起軟鍵盤
  4. 顯示錶情鍵盤
  5. 解鎖內容高度

是不是比上一篇博客中分析三種顯示狀態,監聽軟鍵盤變化而做同步變化的方法要簡單明瞭很多?

詳細代碼見我的Github:Android-EmotionInputDetector

本文首發http://www.dss886.com,轉載請註明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章