├── app ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── sollian │ │ └── bottomsheetdialog │ │ ├── MainActivity.java │ │ ├── MyApplication.java │ │ ├── fragment │ │ ├── CommonFragment.java │ │ └── PanelFragment.java │ │ └── widget │ │ └── ListenFocusEditText.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ ├── fragment_common.xml │ └── fragment_panel.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── sollian │ │ └── library │ │ ├── BaseBottomSheetDialogFragment.java │ │ ├── KeyboardObserver.java │ │ ├── OnKeyboardChangeListener.java │ │ ├── PanelFrameLayout.java │ │ ├── PanelInputDialogFragment.java │ │ ├── PanelInputDialogFragment2.java │ │ └── Util.java │ └── res │ ├── anim │ ├── slide_down.xml │ └── slide_up.xml │ └── values │ ├── strings.xml │ └── styles.xml └── settings.gradle /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 30 5 | defaultConfig { 6 | applicationId "com.sollian.bottomsheetdialog" 7 | minSdkVersion 14 8 | targetSdkVersion 30 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_7 21 | targetCompatibility JavaVersion.VERSION_1_7 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(include: ['*.jar'], dir: 'libs') 27 | implementation project(':library') 28 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 29 | implementation 'com.android.support:appcompat-v7:27.1.1' 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sollian/bottomsheetdialog/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.sollian.bottomsheetdialog; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.FragmentActivity; 5 | import android.view.View; 6 | 7 | import com.sollian.bottomsheetdialog.fragment.CommonFragment; 8 | import com.sollian.bottomsheetdialog.fragment.PanelFragment; 9 | 10 | public class MainActivity extends FragmentActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | } 17 | 18 | public void openCommon(View view) { 19 | new CommonFragment().show(getSupportFragmentManager(), null); 20 | } 21 | 22 | public void openPanel(View view) { 23 | new PanelFragment().show(getSupportFragmentManager(), null); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sollian/bottomsheetdialog/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.sollian.bottomsheetdialog; 2 | 3 | import android.app.Application; 4 | 5 | import com.sollian.library.Util; 6 | 7 | /** 8 | * @author admin on 2018/3/20. 9 | */ 10 | 11 | public class MyApplication extends Application { 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | Util.init(this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/sollian/bottomsheetdialog/fragment/CommonFragment.java: -------------------------------------------------------------------------------- 1 | package com.sollian.bottomsheetdialog.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.sollian.bottomsheetdialog.R; 11 | import com.sollian.library.BaseBottomSheetDialogFragment; 12 | 13 | /** 14 | * @author sollian on 2018/3/20. 15 | */ 16 | 17 | public class CommonFragment extends BaseBottomSheetDialogFragment { 18 | @Nullable 19 | @Override 20 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 21 | return inflater.inflate(R.layout.fragment_common, container, false); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/sollian/bottomsheetdialog/fragment/PanelFragment.java: -------------------------------------------------------------------------------- 1 | package com.sollian.bottomsheetdialog.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.ViewTreeObserver; 10 | import android.widget.Button; 11 | import android.widget.FrameLayout; 12 | import android.widget.TextView; 13 | import com.sollian.bottomsheetdialog.R; 14 | import com.sollian.bottomsheetdialog.widget.ListenFocusEditText; 15 | import com.sollian.bottomsheetdialog.widget.ListenFocusEditText.OnWindowFocusChangeListener; 16 | import com.sollian.library.PanelFrameLayout; 17 | import com.sollian.library.PanelInputDialogFragment; 18 | import com.sollian.library.Util; 19 | 20 | /** 21 | * @author sollian on 2018/3/20. 22 | */ 23 | 24 | public class PanelFragment extends PanelInputDialogFragment implements View.OnClickListener { 25 | 26 | private static final int PANEL_NONE = 0; 27 | private static final int PANEL_EMOTION = 1; 28 | private static final int PANEL_GIF = 2; 29 | 30 | private int curPanel = PANEL_NONE; 31 | 32 | private Button vOpenPanelEmotion; 33 | private Button vOpenPanelGif; 34 | private ListenFocusEditText vEdit; 35 | 36 | private PanelFrameLayout vPanelHolder; 37 | 38 | /** 39 | * 上次软键盘状态 40 | */ 41 | private int lastState = STATE_KEYBOARD; 42 | 43 | @Nullable 44 | @Override 45 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 46 | @Nullable Bundle savedInstanceState) { 47 | return inflater.inflate(R.layout.fragment_panel, container, false); 48 | } 49 | 50 | @Override 51 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 52 | super.onViewCreated(view, savedInstanceState); 53 | 54 | vEdit = view.findViewById(R.id.edit); 55 | vOpenPanelEmotion = view.findViewById(R.id.open_panel_emotion); 56 | vOpenPanelGif = view.findViewById(R.id.open_panel_gif); 57 | vPanelHolder = view.findViewById(R.id.panel); 58 | 59 | init(view, vPanelHolder, vEdit); 60 | 61 | vOpenPanelEmotion.setOnClickListener(this); 62 | vOpenPanelGif.setOnClickListener(this); 63 | } 64 | 65 | @Override 66 | public void onResume() { 67 | super.onResume(); 68 | if (lastState == STATE_KEYBOARD) { 69 | /* 70 | 这种方式弹出软键盘最为保险,直接post在android 11上有问题 71 | ViewRootImpl#handleWindowFocusChanged在调用View#dispatchWindowFocusChanged后, 72 | 会调用onPostWindowFocus方法为InputMethodManager(android 11为ImeFocusController)设置mServedView 73 | 只有mServedView==vEdit时,才会调起软键盘 74 | */ 75 | vEdit.setWindowFocusChangeListener(new OnWindowFocusChangeListener() { 76 | @Override 77 | public void onWindowFocusChanged(boolean hasFocus) { 78 | if (hasFocus) { 79 | vEdit.setWindowFocusChangeListener(null); 80 | vEdit.post(new Runnable() { 81 | @Override 82 | public void run() { 83 | Util.showKeyboard(getDialog().getWindow().getCurrentFocus()); 84 | } 85 | }); 86 | } 87 | } 88 | }); 89 | //api >= 18 可以直接使用下面的方式 90 | // vEdit.getViewTreeObserver() 91 | // .addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() { 92 | // @Override 93 | // public void onWindowFocusChanged(boolean hasFocus) { 94 | // if (hasFocus) { 95 | // vEdit.getViewTreeObserver().removeOnWindowFocusChangeListener(this); 96 | // vEdit.post(new Runnable() { 97 | // @Override 98 | // public void run() { 99 | // Util.showKeyboard(getDialog().getWindow().getCurrentFocus()); 100 | // } 101 | // }); 102 | // } 103 | // } 104 | // }); 105 | vEdit.requestFocus(); 106 | } 107 | } 108 | 109 | @Override 110 | public void onPause() { 111 | super.onPause(); 112 | lastState = getState(); 113 | } 114 | 115 | @Override 116 | public void onClick(View v) { 117 | switch (v.getId()) { 118 | case R.id.open_panel_emotion: 119 | openPanelEmotion(); 120 | break; 121 | case R.id.open_panel_gif: 122 | openPanelGif(); 123 | break; 124 | default: 125 | break; 126 | } 127 | } 128 | 129 | private void openPanelEmotion() { 130 | if (getState() == STATE_PANEL && curPanel == PANEL_EMOTION) { 131 | changeState(STATE_KEYBOARD); 132 | } else { 133 | curPanel = PANEL_EMOTION; 134 | changeState(STATE_PANEL); 135 | } 136 | } 137 | 138 | private void openPanelGif() { 139 | if (getState() == STATE_PANEL && curPanel == PANEL_GIF) { 140 | changeState(STATE_KEYBOARD); 141 | } else { 142 | curPanel = PANEL_GIF; 143 | changeState(STATE_PANEL); 144 | } 145 | } 146 | 147 | @Override 148 | protected void beforeStateChange(int oldState, int newState) { 149 | if (newState != STATE_PANEL) { 150 | curPanel = PANEL_NONE; 151 | } else { 152 | showPanel(oldState == STATE_KEYBOARD); 153 | } 154 | 155 | vOpenPanelEmotion.setText(curPanel == PANEL_EMOTION ? "打开软键盘" : "打开表情面板"); 156 | vOpenPanelGif.setText(curPanel == PANEL_GIF ? "打开软键盘" : "打开Gif面板"); 157 | } 158 | 159 | private void showPanel(boolean isKeyboardShowing) { 160 | View panel = getActivePanel(); 161 | 162 | if (panel != null) { 163 | int childCount = vPanelHolder.getChildCount(); 164 | for (int i = 0; i < childCount; i++) { 165 | View child = vPanelHolder.getChildAt(i); 166 | if (child != panel) { 167 | /* 168 | 此处不要用GONE 169 | GONE会触发requestLayout,导致在软键盘消失之前展示Panel 170 | */ 171 | child.setVisibility(View.INVISIBLE); 172 | } else { 173 | child.setVisibility(View.VISIBLE); 174 | } 175 | } 176 | 177 | addViewToPanel(panel, isKeyboardShowing); 178 | } 179 | } 180 | 181 | private View getActivePanel() { 182 | View panel = null; 183 | switch (curPanel) { 184 | case PANEL_EMOTION: 185 | panel = genPanel("表情面板"); 186 | break; 187 | case PANEL_GIF: 188 | panel = genPanel("Gif面板"); 189 | break; 190 | default: 191 | break; 192 | } 193 | return panel; 194 | } 195 | 196 | private void addViewToPanel(View view, boolean isKeyboardShowing) { 197 | if (view.getParent() == null) { 198 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( 199 | Util.getScreenWidth(getActivity()), 200 | FrameLayout.LayoutParams.MATCH_PARENT); 201 | if (isKeyboardShowing) { 202 | //不会触发requestLayout,软键盘隐藏时,自动触发layout 203 | vPanelHolder.addViewInLayout(view, 0, lp, true); 204 | } else { 205 | vPanelHolder.addView(view, lp); 206 | } 207 | } 208 | } 209 | 210 | private View genPanel(String desc) { 211 | TextView vText = new TextView(getActivity()); 212 | vText.setText(desc); 213 | vText.setTextSize(20); 214 | return vText; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /app/src/main/java/com/sollian/bottomsheetdialog/widget/ListenFocusEditText.java: -------------------------------------------------------------------------------- 1 | package com.sollian.bottomsheetdialog.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.EditText; 6 | 7 | /** 8 | * @author shouxianli on 2020/10/12. 9 | */ 10 | public class ListenFocusEditText extends EditText { 11 | 12 | private OnWindowFocusChangeListener windowFocusChangeListener; 13 | 14 | public ListenFocusEditText(Context context) { 15 | super(context); 16 | } 17 | 18 | public ListenFocusEditText(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | } 21 | 22 | public ListenFocusEditText(Context context, AttributeSet attrs, int defStyleAttr) { 23 | super(context, attrs, defStyleAttr); 24 | } 25 | 26 | @Override 27 | public void onWindowFocusChanged(boolean hasWindowFocus) { 28 | super.onWindowFocusChanged(hasWindowFocus); 29 | 30 | if (windowFocusChangeListener != null) { 31 | windowFocusChangeListener.onWindowFocusChanged(hasWindowFocus); 32 | } 33 | } 34 | 35 | public void setWindowFocusChangeListener( 36 | OnWindowFocusChangeListener windowFocusChangeListener) { 37 | this.windowFocusChangeListener = windowFocusChangeListener; 38 | } 39 | 40 | public interface OnWindowFocusChangeListener { 41 | 42 | void onWindowFocusChanged(boolean hasFocus); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |