Android軟鍵盤和底部面板的無縫切換

開局就是二張圖。先看效果是不是自己想要的,然後再慢慢往下看。第一張圖是默認高度,第二張圖是高度增加之後的變化。可以看到,切換還是蠻順暢的,沒有出現跳閃的情況。         

      

 

 

這兩天在弄一個留言板功能,可進行文字、語音、圖片還有其他功能的留言。這時就需要一個面板來存放這些附加功能。一般都是輸入框旁邊有個加號,來進行面板的彈出。但是我當我底部面板出現時,會出現跳閃問題,就是面板和輸入框會被軟鍵盤頂到上面去,然後等軟鍵盤收起來,面板才降下來,這種效果體驗起來不是很好,就先自己想了想,但也沒想到好的解決辦法,又看了看微信、QQ、支付寶的聊天界面,體驗效果很好,並且當軟鍵盤的高度變化時,面板的高度也會隨之變化。體驗效果很好。

        算了,還是百度,搜了搜知道了大致的解決方案。就是當面板和輸入框切換時去改變軟鍵盤的顯示模式。一般我們改變軟鍵盤的顯示模式都是在清單文件AndroidManifest.xml中對Activity的windowSoftInputMode標籤進行修改。但這次我們需要在代碼總動態的去改變。主要是針對這兩種模式進行改變。如下

當屬性爲adjustResize的時候,軟鍵盤彈出時會擠壓Activity窗口大小,這樣可以保證輸入法不會覆蓋到輸入框;

當屬性爲adjustNothing的時候,軟鍵盤彈出時會覆蓋到當前窗口上,窗口大小不變,這種情況輸入法有可能會覆蓋輸入框。

知道解決的關鍵點之後,還需知道軟鍵盤的高度,以及軟鍵盤的隱藏和顯示狀態。這樣就可以對不同的情況進行相應的改變。

先上代碼獲取軟鍵盤的高度和顯示、隱藏狀態的代碼。

public class KeyboardLayout extends FrameLayout {

    private KeyboardLayoutListener mListener;
    private boolean mIsKeyboardActive = false; // 輸入法是否激活
    private int mKeyboardHeight = 0; // 輸入法高度

    public KeyboardLayout(Context context) {
        this(context, null, 0);
    }

    public KeyboardLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public KeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 監聽佈局變化
        getViewTreeObserver().addOnGlobalLayoutListener(new KeyboardOnGlobalChangeListener());
    }

    private class KeyboardOnGlobalChangeListener implements ViewTreeObserver.OnGlobalLayoutListener {

        int mScreenHeight = 0;

        private int getScreenHeight() {
            if (mScreenHeight > 0) {
                return mScreenHeight;
            }
            mScreenHeight = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
                    .getDefaultDisplay().getHeight();
            return mScreenHeight;
        }

        @Override
        public void onGlobalLayout() {
            Rect rect = new Rect();
            // 獲取當前頁面窗口的顯示範圍
            ((Activity) getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
            int screenHeight = getScreenHeight();
            int keyboardHeight = screenHeight - rect.bottom; // 輸入法的高度
            boolean isActive = false;
            if (Math.abs(keyboardHeight) > screenHeight / 5) {
                isActive = true; // 超過屏幕五分之一則表示彈出了輸入法
                mKeyboardHeight = keyboardHeight;
            }
            mIsKeyboardActive = isActive;
            if (mListener != null) {
                mListener.onKeyboardStateChanged(isActive, keyboardHeight);
            }
        }
    }

    public void setKeyboardListener(KeyboardLayoutListener listener) {
        mListener = listener;
    }

    public KeyboardLayoutListener getKeyboardListener() {
        return mListener;
    }

    public boolean isKeyboardActive() {
        return mIsKeyboardActive;
    }

    /**
     * 獲取輸入法高度
     *
     * @return
     */
    public int getKeyboardHeight() {
        return mKeyboardHeight;
    }

    public interface KeyboardLayoutListener {
        /**
         * @param isActive       輸入法是否激活
         * @param keyboardHeight 輸入法面板高度
         */
        void onKeyboardStateChanged(boolean isActive, int keyboardHeight);
    }
}

這個類是一個自定義佈局類,只是用來監聽軟鍵盤的狀態,使用時直接放到對應Activity的佈局下即可。不需用給寬高屬性。

  <com.dfzb.bloodinstitutepatient.widget.KeyboardLayout
        android:id="@+id/conversation_keyboard_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

