Last active
October 21, 2018 15:35
-
-
Save legendmohe/146679ad2a2a76ca1bec168bbdb7e926 to your computer and use it in GitHub Desktop.
解决因键盘和表情panel显示/隐藏引起的闪动
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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