Android中动态初始化布局参数以及ConstraintLayout使用中遇到的坑

Posted zuguorui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android中动态初始化布局参数以及ConstraintLayout使用中遇到的坑相关的知识,希望对你有一定的参考价值。

android中动态初始化布局以及ConstraintLayout遇到的一个坑

ConstraintLayout是Android中的一个很强大的布局,它通过控件之间的相对定位,来完成一个layout中的所有view的布局,但布局方法相对于RelativeLayout更为灵活。能够大幅减少布局嵌套,提升性能。

这次遇到的问题是在Activity中动态对Fragment进行布局和动画效果,难点在于Fragment的尺寸是wrap_content的(为了减少在手机屏幕尺寸上的适配成本)。而这个Fragmen在Activity中一开始是隐藏在整个屏幕的下方,在需要的时候才以动画的形式滑上来展现出来,而且一共有三个这样的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的。因此我们需要在系统完成一次measure以及layout流程之后才能根据Fragment的高去设置这三个容器layout的bottomMargin值,使bottomMargin = -height,以确保它们是隐藏在屏幕下方的。

  • 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。需要注意的是加载之前我们将那三个容器layout都设置为不可见的。这是因为在加载完Fragment之后,FragmentManager会随着Activity的生命周期将Fragment放在我们指定的layout中并设置layout的参数。接着是测量、布局等。而我们一开始由于不知道高度值,bottomMargin是为0的,如果设置为可见,那么在Activity可见时,我们的三个Fragment也会成为可见的。

也许有人问为何不在这里获取Fragment的高度值然后设置容器的bottomMargin呢?因为此时Activity还没有进行measure以及layout,因此没有尺寸信息,getMeasuredHeight()和getHeight()的返回值都是0。因此我们需要在Activity的ViewTree至少进行了一遍measure和layout之后才能拿到尺寸信息。
那这个时机是什么时候呢?我们需要知道一个很有用的监听器,它就是用来监听何时ViewTree完成layout的。

  • 监听ViewTree布局完成并设置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<Long>() 
                            @Override
                            public void accept(Long aLong) throws Exception 

                                if(!mIsLoggedIn && !shouldShowLoginFragment)
                                
                                    loadWelcomeFragment();
                                else if(!mIsLoggedIn && shouldShowLoginFragment)
                                
                                    loadRegistrationFragment();
                                else
                                    loadMainFragment();
                                
                            
                        );
            
        );

我们通过DecorView(它是一个Activity的根View)来添加监听器。在监听器被触发之后就移除它,因为我们只需要一次监听。然后根据状态来决定显示哪一个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的滑上滑下的动画是一闪一闪的,上下抖动。其他两个正常。。。。
在排查了动画参数设置问题、变量名字没有拼写错误之后,怎么都找不到问题所在。

后来没办法,最笨的,上调试!

结果发现,抖动的那个Fragment在TreeObserver被调用时以及Activity完全显示出来这两个阶段,它会变高!!!!这就导致一开始的动画参数就是错的。

很纳闷这是怎么回事,后来终于发现它与众不同的地方就在于,其他两个最外层layout是LinearLayout和RelativeLayout,只有它用ConstraintLayout,并且这个ConstraintLayout还是wrap_content的,暂且将它改成固定高度后,问题就消失了。。。。。

难道ConstraintLayout的measure和layout流程和别人不一样?

暂时赶时间还没来得及查看源码细细追究。后来我改成了其他布局,问题没有了,就算是wrap_content的。

暂且先记下,当做一个思路。

以上是关于Android中动态初始化布局参数以及ConstraintLayout使用中遇到的坑的主要内容,如果未能解决你的问题,请参考以下文章

android 动态设置布局宽度

Android Fragment 参数传递与动态布局

动态添加视图并设置参数和规则无法正常工作(android)

android activity 各种布局方式以及相关参数

如何实现在动态布局中添加更多视图在android中实现滚动视图

android设置linearlayout布局的背景颜色,怎么动态改变背景颜色?