嵌套约束布局和巨大布局 inflation 次

Nested Constraint layouts and huge layout inflation times

我有很多像下面这样的布局。与 NestedScrollView 相同,而不是 CardView。

但在所有情况下,这都会导致嵌套约束布局。特别是在一个屏幕中,我经常使用以下内容作为自定义视图类型。但这会导致巨大的 inflation 次。我测量了 500 毫秒或更多。这当然是不能接受的。

我可以做些什么来提高布局性能。自定义视图是一个 EditText,看起来像

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">


<com.google.android.material.card.MaterialCardView
    android:id="@+id/cardView"
    android:layout_width="0dp"
    android:layout_height="48dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--Some layout code -->
    </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>


<androidx.appcompat.widget.AppCompatTextView
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toStartOf="parent"
    app:layout_constraintStart_toStartOf="@+id/cardView"
    app:layout_constraintTop_toBottomOf="@+id/cardView" />

</androidx.constraintlayout.widget.ConstraintLayout>

编辑:根据要求。完整的 xml、java 以及我如何在另一个 xml

中使用它

CustomEditText.xml:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingStart="8dp"
android:paddingTop="9dp"
android:paddingEnd="8dp">


<com.google.android.material.card.MaterialCardView
    android:id="@+id/cardView"
    android:layout_width="0dp"
    android:layout_height="48dp"
    app:cardCornerRadius="12dp"
    app:cardElevation="3dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_goneMarginBottom="10dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/iv_startIcon"
            android:layout_width="19dp"
            android:layout_height="0dp"
            android:layout_marginStart="19dp"
            android:layout_marginTop="15dp"
            android:layout_marginBottom="15dp"
            android:adjustViewBounds="true"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:tint="?attr/colorPrimary"
            tools:srcCompat="@drawable/icon_phone"
            tools:visibility="visible" />

        <TextView
            android:id="@+id/tv_prefix_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="19dp"
            android:textColor="?attr/colorPrimary"
            android:visibility="gone"
            android:textSize="13sp"
            tools:visibility="visible"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="ABC" />

        <androidx.constraintlayout.widget.Barrier
            android:id="@+id/barrier_start"
            android:layout_width="1dp"
            android:layout_height="wrap_content"
            app:barrierDirection="end"
            app:constraint_referenced_ids="tv_prefix_text,iv_startIcon" />

        <androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_text"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginStart="13dp"
            android:layout_marginEnd="19dp"
            android:background="@null"
            android:textSize="13sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/barrier_end"
            app:layout_constraintStart_toEndOf="@+id/barrier_start"
            app:layout_constraintTop_toTopOf="parent"
         />

        <TextView
            android:id="@+id/tv_hint"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginStart="13dp"
            android:gravity="center_vertical"
            android:textSize="13sp"
            android:maxLines="1"
            android:layout_marginEnd="57dp"
            android:ellipsize="end"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/barrier_start"
            app:layout_constraintTop_toTopOf="parent"
            tools:hint="Hint text" />

        <androidx.constraintlayout.widget.Barrier
            android:id="@+id/barrier_end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:barrierDirection="left"
            app:constraint_referenced_ids="iv_endIcon,tv_optional" />

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tv_optional"
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:layout_marginEnd="19dp"
            android:alpha="0.44"
            android:gravity="center_vertical"
            android:text="@string/edit_text_optional"
            android:textSize="10sp"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:visibility="visible" />

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/iv_endIcon"
            android:layout_width="39dp"
            android:layout_height="0dp"
            android:layout_marginEnd="9dp"
            android:adjustViewBounds="true"
            android:paddingStart="10dp"
            android:paddingTop="18dp"
            android:paddingEnd="10dp"
            android:paddingBottom="18dp"
            android:visibility="invisible"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1"
            app:layout_constraintStart_toEndOf="@+id/barrier_end"
            app:layout_constraintTop_toTopOf="parent"
            tools:srcCompat="@drawable/icon_alert"
            tools:tint="?colorError"
            tools:visibility="visible" />

        <androidx.core.widget.ContentLoadingProgressBar
            android:id="@+id/pb_loading"
            style="?android:attr/progressBarStyle"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_marginTop="15dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="15dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
          />
    </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>


<androidx.appcompat.widget.AppCompatTextView
    android:id="@+id/tv_count"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="7dp"
    android:layout_marginEnd="14dp"
    android:alpha="0.44"
    android:textSize="9sp"
    android:textStyle="bold"
    android:fontFamily="@font/nunito2"
    android:visibility="gone"
    app:layout_constraintEnd_toEndOf="@+id/cardView"
    app:layout_constraintTop_toBottomOf="@+id/cardView"
    tools:text="0/30"
    tools:visibility="visible" />

<androidx.appcompat.widget.AppCompatTextView
    android:id="@+id/tv_helper"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="14dp"
    android:layout_marginTop="7dp"
    android:layout_marginEnd="16dp"
    android:alpha="0.44"
    android:textSize="9sp"
    android:visibility="visible"
    app:layout_constraintEnd_toStartOf="@+id/tv_count"
    app:layout_constraintStart_toStartOf="@+id/cardView"
    app:layout_constraintTop_toBottomOf="@+id/cardView"
    app:layout_goneMarginEnd="19dp"
    tools:text="Helper text"
    tools:visibility="visible" />

</androidx.constraintlayout.widget.ConstraintLayout>

和CustomEditText.java(抱歉,代码很多。但我不知道什么是重要的,所以我无法减少):

public class CustomEditText extends LinearLayoutCompat {
private static final String TAG = "CustomEditText";
private static final String ANDROID_NS = "http://schemas.android.com/apk/res/android";
private static final String APP_NS = "http://schemas.android.com/apk/res-auto";
private static final String COUNTER_SEPARATOR = "/";
private static final int TRANSLATION_Y_DP = 21;
private final int translationY;
private static final float HELPER_ALPHA = 0.44f;
private final String counterErrorText;
private final List<TextWatcher> textWatcherList = new ArrayList<>();

private final Drawable alertIcon;
private final Drawable clearIcon;


private final CustomEditTextBinding b;

private String errorText;

private String helperText;

private String hintText;

private Drawable startIcon;
private int iconTint;

private int counterMaxLength;

private boolean isCounterEnabled;

private boolean isOptional;

private boolean isProgressBarVisible = false;

private boolean isClearEnabled;

private boolean hasFocus;

private boolean isError = false;

private boolean isCounterError = false;

private String prefixText;

private final boolean initialized;

private final boolean hasBorder;


public CustomEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    b = CustomEditTextBinding.inflate(LayoutInflater.from(context), this, true);
    b.tvHint.setPivotX(0);
    b.pbLoading.hide();
    translationY = GuiUtils.dpToPx(context, TRANSLATION_Y_DP);
    alertIcon = AppCompatResources.getDrawable(context, R.drawable.icon_alert);
    clearIcon = AppCompatResources.getDrawable(context, R.drawable.icon_clear);


    counterErrorText = context.getString(R.string.counter_max_length_error);

    TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CustomEditText);

    //Start icon
    startIcon = attributes.getDrawable(R.styleable.CustomEditText_startIcon);
    iconTint = attributes.getColor(R.styleable.CustomEditText_iconTint, ColorUtils.getAttrColor(context, R.attr.colorPrimary));
    refreshStartIcon();

    //Hint text
    hintText = attributes.getString(R.styleable.CustomEditText_hint);
    refreshHintText();

    //Helper text
    helperText = attributes.getString(R.styleable.CustomEditText_helperText);
    refreshHelperText();

    //Error text
    errorText = attributes.getString(R.styleable.CustomEditText_errorText);

    //Prefix text
    prefixText = attributes.getString(R.styleable.CustomEditText_prefixText);
    refreshPrefixText();

    isClearEnabled = attributes.getBoolean(R.styleable.CustomEditText_clearEnabled, false);
    refreshClearEnabled();

    //Optional text
    isOptional = attributes.getBoolean(R.styleable.CustomEditText_optional, false);
    refreshOptional();

    b.ivEndIcon.setVisibility(GONE);
    refreshEndIcon();

    //Counter
    isCounterEnabled = attributes.getBoolean(R.styleable.CustomEditText_counterEnabled, false);
    counterMaxLength = attributes.getInt(R.styleable.CustomEditText_counterMaxLength, 0);
    refreshCounter();

    //Elevation
    int elevation = attributes.getDimensionPixelSize(R.styleable.CustomEditText_elevation, -1);
    if (elevation != -1) b.cardView.setCardElevation(elevation);

    //Border
    hasBorder = attributes.getBoolean(R.styleable.CustomEditText_hasBorder, false);
    refreshStroke();

    if (attrs != null) {
        //Pass through to edit text
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            int importantForAutofill = attrs.getAttributeIntValue(ANDROID_NS, "importantForAutofill", 0);
            b.etText.setImportantForAutofill(importantForAutofill);
            String autofillHints = attrs.getAttributeValue(ANDROID_NS, "autofillHints");
            b.etText.setAutofillHints(autofillHints);
        }

        //Focusable
        boolean isFocusable = attrs.getAttributeBooleanValue(ANDROID_NS, "focusable", true);
        b.etText.setFocusable(isFocusable);

        //CursorVisible
        boolean isCursorVisible = attrs.getAttributeBooleanValue(ANDROID_NS, "cursorVisible", true);
        b.etText.setCursorVisible(isCursorVisible);

        //InputType
        int inputType = attrs.getAttributeIntValue(ANDROID_NS, "inputType", InputType.TYPE_CLASS_TEXT);
        b.etText.setInputType(inputType);

        //ImeOption
        int imeOptions = attrs.getAttributeIntValue(ANDROID_NS, "imeOptions", EditorInfo.IME_ACTION_UNSPECIFIED);
        b.etText.setImeOptions(imeOptions);

        //MaxLines
        int maxLines = attrs.getAttributeIntValue(ANDROID_NS, "maxLines", 1);
        b.etText.setMaxLines(maxLines);

    }
    attributes.recycle();
    initListeners();
    initialized = true;
}

private void refreshClearEnabled() {
    refreshEndIcon();
}


private void refreshOptional() {
    if (isProgressBarVisible || !isOptional || isError || isCounterError || !isEmpty() && isClearEnabled)
        b.tvOptional.setVisibility(GONE);
    else b.tvOptional.setVisibility(VISIBLE);
}

private void refreshStartIcon() {
    if (startIcon != null) {
        b.ivStartIcon.setImageDrawable(startIcon);
        b.ivStartIcon.setVisibility(VISIBLE);
        b.tvPrefixText.setVisibility(GONE);
        b.ivStartIcon.setImageTintList(ColorStateList.valueOf(iconTint));
    } else {
        b.ivStartIcon.setVisibility(GONE);
    }
}

private void refreshPrefixText() {
    if (prefixText != null) {
        b.ivStartIcon.setVisibility(GONE);
        b.tvPrefixText.setVisibility(VISIBLE);
        b.tvPrefixText.setText(prefixText);
    } else {
        b.tvPrefixText.setVisibility(GONE);
    }
}

private void refreshEndIcon() {
    if (!isProgressBarVisible && (isError || isCounterError)) {
        b.ivEndIcon.setVisibility(VISIBLE);
        b.ivEndIcon.setImageDrawable(alertIcon);
        b.ivEndIcon.setImageTintList(ColorUtils.getAttrColorList(getContext(), R.attr.colorError));
        b.ivEndIcon.setAlpha(1f);
    } else if (!isProgressBarVisible && isClearEnabled && !isEmpty()) {
        b.ivEndIcon.setImageDrawable(clearIcon);
        b.ivEndIcon.setImageTintList(ColorUtils.getAttrColorList(getContext(), R.attr.colorOnBackground));
        b.ivEndIcon.setVisibility(VISIBLE);
        b.ivEndIcon.setAlpha(0.42f);
    } else {
        b.ivEndIcon.setVisibility(GONE);
    }
}

private void refreshHelperText() {
    if (isError || isCounterError) {
        if (isCounterError) b.tvHelper.setText(counterErrorText);
        else b.tvHelper.setText(errorText);
        b.tvHelper.setVisibility(VISIBLE);
        b.tvHelper.setAlpha(1f);
        b.tvHelper.setTextColor(ColorUtils.getAttrColor(getContext(), R.attr.colorError));
    } else if (helperText == null) {
        b.tvHelper.setVisibility(INVISIBLE);
    } else {
        b.tvHelper.setAlpha(HELPER_ALPHA);
        b.tvHelper.setVisibility(VISIBLE);
        b.tvHelper.setText(helperText);
        b.tvHelper.setTextColor(ColorUtils.getAttrColor(getContext(), R.attr.colorOnBackground));
    }
}

private void refreshCounter() {
    if (isCounterEnabled) {
        b.tvCount.setVisibility(VISIBLE);
        refreshCounterText();
    } else {
        b.tvCount.setVisibility(GONE);
    }
}

private void refreshStroke() {
    if (isError || isCounterError) {
        setStroke(StrokeType.ERROR);
    } else if (hasFocus) {
        setStroke(StrokeType.FOCUS);
    } else if (hasBorder) {
        setStroke(StrokeType.DEFAULT);
    } else {
        setStroke(StrokeType.NONE);
    }
}

private void refreshProgressBar() {
    refreshOptional();
    refreshClearEnabled();
    if (isProgressBarVisible) b.pbLoading.show();
    else b.pbLoading.hide();
}

@SuppressLint("SetTextI18n")
private void refreshCounterText() {
    if (!isCounterEnabled) return;
    int countChars = getText().length();
    String counterText = countChars + COUNTER_SEPARATOR + counterMaxLength;
    isCounterError = countChars > counterMaxLength;
    if(initialized && isCounterError) setError();
    b.tvCount.setText(counterText);
}

private void refreshHintText() {
    b.tvHint.setHint(hintText);
}


private void initListeners() {
    b.etText.addTextChangedListener((TextWatcherAdapter) (s, start, before, count) -> {
        //Do not trigger on init, only on user interaction
        if (!initialized) return;
        if (s.length() == 1) triggerAnimation(true);
        else if (s.length() == 0) triggerAnimation(false);

    });

    b.etText.setOnFocusChangeListener((v, hasFocus) -> {
        this.hasFocus = hasFocus;
        refreshError();
        refreshStroke();

    });
    b.etText.addTextChangedListener((TextWatcherAdapter) (s, start, before, count) -> {
        clearError();
        refreshCounterText();
        refreshEndIcon();
        refreshOptional();
        //Trigger all other TextWatchers, add them here instead of the text itself to trigger all internal ones first
        for (TextWatcher textWatcher : textWatcherList) textWatcher.onTextChanged(s,start,before,count);
    });

    b.etText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            //Trigger all other TextWatchers, add them here instead of the text itself to trigger all internal ones first
            for (TextWatcher textWatcher : textWatcherList) textWatcher.afterTextChanged(s);

        }
    });


    b.etText.setOnEditorActionListener((v, actionId, event) -> {
        if (actionId == EditorInfo.IME_ACTION_DONE) {
            b.etText.clearFocus();
        }
        return false;
    });
    b.ivEndIcon.setOnClickListener(v -> {
        if (isClearEnabled) b.etText.setText(null);
    });
}

private void triggerAnimation(boolean fadeOut) {
    if (fadeOut) {
        b.tvHint.animate().translationY(-translationY * 0.32f).scaleX(0.7f).scaleY(0.7f);
        //This works but not with setText because getHeight return 0. The reason for that: i do not know
        //b.tvHint.animate().translationY(-b.tvHint.getHeight() * 0.32f).scaleX(0.7f).scaleY(0.7f);
    } else {
        b.tvHint.animate().translationY(0).translationX(0).scaleX(1f).scaleY(1f);
    }
}

public void setText(String text) {
    b.etText.setText(text);
    if (text != null && !text.isEmpty()) triggerAnimation(true);
}

private void refreshError() {
    refreshOptional();
    refreshEndIcon();
    refreshHelperText();
    refreshStroke();

}

@Override
public void setEnabled(boolean enabled) {
    super.setEnabled(enabled);
    b.etText.setEnabled(enabled);

    if (enabled) {
        PictureUtils.saturatePicture(b.ivStartIcon, 1);
        PictureUtils.saturatePicture(b.ivEndIcon, 1);
        b.tvPrefixText.setTextColor(ColorUtils.getPrimary(getContext()));
        if (isError) setStroke(StrokeType.ERROR);
        else if (hasFocus) setStroke(StrokeType.FOCUS);
        else setStroke(StrokeType.DEFAULT);
        b.cardView.setCardBackgroundColor(ColorUtils.getAttrColor(getContext(), R.attr.cardColorOnPrimary));
    } else {
        PictureUtils.saturatePicture(b.ivStartIcon, 0);
        PictureUtils.saturatePicture(b.ivEndIcon, 0);
        b.tvPrefixText.setTextColor(ColorUtils.getAttrColor(getContext(), R.attr.colorItemDisabled));
        setStroke(StrokeType.DISABLED);
        b.cardView.setCardBackgroundColor(ColorUtils.getAttrColor(getContext(), R.attr.colorDisabled));
    }
}

public void setError() {
    isError = true;
    refreshError();
}

public void setError(String errorText) {
    if (errorText == null) {
        clearError();
    } else {
        isError = true;
        this.errorText = errorText;
        refreshError();
    }
}

public void clearError() {
    isError = false;
    //Its up to the user to decide that this will not be an error! So reset no matter if more is entered
    isCounterError = false;
    refreshError();
}

public boolean isValid() {
    if (isOptional) return !isError;
    else return !getText().isEmpty() && !isError;
}

private void setStroke(StrokeType strokeType) {
    switch (strokeType) {
        case ERROR:
            b.cardView.setStrokeColor(ColorUtils.getAttrColor(getContext(), R.attr.colorError));
            b.cardView.setStrokeWidth(4);
            break;
        case FOCUS:
            b.cardView.setStrokeColor(ColorUtils.getAttrColor(getContext(), R.attr.colorPrimary));
            b.cardView.setStrokeWidth(4);
            break;
        case DEFAULT:
            int color = ColorUtils.getAttrColor(getContext(), R.attr.colorOnBackground);
            ColorDrawable cd = new ColorDrawable(color);
            cd.setAlpha(54);
            b.cardView.setStrokeColor(cd.getColor());
            b.cardView.setStrokeWidth(2);
            break;
        case DISABLED:
            b.cardView.setStrokeColor(ColorUtils.getAttrColor(getContext(), R.attr.colorItemDisabled));
            b.cardView.setStrokeWidth(2);
            break;
        case NONE:
            b.cardView.setStrokeColor(null);
            b.cardView.setStrokeWidth(0);
            break;
    }
}

public String getText() {
    return b.etText.getEditableText().toString().trim();
}

public void addTextChangedListener(TextWatcher textWatcher) {
    //b.etText.addTextChangedListener(textWatcher);
    textWatcherList.add(textWatcher);
}

public boolean isEmpty() {
    return StringUtils.checkNull(getText());
}

@Override
public void setOnClickListener(@Nullable OnClickListener listener) {
    b.etText.setOnClickListener(listener);
}

public void setErrorText(String errorText) {
    this.errorText = errorText;
}

public void setHelperText(String helperText) {
    this.helperText = helperText;
    refreshHelperText();
}

public void setHintText(String hintText) {
    this.hintText = hintText;
    refreshHintText();
}

