Skip to content

Instantly share code, notes, and snippets.

@legendmohe
Last active October 21, 2018 15:35
Show Gist options
  • Save legendmohe/146679ad2a2a76ca1bec168bbdb7e926 to your computer and use it in GitHub Desktop.
Save legendmohe/146679ad2a2a76ca1bec168bbdb7e926 to your computer and use it in GitHub Desktop.
解决因键盘和表情panel显示/隐藏引起的闪动
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
/**
* 负责处理显示/隐藏切换逻辑 和 键盘高度变化监听
* 用于对话框,评论框等需要键盘和自定义panel协作的地方
* <p>
* Created by legendmohe on 2017/11/22.
*/
public class KeyboardPanelSwitcher extends LinearLayout {
private static final String TAG = "KeyboardPanelSwitcher";
private static final int SUPPOSE_KEYBOARD_HEIGHT_DP = 140;
private final float mSupposeKBHeight;
private int mOldBottom = -1; // 为了首次不调用keyboard hide
private int mKeyboardHeight = -1; // 获得键盘真正的高度
private InnerListenerWrapper mListener;
private boolean mIsKeyboardShown;
private boolean mPendingShowPanel;
private boolean mPendingHidePanel;
private boolean mPendingHideKeyboard;
/*
记录是否正在切换,注意onKeyboardHide是最后调用的,所以在onKeyboardHide时设置为false
*/
private boolean mPendingSwitchingToPanel;
/*
记录是否正在切换,注意onPanelHide是最后调用的,所以在onPanelHide时设置为false
*/
private boolean mPendingSwitchingToKeyboard;
private boolean mPendingShowKeyboard;
private View mFocusableView;
private View mPanelView;
private boolean mSwitchEnabled = false;
public KeyboardPanelSwitcher(Context context) {
this(context, null);
}
public KeyboardPanelSwitcher(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KeyboardPanelSwitcher(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mSupposeKBHeight = dpToPx(context, SUPPOSE_KEYBOARD_HEIGHT_DP);
setOrientation(LinearLayout.VERTICAL);
Log.d(TAG, "mSupposeKBHeight = [" + mSupposeKBHeight + "]");
}
private float dpToPx(Context context, int dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
}
/**
* 这里通过高度变化捕获键盘显示/隐藏/高度调整事件。
* 注意标记变量的使用。
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (!changed) {
return;
}
handleLayoutBottomChanged(b);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (mListener != null) {
mListener.onLayoutHeightChanged(this, oldh, h);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mListener != null) {
mListener.onPreMeasure(this, widthMeasureSpec, heightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mListener != null) {
mListener.onPostMeasure(this);
}
}
//////////////////////////////////////////////////////////////////////
public void showPanel() {
if (!mSwitchEnabled)
throw new IllegalStateException("panel switch is disable");
if (!isPanelShown()) {
mPendingShowPanel = true;
}
mPanelView.setVisibility(View.VISIBLE);
if (mListener != null) {
mListener.onPanelShow(this);
}
}
public void switchToPanel() {
if (!isPanelShown()) {
mPendingShowPanel = true;
mPendingSwitchingToPanel = true;
}
hideKeyboard();
}
public void switchOrShowPanel() {
if (isKeyboardShown()) {
switchToPanel();
} else {
showPanel();
}
}
public void hidePanel() {
if (!mSwitchEnabled)
throw new IllegalStateException("panel switch is disable");
if (isPanelShown()) {
mPendingHidePanel = true;
}
mPanelView.setVisibility(View.GONE);
}
public void switchToKeyboard() {
if (isPanelShown()) {
mPendingHidePanel = true;
mPendingSwitchingToKeyboard = true;
}
showKeyboard();
}
public void showKeyboard() {
if (!isKeyboardShown()) {
mPendingShowKeyboard = true;
}
if (mFocusableView != null) {
mFocusableView.requestFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mFocusableView, InputMethodManager.SHOW_IMPLICIT);
}
}
public void switchOrShowKeyboard() {
if (isPanelShown()) {
switchToKeyboard();
} else {
showKeyboard();
}
}
public void hideKeyboard() {
if (isKeyboardShown()) {
mPendingHideKeyboard = true;
}
if (mFocusableView != null) {
mFocusableView.clearFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mFocusableView.getWindowToken(), 0);
}
}
public void hideAll() {
if (isKeyboardShown()) {
hideKeyboard();
}
if (isPanelShown()) {
hidePanel();
}
}
//////////////////////////////////////////////////////////////////////
/**
* 使用wrapper的原因是分离 显示/隐藏切换逻辑 和 键盘高度变化监听逻辑
*/
private class InnerListenerWrapper implements Listener {
private Listener mListener;
private boolean mPanelShown;
public InnerListenerWrapper(Listener listener) {
mListener = listener;
}
@Override
public void onKeyboardShow(View view) {
if (mListener != null) {
mListener.onKeyboardShow(view);
}
}
@Override
public void onKeyboardHide(View view) {
if (mListener != null) {
mListener.onKeyboardHide(view);
}
}
@Override
public void onPanelShow(View view) {
if (mListener != null) {
mListener.onPanelShow(view);
}
}
@Override
public void onPanelHide(View view) {
if (mListener != null) {
mListener.onPanelHide(view);
}
}
@Override
public void onKeyboardHeightChanged(View view, int oldH, int newH) {
if (mSwitchEnabled) {
modifyPanelHeight(newH);
}
if (mListener != null) {
mListener.onKeyboardHeightChanged(view, oldH, newH);
}
}
@Override
public void onLayoutHeightChanged(View view, int oldH, int newH) {
if (mListener != null) {
mListener.onLayoutHeightChanged(view, oldH, newH);
}
}
/**
* 避免闪屏的核心代码
*
* @param view
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
public void onPreMeasure(View view, int widthMeasureSpec, int heightMeasureSpec) {
// 在首次onMeasure前,改变panel view的状态
// (默认情况下,就算同时设置了panel visible/invisible,键盘收起/隐藏首次引起的onMeasure中,
// panel的状态还没改变)
if (mSwitchEnabled) {
if (mPendingShowPanel) {
showPanelInternal();
} else if (mPendingHidePanel || mPendingSwitchingToKeyboard) {
hidePanelInternal();
}
}
if (mListener != null) {
mListener.onPreMeasure(view, widthMeasureSpec, heightMeasureSpec);
}
}
private void showPanelInternal() {
mPanelView.setVisibility(View.VISIBLE);
boolean oldPanelState = mPanelShown;
mPanelShown = true;
if (!oldPanelState) {
if (mListener != null) {
mListener.onPanelShow(KeyboardPanelSwitcher.this);
}
mPendingShowPanel = false;
}
}
private void hidePanelInternal() {
mPanelView.setVisibility(View.GONE);
boolean oldPanelState = mPanelShown;
mPanelShown = false;
if (oldPanelState) {
if (mListener != null) {
mListener.onPanelHide(KeyboardPanelSwitcher.this);
}
mPendingHidePanel = false;
mPendingSwitchingToKeyboard = false;
}
}
@Override
public void onPostMeasure(View view) {
if (mListener != null) {
mListener.onPostMeasure(view);
}
}
private void modifyPanelHeight(int height) {
ViewGroup.LayoutParams layoutParams = mPanelView.getLayoutParams();
layoutParams.height = height;
mPanelView.setLayoutParams(layoutParams);
}
public boolean isPanelShown() {
return mPanelShown;
}
}
/**
* 检测键盘事件
*
* @param newBottom
*/
public void handleLayoutBottomChanged(int newBottom) {
int curBottom = newBottom;
int delta = mOldBottom - curBottom;
if (mOldBottom != -1 && delta < 0 && Math.abs(delta) > mSupposeKBHeight) { // keyboard hide
Log.d(TAG, "keyboard hide");
if (mListener != null) {
mListener.onKeyboardHide(this);
}
mIsKeyboardShown = false;
mPendingHideKeyboard = false;
// 切换到panel的时候
mPendingSwitchingToPanel = false;
} else if (delta > 0 && Math.abs(delta) > mSupposeKBHeight) { // keyboard shown
Log.d(TAG, "keyboard show");
if (mListener != null) {
mListener.onKeyboardShow(this);
}
int oldHeight = mKeyboardHeight;
mKeyboardHeight = Math.abs(delta);
if (mListener != null && oldHeight != mKeyboardHeight) {
mListener.onKeyboardHeightChanged(this, oldHeight, mKeyboardHeight);
}
mIsKeyboardShown = true;
mPendingShowKeyboard = false;
} else if (Math.abs(delta) > 0 && Math.abs(delta) < mSupposeKBHeight) { // user change keyboard height
int oldHeight = mKeyboardHeight;
mKeyboardHeight = mKeyboardHeight + delta;
if (mListener != null && oldHeight != mKeyboardHeight) {
mListener.onKeyboardHeightChanged(this, oldHeight, mKeyboardHeight);
}
}
mOldBottom = curBottom;
}
//////////////////////////////////////////////////////////////////////
public boolean isSwitchEnabled() {
return mSwitchEnabled;
}
public void setSwitchEnabled(boolean switchEnabled) {
mSwitchEnabled = switchEnabled;
}
public View getPanelView() {
return mPanelView;
}
public void setPanelView(View panelView) {
mSwitchEnabled = panelView != null;
mPanelView = panelView;
}
public boolean isPendingShowPanel() {
return mPendingShowPanel;
}
public boolean isPendingShowKeyboard() {
return mPendingShowKeyboard;
}
public View getFocusTarget() {
return mFocusableView;
}
public void setFocusTarget(View focusableView) {
mFocusableView = focusableView;
if (mFocusableView != null) {
mFocusableView.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
// 如果panel显示,且mFocusableView失去焦点,则认为是切换到keyboard
// 注:为什么要手动触发switchToKeyboard(),因为后面的处理(onMeasure)要靠某些flag来处理
// panel和keyboard的切换逻辑。单纯靠keyboard出现引起的height change 是不足够的
if (!isPendingSwitchingToPanel() && isPanelShown() && hasFocus) {
switchToKeyboard();
}
}
});
}
}
public void setListener(Listener listener) {
mListener = new InnerListenerWrapper(listener);
}
public boolean isKeyboardShown() {
return mIsKeyboardShown;
}
public boolean isPanelShown() {
return mListener != null && mListener.isPanelShown();
}
public boolean isPendingSwitchingToPanel() {
return mPendingSwitchingToPanel;
}
public boolean isPendingSwitchingToKeyboard() {
return mPendingSwitchingToKeyboard;
}
public int getKeyboardHeight() {
return mKeyboardHeight;
}
//////////////////////////////////////////////////////////////////////
public interface Listener {
void onKeyboardShow(View view);
void onKeyboardHide(View view);
void onPanelShow(View view);
void onPanelHide(View view);
void onKeyboardHeightChanged(View view, int oldH, int newH);
void onLayoutHeightChanged(View view, int oldH, int newH);
void onPreMeasure(View view, int widthMeasureSpec, int heightMeasureSpec);
void onPostMeasure(View view);
}
public static abstract class SimpleListener implements Listener {
public void onKeyboardShow(View view) {
}
public void onKeyboardHide(View view) {
}
public void onPanelShow(View view) {
}
public void onPanelHide(View view) {
}
public void onKeyboardHeightChanged(View view, int oldH, int newH) {
}
public void onLayoutHeightChanged(View view, int oldH, int newH) {
}
public void onPreMeasure(View view, int widthMeasureSpec, int heightMeasureSpec) {
}
public void onPostMeasure(View view) {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment