Androidの開発-どのように優雅にレイアウトを入力法の上に置くか

32208 ワード

0.はじめに
Androidアプリケーションの開発では、いくつかの製品のニーズがあり、入力法の高さを得る必要があります.残念なことに、Android公式ではこのようなAPIは提供されていません.
最近の生放送プロジェクトでは、淘宝の生放送ページを見て、ユーザーが下のレイアウトをクリックすると、入力法をポップアップすると同時に、新しいEditTextを入力法の真上に置く必要があります.これは、入力法の高さを正確に取得するとともに、仮想ボタンバーの高さを両立させる必要があります.
また、入力方式が現れると、後のインタフェースのレイアウトには何の影響も受けず、android:windowSoftInputMode=「adjustNothing」の効果であることが明らかになった.WindowSoftInputModeの様々な属性の意味については、公式ドキュメントを参照してください.
そのため、上記のように、私たちのニーズは次のとおりです.
  • ActivityがadjustNothingに設定ときに入力方式の正確な高さ
  • を取得する.
  • 私たちはもう一つの要求を追加して、全面的な携帯電話と互換性があります.下部の仮想操作欄が存在するかどうかにかかわらず、EditTextを入力方式の真上
  • に正確に置くべきである.
    1.一般的なソリューション
    Android公式はAPIを提供していないので、入力法の高さを正確に取得することはできません.だから、私たちは自分で方法を考えて実現するしかありません.
    ネット上の案は一つ一つ探しているが、効果は理想的ではなく、10のうち9つは傍受レイアウトの変化、すなわちViewTreeObserverを傍受している.OnGlobalLayoutListenerというインタフェースは、入力方式をポップアップした後、画面の総高さから現在のページウィンドウの表示範囲を減算して入力方式の高さを得る.通常コードは以下の通りである.
    @Override
    public void onGlobalLayout() {
    ​    Rect rect = new Rect();//              ((Activity) getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);int screenHeight = getScreenHeight();int keyboardHeight = screenHeight - rect.bottom; //       if (Math.abs(keyboardHeight) > screenHeight / 5) {//                  }
    }
    

    この案の劣勢は明らかだ.
  • まず「スクリーン1/5を超える」という閾値の設定は主観的であり、閾値を200画素に設定する案もあり、いずれも同じである.
  • さらに、onGlobalLayoutがポップアップ入力法のみでトリガーされ、ウィンドウの表示範囲が縮小されるかどうかは、ここで疑問符を打たなければならない.他の状況があって誤審を招くかどうかは分からない.
  • で最も重要なのは、このスキームがandroid:windowSoftInputMode=「adjustResize」に構成されている場合に問題なく、レイアウトがこの属性の下で確実に動的に調整されるため、入力法の高さを正しく取得する必要があることです.しかし、この調整は私たちが望んでいるものではありません.私たちの生中継シーンでは、入力法がレイアウトの調整に影響を与えることを望んでいません.ただし、Activityがandroid:windowSoftInputMode="adjustNothing"に設定されている場合、レイアウトは入力メソッドがポップアップされたときに調整されず、上記のスキームは自然に失効します.

  • 2.「透明Window」ベースのソリューションの概要
    シナリオ原理の概要
    このスキームの核心原理は,幅0,高さMATCH_を新規作成することである.PARENTは、adjustResize属性をサポートする透明なPopupWindowである、現在のActivityのcontentの上に覆い、getViewTreeObserver()を通過する.addOnGlobalLayoutListener方式で、PopupWindowにレイアウトの変化を感知させ、入力法がポップアップされると、私たちのPopupWindowはresizeに画面の高さより小さいサイズになり、画面の高さからこのサイズを減算することで入力法の高さが得られます.このシナリオは、Activityコンテナには関係ないため、android:windowSoftInputMode="adjustNothing"に設定した場合でも有効です.
    3.「透明Window」方式に基づく具体的な実現
    3.1インタフェースの設計
    まずインタフェースを定義し、onKeyboardHeightChanged()は入力法の高さが変化したときに呼び出され、パラメータheight<=0の場合、入力法が格納されることを意味します.height>0は、入力法が開かれていることを意味し、height値は入力法の高さである.パラメータorientationは画面方向、スタンバイです.
    我々の応用例では,下部の仮想操作欄が存在するかどうかにかかわらず,EditTextを入力法の真上に正確に置くべきであるため,同様に仮想操作欄の高さに関心を持たなければならない.そこで別の方法onVirtualBottomHeight()を設計した.パラメータheightは仮想オペレータバーの高さであり、0はフルスクリーン、すなわち仮想オペレータバーが存在しない場合を示す.
    /**
     * Created by Calvin on 2020/6/1.
     */
    public interface KeyboardHeightObserver {
        
        void onKeyboardHeightChanged(int height, int orientation);
        
        void onVirtualBottomHeight(int height);
    }
    

    3.2 PopupWindowの実現
  • KeyboardHeightProvider()構築方法では、adjustResizeなどの重要なパラメータのようなPopupWindowの様々なパラメータを構成する.
  • PopupWindowのshowAtLocation()タイミングはstart()メソッドで実現されるが、ここで呼び出すタイミングは重要であり、ActivityのonResumeの後に呼び出さなければならないため、Activityでは手動でpostする必要がある.
  • は、ViewツリーのOnGlobalLayoutListenerをリスニングし、画面の高さからPopupWindowの可視高さを減算し、diffの高さdiffを得る.
  • 1.diffが負の場合、この値の逆の数はちょうど下部の仮想操作バーの高さに等しく、onVirtualBottomHeight()メソッドにコールバックするには
  • を使用します.
  • 2.diffが0の場合は、入力方式が格納状態であることを示し、onKeyboardHeightChanged()にコールバックする方法は
  • を用いる.
  • 3.diffが正数の場合、入力方式がオンであることを示し、onKeyboardHeightChanged()にコールバックする方法は
  • を用いる.
  • PopupWindowの可視高さを計算するとき、getWindowVisibleDisplayFrame()という方法を使いました.この方法は複雑で、使用するときに多くの注意点があり、完全に別の文章を書くことができます.ここでは4点を簡単に紹介します.
  • 1.これは、現在のウィンドウの任意のViewを使用してgetWindowVisibleDisplayFrame()を実行して返される結果が同じで、現在のウィンドウの表示領域のサイズを取得するために使用されます.ビューが表示されているかどうかは、返される結果に影響しません.
  • 2.実際のウィンドウの表示領域サイズは、ViewオブジェクトがすでにWindowにattachされている場合にのみ呼び出されます.ActivityのonAttachedToWindow()メソッドとカスタムViewのonAttachedToWindow()で実行するのは、現在のWindowがattachによってWindowManagerに割り当てられているためではありませんが、WindowのViewにはattachがWindowに割り当てられていません.後者の実際の試験結果は不安定である.推奨される呼び出しタイミングは、onWindowFocusChangedおよびonGlobalLayoutで使用できます.
  • 3.ウィンドウがフルスクリーンの場合、outRectのtop値は常に0です.ウィンドウのLayoutParamsのheightがMATCH_に設定されている場合PARENT,outRectのtop値はシステムステータスバーの高さに等しい.ウィンドウのLayoutParamsのheightがWRAP_に設定されている場合CONTENTまたは特定の値で、ウィンドウとステータスバーがオーバーラップし、outRectのtop値はオーバーラップ領域の高さに等しい.入力方式/仮想キーバーとoutRectにも同様に適用できる.bottomの関係.例えば、MATCH_の高さを取得します.PARENTのウィンドウの入力/仮想ボタンバー表示と非表示の2つの状態でのbottom差は、入力/仮想ボタンバーの高さです.
  • 4.getWindowVisibleDisplayFrame()メソッドは、IPC方式でWindowManagerからこの情報を取得するため、相対的にオーバーヘッドが大きいため、パフォーマンスに要求される場所で呼び出すには適していません.

  • /**
     * Created by Calvin on 2020/6/1.
     */
    public class KeyboardHeightProvider extends PopupWindow {
    
        private KeyboardHeightObserver observer;
    
        private int keyboardLandscapeHeight;
    
        private int keyboardPortraitHeight;
    
        private View popupView;
    
        private View parentView;
    
        private Activity activity;
    
        public KeyboardHeightProvider(Activity activity) {
            super(activity);
            this.activity = activity;
    
            LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
            this.popupView = inflater.inflate(R.layout.keyboard_popup_window, null, false);
            setContentView(popupView);
    
            setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE | LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
            setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
    
            parentView = activity.findViewById(android.R.id.content);
    
            setWidth(0);
            setHeight(LayoutParams.MATCH_PARENT);
    
            popupView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
                if (popupView != null) {
                    handleOnGlobalLayout();
                }
            });
        }
        
        public void start() {
            if (!isShowing() && parentView.getWindowToken() != null) {
                setBackgroundDrawable(new ColorDrawable(0));
                showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
            }
        }
        
        public void close() {
            this.observer = null;
            dismiss();
        }
        
        public void setKeyboardHeightObserver(KeyboardHeightObserver observer) {
            this.observer = observer;
        }
        
        private int getScreenOrientation() {
            return activity.getResources().getConfiguration().orientation;
        }
        
        private void handleOnGlobalLayout() {
            Point screenSize = new Point();
            activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
    
            Rect rect = new Rect();
            popupView.getWindowVisibleDisplayFrame(rect);
    
            int orientation = getScreenOrientation();
            int keyboardHeight = screenSize.y - rect.bottom;
    
            if (keyboardHeight < 0 && observer != null) {
                observer.onVirtualBottomHeight(-keyboardHeight);
            }
    
            if (keyboardHeight == 0) {
                notifyKeyboardHeightChanged(0, orientation);
            } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
                this.keyboardPortraitHeight = keyboardHeight;
                notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
            } else {
                this.keyboardLandscapeHeight = keyboardHeight;
                notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
            }
        }
    
        private void notifyKeyboardHeightChanged(int height, int orientation) {
            if (observer != null) {
                observer.onKeyboardHeightChanged(height, orientation);
            }
        }
    }
    

    3.3 Activityでの使用
    Activityでメンバー変数を定義し、onCreate()で初期化します.
    private KeyboardHeightProvider mProvider;
    //        ,    0
    private int mVirtualBottomHeight;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        mProvider = new KeyboardHeightProvider(getActivity());
        new Handler().post(() -> mProvider.start());
    }
    

    メモリの漏洩を防ぐために、ライフサイクルに関する処理を行います.
    @Override
    protected void onResume() {
        super.onResume();
        mProvider.setKeyboardHeightObserver(this);
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        mProvider.setKeyboardHeightObserver(null);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mProvider.close();
    }
    

    最後にコールバックメソッドで、データの使用を完了します.
    @Override
    public void onKeyboardHeightChanged(int height, int orientation) {
        if (height > 0) {
            //     
            //      EditText xml       ,      ,    MarginBottom
            //   MarginBottom             mVirtualBottomHeight
            mInputLayout.setVisibility(View.VISIBLE);
            ViewGroup.MarginLayoutParams params =
                    (ViewGroup.MarginLayoutParams) mInputLayout.getLayoutParams();
            params.setMargins(0, 0, 0, height + mVirtualBottomHeight);
            mInputLayout.requestLayout();
        } else {
            //     ,        EditText  
            mInputLayout.setVisibility(View.GONE);
        }
    }
    
    @Override
    public void onVirtualBottomHeight(int height) {
        //         
        mVirtualBottomHeight = height;
    }
    

    4.後続の彩卵
  • 小米2 Sモデルでは奇抜な互換性の問題が発生し、PopupWindowではonGlobalLayoutのコールバックをトリガーできなかったが、システムバグと判断し、携帯電話システムを更新して解決した.
  • ActivityのadjustNothing属性をadjustResizeに変更すると機能は依然として正常で、効果はadjustNothingと同じで、これは少し奇抜で、私たちが当初全力を尽くしてadjustNothingを使ったのは生放送ページResizeをさせないためで、後続の研究の結果、私たちの工事の中で、現在の生放送ページのActivityのために全画面モードを設置して、全画面モードの下でadjustResizeは失効することが分かった.効果はadjustNothingと同じです(ただし、テストポップアップ入力方式後もRect.bottomウィンドウサイズが変わり、adjustNothingモードでは変更できません).フルスクリーンコードを削除すると、adjustResizeはインタフェースを混乱させます.ここでは歪みが正しかったとしか言いようがありませんが、もちろんフルスクリーンモード+adjustNothingでも問題はないに違いありません.
  • の前の項目には、フルスクリーンモードで入力ボックスが上に上がる必要がある場合、adjustResize入力ボックスを設定しても入力によってブロックされます.本明細書のビジネスシーンでは必要ありません.このニーズがあれば、Androidフルスクリーン状態でポップアップ入力法adjustResizeが無効な修復案Androidフルスクリーンの下で、各キーボードブロック入力ボックスの解決方法フルスクリーンActivityのキーボードブロック入力ボックス
  • を解決する3つの参考記事のソリューションを参照してください.