public void setStartIcon(Drawable startIcon) {
    this.startIcon = startIcon;
    refreshStartIcon();
}

public void setPrefixText(String prefix) {
    this.prefixText = prefix;
    refreshPrefixText();
}

public void setLoading(boolean active) {
    this.isProgressBarVisible = active;
    refreshProgressBar();
}

public void setCounterMaxLength(int counterMaxLength) {
    this.counterMaxLength = counterMaxLength;
    refreshCounter();
}

public void setCounterEnabled(boolean counterEnabled) {
    isCounterEnabled = counterEnabled;
    refreshCounter();
}

public void setOptional(boolean optional) {
    isOptional = optional;
    refreshOptional();
}

public void setClearEnabled(boolean clearEnabled) {
    isClearEnabled = clearEnabled;
    refreshClearEnabled();
}

public boolean isCounterError() {
    return isCounterError;
}

@Override
public void setFocusable(boolean focusable) {
    b.etText.setFocusable(focusable);
}

public void setCursorVisible(boolean cursorVisible) {
    b.etText.setCursorVisible(cursorVisible);
}

public boolean isError() {
    return isError || isCounterError;
}

private enum StrokeType {ERROR, FOCUS, DEFAULT, DISABLED, NONE}

// TODO: 13.10.2021 Text drawable needs to get bigger
public void scalePictureForText() {
    LayoutParams params = new LayoutParams(30, 0);
    params.setMargins(19, 5, 0, 5);
    b.ivStartIcon.setLayoutParams(params);
}
}

这就是我在另一个布局中使用它的方式(我删除了约束):

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:fillViewport="true"
    android:overScrollMode="never">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal">

        <com.company.app.views.CustomEditText
            android:id="@+id/et_number_primary"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:imeOptions="actionDone"
            android:inputType="numberDecimal"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<!--Button that should not scroll-->
</androidx.constraintlayout.widget.ConstraintLayout>

我可以建议您一些方法来分析您的布局:

  • 您可以使用 Lint 工具。 Select 分析(从顶部栏开始)-> 检查代码 -> select 您的布局文件或整个项目(如果需要)。这将自动检测布局中的问题并提出修复建议。

  • 您也可以在设备的开发者选项中使用 Profile GPU rendering。此工具允许您查看布局和测量阶段为渲染的每一帧花费了多长时间。此数据可以帮助您诊断运行时性能问题,并帮助您确定需要解决的布局和测量问题(如果有的话)。

  • 或者从开发者选项中启用 Debug GPU Overdraw 选项以显示 ui 的哪个区域透支了。

求助:Inspect GPU rendering speed and overdraw

我建议对布局的所有可选部分使用 ViewStubs,即默认情况下不可见的项目。然后这些部件在变得可见或需要之前不会充气,这应该可以为您节省一些初始 inflation 时间。

也许您可以消除 MaterialCardView,然后在它们之间没有 MaterialCardView 时合并约束布局,更少的布局意味着更少的 inflation 时间。看起来您正在使用 MaterialCardView 来处理弯角和阴影,但您可以尝试制作自己的带有弯角和阴影的背景,然后将其直接应用于约束布局以使用更少的布局获得相同的效果。

您的自定义视图扩展 LinearLayoutCompat 有什么原因吗?可以使它成为一个约束布局,并使用合并作为根标签来消除一个布局。

此外,您的构造函数中有大量代码,这可能是最大的罪魁祸首,您能否尝试在构造函数中放置计时器并记录 b = CustomEditTextBinding.inflate(LayoutInflater.from(context), this, true); 花费的时间与它花费的时间完成那里的其他一切。这将告诉您问题是否真的与布局相关,或者您是否在构造函数中做了太多事情。

添加

ConstraintLayout 是为了展平视图层次结构,而不是 View 来膨胀。它们不是为嵌套而设计的,所以如果可以避免使用它们,我建议您不要使用它们。在大多数情况下 FrameLayoutLinearLayout 就足够了。