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使用中遇到的坑的主要内容,如果未能解决你的问题,请参考以下文章