主體的邏輯代碼。

public class ConversationActivity extends BaseActivity {

    //底部面板View
    @BindView(R.id.conversation_rl_bottom_other)
    LinearLayout llBottomPanel;
    //輸入框
    @BindView(R.id.conversation_et_chat)
    EditText et;
    //內容顯示區域,當點擊此區域時,隱藏軟鍵盤和麪板
    @BindView(R.id.conversation_rl_content)
    RelativeLayout rlContent;
    //監聽軟鍵盤View
    @BindView(R.id.conversation_keyboard_layout)
    KeyboardLayout keyboardLayout;


    private int llBottomOtherHeight;

    private int screenHeight = 0;

    private boolean isShowingInput = false;//軟鍵盤是否正在顯示

    private Context context = ConversationActivity.this;

    private int mKeyboardHeight = 400; // 輸入法默認高度爲400

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_conversation);
        ButterKnife.bind(this);
        initTitleBar(true, true, "留言板");
        initData();
        initView();
    }


    private void initData() {
        llBottomOtherHeight = DimesionUtils.getDimensionPixelSize(R.dimen.x550);
        //獲取屏幕高度  
        screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();
    }

    private void initView() {
        rootView.addOnLayoutChangeListener(this);
        et.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //點擊輸入框時,軟鍵盤會直接彈出。如果面板顯示,則進行300ms的延時隱藏,防止跳閃。
                if (llBottomPanel.getVisibility() == View.VISIBLE) {
                    llBottomPanel.postDelayed(mHideEmotionPanelTask, 300);
                }
            }
        });

        keyboardLayout.setKeyboardListener(new KeyboardLayout.KeyboardLayoutListener() {
            @Override
            public void onKeyboardStateChanged(boolean isActive, int keyboardHeight) {
                isShowingInput = isActive;
                if (isActive) { // 輸入法打開
                    Logger.i("", "-----輸入法打開--鍵盤高度:" + keyboardHeight);
                    if (mKeyboardHeight != keyboardHeight) { // 鍵盤發生改變時才設置底部面板的高度,因爲會觸發onGlobalLayoutChanged,導致onKeyboardStateChanged再次被調用
                        mKeyboardHeight = keyboardHeight;
                        updatePanelHeight(); // 每次輸入法彈起時,設置底部面板的高度爲鍵盤的高度,以便下次底部面板彈出時剛好等於鍵盤高度
                    }
                } else {
                    Logger.i("", "-----輸入法關閉--鍵盤高度:" + keyboardHeight);
                }
            }
        });

        //觸摸空白處進行軟鍵盤和麪板的隱藏
        rlContent.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    if (getCurrentFocus() != null && getCurrentFocus().getWindowToken() != null) {
                        hidePanelOrInput();
                    }
                }
                return false;
            }
        });
    }

    // 每當輸入框高度發生變化時,去改變面板的高度
    private void updatePanelHeight() {
        ViewGroup.LayoutParams layoutParams = llBottomPanel.getLayoutParams();
        layoutParams.height = mKeyboardHeight;
        llBottomOtherHeight = mKeyboardHeight;//將面板的高度設置爲軟鍵盤高度
        llBottomPanel.setLayoutParams(layoutParams);
    }

    private Runnable mHideEmotionPanelTask = new Runnable() {
        @Override
        public void run() {
            hidePanel(false, 0);
        }
    };

    @OnClick({R.id.conversation_iv_add, R.id.conversation_iv_switch})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.conversation_iv_add:
                if (isShowingInput) {//如果鍵盤顯示
                    if (llBottomPanel.getVisibility() == View.GONE) {//面板隱藏,則顯示面板
                        showPanel(false, 0);
                    }
                } else {//鍵盤不顯示
                    if (llBottomPanel.getVisibility() == View.VISIBLE) {//如果面板顯示 ,則隱藏面板,顯示鍵盤
                        showInput();
                        llBottomPanel.postDelayed(mHideEmotionPanelTask, 300);
                    } else {
                        showPanel(true, 300);
                    }
                }
                break;
            case R.id.conversation_iv_switch:
                hidePanelOrInput();
                break;
            default:
                break;
        }
    }


    /**
     * 顯示底部面板 (如果鍵盤已經出現,此時展示面板不需用動畫,直接展示即可,如果鍵盤未顯示,則增加一個向上的動畫)
     *
     * @param isAnimal 是否以動畫形式展現
     * @param duration 動畫時長(默認爲300ms)
     */
    private void showPanel(boolean isAnimal, long duration) {
        //設置軟鍵盤爲覆蓋模式
        updateSoftInputMethod(this, WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
        if (isAnimal) {
            ValueAnimator animator = ValueAnimator.ofInt(0, llBottomOtherHeight);
            setAnimaUpdateListener(animator, llBottomPanel);
            animator.setDuration(duration);
            animator.addListener(new AnimatorListenerAdapter() {

                @Override
                public void onAnimationStart(Animator animation) {
                    llBottomPanel.setVisibility(View.VISIBLE);
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                }
            });
            animator.start();
        } else {
            //恢復面板高度
            ViewGroup.LayoutParams layoutParams = llBottomPanel.getLayoutParams();
            layoutParams.height = llBottomOtherHeight;
            llBottomPanel.setLayoutParams(layoutParams);

            llBottomPanel.setVisibility(View.VISIBLE);
            hideInput();
        }
    }


    /**
     * 隱藏底部面板 (如果鍵盤已經出現,此時隱藏面板不需用動畫,直接隱藏即可,如果鍵盤未顯示,則增加一個向下的動畫)
     *
     * @param isAnimal 是否以動畫形式展現
     * @param duration 動畫時長(默認爲300ms)
     */
    private void hidePanel(boolean isAnimal, long duration) {
        //設置軟鍵盤爲擠壓模式
        updateSoftInputMethod(this, WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

        if (isAnimal) {
            ValueAnimator animator = ValueAnimator.ofInt(llBottomOtherHeight, 0);
            setAnimaUpdateListener(animator, llBottomPanel);
            animator.setDuration(duration);
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    llBottomPanel.setVisibility(View.GONE);
                }
            });
            animator.start();
        } else {
            if (llBottomPanel.getVisibility() == View.VISIBLE) {
                llBottomPanel.setVisibility(View.GONE);
            }
        }


    }

    //更改輸入法軟鍵盤彈出方式
    public static void updateSoftInputMethod(Activity activity, int softInputMode) {
        if (!activity.isFinishing()) {
            WindowManager.LayoutParams params = activity.getWindow().getAttributes();
            if (params.softInputMode != softInputMode) {
                params.softInputMode = softInputMode;
                activity.getWindow().setAttributes(params);
            }
        }
    }

    //隱藏軟鍵盤
    protected void hideInput() {
        InputMethodManager imm = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(et.getWindowToken(), 0);
    }

    //顯示軟鍵盤
    protected void showInput() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.showSoftInput(et, InputMethodManager.SHOW_FORCED);
    }


    //屬性動畫是可以直接改變view的寬高的 通過不斷改變view的屬性來產生動畫的效果
    public void setAnimaUpdateListener(ValueAnimator valueAnimator, final View view) {
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator arg0) {
                int value = (int) arg0.getAnimatedValue();
                ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
                layoutParams.height = value;
                view.setLayoutParams(layoutParams);
            }
        });
    }


    //隱藏輸入框或底部面板
    public void hidePanelOrInput() {
        //點擊空白區域 如果軟鍵盤顯示則隱藏
        if (isShowingInput) {//
            hideInput();
        }
        //如果面板顯示,則進行下降動畫的隱藏
        if (llBottomPanel.getVisibility() == View.VISIBLE) {
            hidePanel(true, 300);
        }
    }



    @Override
    public void onBackPressed() {
        if (llBottomPanel.getVisibility() == View.VISIBLE) {
            hidePanel(true, 300);
            return;
        }
        super.onBackPressed();
    }
}

佈局代碼我就不貼了,主要就是Activity代碼。上面我都寫了相應的註釋。這裏面我還增加了底部面板的顯示和隱藏動畫(屬性動畫),這樣不會太生硬。當軟鍵盤隱藏時,顯示面板和隱藏面板都會以動畫形式展現。當軟鍵盤顯示時,則直接顯示和隱藏面板。

還有一個特別需要注意的地方。需要在清單文件中對Activity設置一個默認的windowSoftInputMode,不然跳閃的情況就還會出現。

嗯,差不多到這就結束了。有不懂的地方可以加我 QQ1902366297

 

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