Androidでレイアウトパラメータを動的に初期化し、ConstraintLayoutの使用中に遭遇したピット

14196 ワード

Androidのダイナミック初期化レイアウトとConstraintLayoutが遭遇した穴
ConstraaintLayoutはAndroidの強力なレイアウトであり、コントロール間の相対的な位置付けによってlayoutのすべてのviewのレイアウトを完成させるが、レイアウト方法はRelativeLayoutよりも柔軟である.レイアウトのネストを大幅に削減し、パフォーマンスを向上させることができます.
今回の問題はActivityでFragmentを動的にレイアウトしてアニメーション化することで、難点はFragmentのサイズがwrap_contentの(携帯電話の画面サイズに適したコストを減らすため).このFragmenはActivityで最初は画面全体の下に隠れていて、必要に応じてアニメーション形式でスライドして現れ、しかも全部で3つのこのようなFragmentがあり、状態によってどちらを見せるかを選択します.楽曲リストのような機能は、通常は非表示で、ボタンをクリックした後にしかスライドしません.
必要に応じて、FragmentのコンテナLayoutのbottomMargin値を変更することで、初期のレイアウトとアニメーション効果を実現することを選択します.具体的な考え方は以下の通りです.
  • Fragmentコンテナのレイアウト
  • <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/fl_frag_container"
                android:clickable="false"
                android:longClickable="false"
                app:layout_constraintBottom_toBottomOf="parent"
                >
                <FrameLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="bottom"
                    android:layout_marginBottom="0dp"
                    android:id="@+id/fl_welcome_fragment_container">FrameLayout>
                <FrameLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="bottom"
                    android:layout_marginBottom="0dp"
                    android:id="@+id/fl_reg_fragment_container">FrameLayout>
                <FrameLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="bottom"
                    android:layout_marginBottom="0dp"
                    android:id="@+id/fl_main_fragment_container">FrameLayout>
    FrameLayout>

    この部分はActivityレイアウトとFragmentに関する部分です.HeightはFragmentの高さによって確定され、Fragmentはwrap_contentの.そのため、Fragmentの高さに基づいて3つのコンテナlayoutのbottomMargin値を設定し、bottomMargin=-heightが画面の下に隠されていることを確認するには、システムがmeasureおよびlayoutプロセスを完了した後にする必要があります.
  • ActivityにFragment
  • をロード
        // onCreate   
        private void loadFragments()
        {
    
            binding.flMainFragmentContainer.setVisibility(View.INVISIBLE);
            binding.flRegFragmentContainer.setVisibility(View.INVISIBLE);
            binding.flWelcomeFragmentContainer.setVisibility(View.INVISIBLE);
    
            mMainFragment = MainFragment.newInstance(mIsLoggedIn, null);
            mRegistFragment = RegistrationFragment.newInstance(null, null);
            mWelcomeFragemtn = WelcomeFragment.newInstance(null, null);
    
            FragmentManager manager = getSupportFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            transaction.add(R.id.fl_main_fragment_container, mMainFragment);
            transaction.add(R.id.fl_reg_fragment_container, mRegistFragment);
            transaction.add(R.id.fl_welcome_fragment_container, mWelcomeFragemtn);
    
            transaction.commit();
    
    
        }

    FragmentTransactionを使用してFragmentをロードします.ロードする前に、その3つのコンテナlayoutを非表示に設定することに注意してください.これは、Fragmentをロードした後、Activityのライフサイクルに従ってFragmentを指定したlayoutに配置し、layoutのパラメータを設定するためです.続いて計測、レイアウトなどです.私たちは最初は高さ値を知らなかったのでbottomMarginは0でしたが、可視に設定するとActivity可視になると、私たちの3つのFragmentも可視になります.
    なぜここでFragmentの高さ値を取得して容器のbottomMarginを設置しないのかと聞かれるかもしれません.このときActivityはまだmeasureおよびlayoutを行っていないため、サイズ情報がなく、getMeasuredHeight()とgetHeight()の戻り値はいずれも0である.そのため、ActivityのView Treeで少なくともmeasureとlayoutを行った後、寸法情報を得る必要があります.このタイミングはいつですか?ViewTreeがlayoutを完了するタイミングを監視するのに役立つリスナーを知る必要があります.
  • ビューツリーレイアウトの傍受が完了し、Fragmentパラメータ
  • が設定される.
            // onCreate   
            getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    Observable.timer(1000, TimeUnit.MILLISECONDS, Schedulers.io())
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe(new Consumer() {
                                @Override
                                public void accept(Long aLong) throws Exception {
    
                                    if(!mIsLoggedIn && !shouldShowLoginFragment)
                                    {
                                        loadWelcomeFragment();
                                    }else if(!mIsLoggedIn && shouldShowLoginFragment)
                                    {
                                        loadRegistrationFragment();
                                    }else{
                                        loadMainFragment();
                                    }
                                }
                            });
                }
            });

    リスナーを追加するには、ActivityのルートViewであるDecorViewを使用します.リスナーがトリガーされた後に削除されます.リスナーは1回だけ必要だからです.そして状態によってどのFragmentを表示するかを決めます.Fragmentを表示する関数の例として、アニメーション部分と初期化が含まれています.
        private void loadWelcomeFragment() {
            showWelcomeFragment();
            dismissRegFragment();
            dismissMainFragment();
    
        }
    
        private void showWelcomeFragment()
        {
            log.e("show welcome fragment called");
    
            if(binding.flWelcomeFragmentContainer.getVisibility() != View.VISIBLE)
            {
                initWelcomeFragment();
            }
            if(welFragAnimator == null)
            {
                initWelAnimator();
            }
    
            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)binding.flWelcomeFragmentContainer.getLayoutParams();
            if(params.bottomMargin == 0)
            {
                return;
            }
    
            welFragAnimator.start();
        }
    
        private void dismissWelcomeFragment()
        {
            log.e("dismiss welcome fragment called");
            if(welFragAnimator == null)
            {
                initWelAnimator();
            }
            ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)binding.flWelcomeFragmentContainer.getLayoutParams();
            if(params.bottomMargin == -binding.flWelcomeFragmentContainer.getHeight())
            {
                return;
            }
            welFragAnimator.reverse();
        }

    明らかに、Fragmentに対応するlayoutが見えないと判断した場合、このFragmentの位置がまだ初期化されていないことを説明し、長い間初期化作業をしています.Fragmentの初期化と対応するアニメーションは以下の通りです.
        private void initWelcomeFragment()
        {
            if(binding.flWelcomeFragmentContainer.getVisibility() != View.VISIBLE)
            {
    
                int height = mWelcomeFragemtn.getView().getHeight();
                log.d("welcome frag container height = " + height);
    
                ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) binding.flWelcomeFragmentContainer.getLayoutParams();
                params.bottomMargin = -height;
                binding.flWelcomeFragmentContainer.setLayoutParams(params);
                binding.flWelcomeFragmentContainer.setVisibility(View.VISIBLE);
    
            }
        }
    
        private void initWelAnimator()
        {
            if(welFragAnimator == null)
            {
                welFragAnimator = ValueAnimator.ofInt(binding.flWelcomeFragmentContainer.getHeight(), 0);
                welFragAnimator.addUpdateListener(welFragAnimatorListener);
                welFragAnimator.setDuration(ANIMATION_DURATION);
            }
        }

    これで、この場所でやっと安心して正しい高さ値を手に入れることができます.そしてこの高さ値でFragmentのレイアウトとアニメーションパラメータを設定しました.
    完璧!
    完璧???
    タイトルはどうする?
    実は完璧ではありません.実際に実行している間に、Fragmentのスライドアップしたアニメーションがキラキラしていて、上下に震えていたからです.他の2つは正常です...アニメーションパラメータの設定に問題があり、変数名にスペルミスがないことを調べたところ、どうしても問題点が見つからなかった.
    それから仕方がなくて、最も愚かで、デバッグに行きます!
    その結果、ジッタのFragmentはTreeObserverが呼び出されたときとActivityがこの2つの段階を完全に表示し、高くなることが分かった!!!これにより、最初のアニメーションパラメータが間違っています.
    何が起こっているのか悩んだが、それが変わったのは、他の2つの最外層layoutがLinearlayoutとRelativeLayoutで、ConstraintLayoutを使っているだけで、このConstraaintLayoutはwrap_contentの、いったんそれを固定高さに変更すると、問題は消えてしまいます...
    ConstraaintLayoutのmeasureとlayoutの流れは他の人とは違うのだろうか.
    しばらく急いでいたが、ソースコードを調べて詳しく追及する暇がなかった.その後、私は他のレイアウトに変更しました.問題はありません.wrap_でも.contentの.
    一応覚えておいて、一つの考えとします.