嵌套约束布局和巨大布局 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 的哪个区域透支了。
我建议对布局的所有可选部分使用 ViewStubs,即默认情况下不可见的项目。然后这些部件在变得可见或需要之前不会充气,这应该可以为您节省一些初始 inflation 时间。
也许您可以消除 MaterialCardView,然后在它们之间没有 MaterialCardView 时合并约束布局,更少的布局意味着更少的 inflation 时间。看起来您正在使用 MaterialCardView 来处理弯角和阴影,但您可以尝试制作自己的带有弯角和阴影的背景,然后将其直接应用于约束布局以使用更少的布局获得相同的效果。
您的自定义视图扩展 LinearLayoutCompat 有什么原因吗?可以使它成为一个约束布局,并使用合并作为根标签来消除一个布局。
此外,您的构造函数中有大量代码,这可能是最大的罪魁祸首,您能否尝试在构造函数中放置计时器并记录 b = CustomEditTextBinding.inflate(LayoutInflater.from(context), this, true);
花费的时间与它花费的时间完成那里的其他一切。这将告诉您问题是否真的与布局相关,或者您是否在构造函数中做了太多事情。
添加 ConstraintLayout
是为了展平视图层次结构,而不是 View
来膨胀。它们不是为嵌套而设计的,所以如果可以避免使用它们,我建议您不要使用它们。在大多数情况下 FrameLayout
或 LinearLayout
就足够了。
我有很多像下面这样的布局。与 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 的哪个区域透支了。
我建议对布局的所有可选部分使用 ViewStubs,即默认情况下不可见的项目。然后这些部件在变得可见或需要之前不会充气,这应该可以为您节省一些初始 inflation 时间。
也许您可以消除 MaterialCardView,然后在它们之间没有 MaterialCardView 时合并约束布局,更少的布局意味着更少的 inflation 时间。看起来您正在使用 MaterialCardView 来处理弯角和阴影,但您可以尝试制作自己的带有弯角和阴影的背景,然后将其直接应用于约束布局以使用更少的布局获得相同的效果。
您的自定义视图扩展 LinearLayoutCompat 有什么原因吗?可以使它成为一个约束布局,并使用合并作为根标签来消除一个布局。
此外,您的构造函数中有大量代码,这可能是最大的罪魁祸首,您能否尝试在构造函数中放置计时器并记录 b = CustomEditTextBinding.inflate(LayoutInflater.from(context), this, true);
花费的时间与它花费的时间完成那里的其他一切。这将告诉您问题是否真的与布局相关,或者您是否在构造函数中做了太多事情。
ConstraintLayout
是为了展平视图层次结构,而不是 View
来膨胀。它们不是为嵌套而设计的,所以如果可以避免使用它们,我建议您不要使用它们。在大多数情况下 FrameLayout
或 LinearLayout
就足够了。