Android 软键盘丝滑切换(一)

作者: 辰渊 | 来源:发表于2023-01-31 14:56 被阅读0次

    在开发app中,软键盘弹出会有卡顿,闪一下的现象,会影响体验效果。为了实现微信软键盘与表情面板流畅的切换效果,查看了好多例子,查阅的很多资料,换了几种实现方式,都达不到流畅切换的效果,最终结合资料,参考网上的很多例子实现了想要的效果。(由于Demo中代码部分为Java代码,所以文中代码也由Kotlin与Java组成)Demo地址https://github.com/xiaoyu00/KeyboardDemo

    效果图

    image

    实现思路

    在manifest android:windowSoftInputMode属性提供了系统自带的键盘弹出界面变化的几种方式,但都达不到流畅切换效果,更别说更复杂的微信键盘切换了,所以我们不用系统的方式,自己实现。
    首先要实时获取到软键盘弹出时的属性:是否弹出,动画执行过程,高度等;然后在界面加载完成后计算带表情面板布局的高度;最后围绕键盘弹出收回的过程中,在键盘属性变化时对整个布局做平移处理。

    实现

    一、键盘监听

    键盘的变化监听是整个实现的核心,Android手机的键盘所有属性是直接获取不到的。监听键盘变化只能用其它方式,网上有几种方方法,我这选择的是根view Insets监听方式(实现OnApplyWindowInsetsListener接口,设置ViewCompat.setWindowInsetsAnimationCallback)。

    1.自定义RootViewDeferringInsetsCallback继承WindowInsetsAnimationCompat.Callback实现OnApplyWindowInsetsListener接口

    public class RootViewDeferringInsetsCallback extends WindowInsetsAnimationCompat.Callback implements OnApplyWindowInsetsListener {
      private View view;
      private WindowInsetsCompat lastWindowInsets;
      private boolean deferredInsets = false;
      private int persistentInsetTypes;
      private int deferredInsetTypes;
      private boolean isPadding = false;
      ...//构造方法
      @Override
      public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat windowInsets) {
          view = v;
          lastWindowInsets = windowInsets;
          if (isPadding) {
              int types;
              if (deferredInsets) {
                  types = deferredInsetTypes;
              } else {
                  types = persistentInsetTypes | deferredInsetTypes;
              }
    
              Insets typeInsets = windowInsets.getInsets(types);
              v.setPadding(typeInsets.left, typeInsets.top, typeInsets.right, typeInsets.bottom);
          }
    
          return WindowInsetsCompat.CONSUMED;
      }
      
      @Override
      public void onPrepare(@NonNull WindowInsetsAnimationCompat animation) {
          if ((animation.getTypeMask() & deferredInsetTypes) != 0) {
              deferredInsets = true;
          }
      }
      @NonNull
      @Override
      public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
          return insets;
      }
      @Override
      public void onEnd(@NonNull WindowInsetsAnimationCompat animation) {
          if (deferredInsets && (animation.getTypeMask() & deferredInsetTypes) != 0) {
              deferredInsets = false;
              if (lastWindowInsets != null && view != null) {
                  ViewCompat.dispatchApplyWindowInsets(view, lastWindowInsets);
              }
          }
      }
    }
    
    

    2.定义KeyBoardListener接口

    public interface KeyBoardListener {
        void onAnimStart(int moveDistance);
        void onAnimDoing(int offsetX,int offsetY);
        void onAnimEnd();
    }
    

    3.自定义KeyBoardInsetsCallBack继承RootViewDeferringInsetsCallback

    public class KeyBoardInsetsCallBack extends RootViewDeferringInsetsCallback {
        public static final int KEYBOARD_TYPE = WindowInsetsCompat.Type.ime();
        public static final int SYSTEM_BAR_TYPE = WindowInsetsCompat.Type.systemBars();
        private KeyBoardListener keyboardListener;
    
        public KeyBoardInsetsCallBack(int dispatchMode, KeyBoardListener keyboardListener) {
            super(dispatchMode);
            this.keyboardListener = keyboardListener;
        }
    
        public KeyBoardInsetsCallBack(KeyBoardListener keyboardListener) {
            this(DISPATCH_MODE_STOP, keyboardListener);
        }
    
        @NonNull
        @Override
        public WindowInsetsAnimationCompat.BoundsCompat onStart(@NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds) {
            keyboardListener.onAnimStart(bounds.getUpperBound().bottom - bounds.getLowerBound().bottom);//计算键盘弹出高度
            return super.onStart(animation, bounds);
        }
    
        @NonNull
        @Override
        public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations) {
            Insets typesInset = insets.getInsets(KEYBOARD_TYPE);
            Insets otherInset = insets.getInsets(SYSTEM_BAR_TYPE);
    
            Insets subtract = Insets.subtract(typesInset, otherInset);
            Insets diff = Insets.max(subtract, Insets.NONE);
            keyboardListener.onAnimDoing(diff.left - diff.right, diff.top - diff.bottom);
            return insets;
        }
    
        @Override
        public void onEnd(@NonNull WindowInsetsAnimationCompat animation) {
            keyboardListener.onAnimEnd();
        }
    }
    

    4.最后在Activity中界面加载完成后添加键盘监听

    val keyBoardInsetsCallBack =KeyBoardInsetsCallBack(object :
                    KeyBoardListener {
                    override fun onAnimStart(moveDistance: Int) {
                        ...
                    }
    
                    override fun onAnimDoing(offsetX: Int, offsetY: Int) {
                       ...
                    }
    
                    override fun onAnimEnd() {
                       ...
                    }
         })
     ViewCompat.setWindowInsetsAnimationCallback(window.decorView, keyBoardInsetsCallBack)
    

    这样就完成了键盘弹出收回过程的监听。

    二、计算并设置View大小

    在界面加载完成后计算View高度,contentLayout为根Layout

    contentLayout.viewTreeObserver
                .addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
                    override fun onGlobalLayout() {
                        contentLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
                        calculationLayoutSize()
                        initKeyBoardListener()
                    }
                })
    
    private fun calculationLayoutSize() {
            val layoutParams = contentLayout.layoutParams as FrameLayout.LayoutParams
            val layoutParams2 = listLayout.layoutParams as LinearLayout.LayoutParams
            val cHeight: Int = contentLayout.height
            PANEL_HEIGHT=(cHeight*0.45).toInt()//PANEL_HEIGHT为表情面板高度
            layoutParams2.height = cHeight
            listLayout.layoutParams = layoutParams2//listlayout为表情面板上面内容layout
            layoutParams.height = cHeight + PANEL_HEIGHT
            contentLayout.layoutParams = layoutParams
        }
    

    整个布局xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/layout_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <LinearLayout
            android:id="@+id/layout_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
          //滑动列表
            <ScrollView
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"
                android:text="Hello World!">
            ...
            </ScrollView>
         // 输入框
            <LinearLayout
                android:id="@+id/layout_edit"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:paddingVertical="6dp"
                android:paddingHorizontal="16dp">
    
                <EditText
                    android:id="@+id/et_input"
                    android:layout_width="0dp"
                    android:layout_height="36dp"
                    android:layout_weight="1"
                    android:background="@drawable/msg_editor_border" />
    
                <ImageView
                    android:id="@+id/face_btn"
                    android:layout_width="26.88dp"
                    android:layout_height="26.88dp"
                    android:layout_margin="5dp"
                    android:scaleType="fitXY"
                    android:src="@drawable/action_face_selector" />
            </LinearLayout>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/view_panel"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/grey"
            android:gravity="center"
            android:orientation="vertical">
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="18sp"
                android:text="这是表情面板" />
        </LinearLayout>
    
    </LinearLayout>
    

    接下来在键盘弹出收起过程中对根布局做平移处理

    相关文章

      网友评论

        本文标题:Android 软键盘丝滑切换(一)

        本文链接:https://www.haomeiwen.com/subject/brbmhdtx.html