在其构造函数中设置自定义 ViewGroup/复合视图 LayoutParameters

Setting custom ViewGroup/ composite View LayoutParameters inside its' constructor

我正在扩展 AppBarLayout 以制作我自己的版本。我的目标是在运行时设置一些 LayoutParameters,例如。 AppBar 高度。

我得到一个 NPE,如果我尝试设置任何参数,我猜是因为尚未创建和设置 LayoutParameters。

public MyAppBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    ...

    LayoutInflater.from(context).inflate(R.layout.layout_my_app_bar, this, true);

    ViewGroup.LayoutParams params = this.getLayoutParams();
    params.height = calculateExpandedHeight(selectedAspectRatio);
    this.setLayoutParams(params);

    ...
}

我目前的解决方法是在 onMeasure 中设置 LayoutParams:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if(!hasSetHeight) {
        ViewGroup.LayoutParams params = this.getLayoutParams();
        params.height = mExpandedHeight;
        this.setLayoutParams(params);
    }
}

有没有办法在自定义 ViewGroup/复合视图的构造函数中设置 LayoutParameters?


layout_my_app_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/tools">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay"/>
</merge>

嗯,您得到的NullPointerException 与LayoutParams 为null 有关,因为View 尚未被系统布局。这为您提供了两个选择:您可以延迟参数调整,直到 View 已布局,或使用 View 的内部 onMeasure(类似于您使用的方式)告诉它如何布局。

前者可以通过 ViewTreeOberserver 的 OnGlobalLayoutListener 实现:

public MyAppBar(Context context, AttributeSet attrs) {
    super(context, attrs);
    ...

    getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                ViewGroup.LayoutParams params = this.getLayoutParams(); 
                // Set the params here

                removeOnGlobalLayoutListener(MyAppBar.this, this); // Remove the listener
            }
        });
    requestLayout();
}

@SuppressLint("NewApi")
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) {
    if(Build.VERSION.SDK_INT < 16)
        v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
    else v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}

后者可能会(稍微)好一些,因为您只是应用看起来是纵横比的东西,它是使用 onMeasure 方法并将您的转换应用于其中的 widthMeasureSpec。例如

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int adjustedHeight = MeasureSpec.getSize(widthMeasureSpec) / selectedAspectRatio

    super.onMeasure(widthMeasureSpec, 
        MeasureSpec.makeMeasureSpec(adjustedHeight, MeasureSpec.getMode(MeasureSpec.EXACTLY)));
}

请记住,onMeasure 并没有真正考虑其他 MeasureSpec 的可能性,我做了一个巨大的假设,即您将始终知道宽度并希望高度根据它自行调整。如果您想变得更加动态并考虑其他情况,easy View measuring 做了很好的解释。只是认为这可能会为您指明正确的方向。