java.lang.IllegalStateException:未找到字段 'caption' 的 ID 为 2131230791 的必需视图 'caption'

java.lang.IllegalStateException: Required view 'caption' with ID 2131230791 for field 'caption' was not found

我创建了一个自定义视图 VerticalTextView,如下所示。

public class VerticalTextView extends AppCompatTextView {

  private boolean isVerticalText = false;
  private boolean topDown = false;

  public VerticalTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    if (attrs != null) {
      TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.VerticalTextView);
      isVerticalText = array.getBoolean(R.styleable.VerticalTextView_is_vertical_text, false);
      topDown = array.getBoolean(R.styleable.VerticalTextView_top_down, false);
      array.recycle();
    }
  }

  public VerticalTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public VerticalTextView(Context context) {
    super(context);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    // vise versa
    if (isVerticalText) {
      int height = getMeasuredWidth();
      int width = getMeasuredHeight();
      setMeasuredDimension(width, height);
    }
  }

  public void setVerticalText(boolean verticalText) {
    isVerticalText = verticalText;
  }

  @Override
  protected void onDraw(Canvas canvas) {
    if (isVerticalText) {
      TextPaint textPaint = getPaint();
      textPaint.setColor(getCurrentTextColor());
      textPaint.drawableState = getDrawableState();

      canvas.save();

      if (topDown) {
        canvas.translate(getWidth(), 0);
        canvas.rotate(90);
      } else {
        canvas.translate(0, getHeight());
        canvas.rotate(-90);
      }

      canvas.translate(getCompoundPaddingLeft(), getExtendedPaddingTop());

      getLayout().draw(canvas);
      canvas.restore();
    } else {
      super.onDraw(canvas);
    }
  }

}  

然后我在LogInFragment中使用如下图:
login_fragment.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:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:background="@color/color_sign_up">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="48dp" />

    <View
        android:id="@+id/logo"
        android:layout_width="@dimen/logo_size"
        android:layout_height="@dimen/logo_size"
        android:layout_marginTop="8dp"
        android:focusable="true"
        android:focusableInTouchMode="true"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/email_input"
        style="@style/Widget.TextInputLayout"
        android:layout_marginTop="48dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/logo">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/email_input_edit"
            style="@style/Widget.TextEdit"
            android:hint="@string/email_hint"
            android:inputType="textEmailAddress" />

    </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/password_input"
        style="@style/Widget.TextInputLayout"
        android:layout_marginTop="16dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/email_input"
        app:passwordToggleTint="@color/color_input_hint">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/password_input_edit"
            style="@style/Widget.TextEdit"
            android:hint="@string/password_hint"
            android:inputType="textPassword" />

    </com.google.android.material.textfield.TextInputLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="48dp"
        android:gravity="center"
        android:text="@string/forgot_password"
        android:textColor="#FFF"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/password_input" />


    <com.learn.fsrsolution.models.VerticalTextView
        android:id="@+id/caption"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/log_in_label"
        android:textAllCaps="true"
        android:textColor="@color/color_label"
        android:textSize="@dimen/unfolded_size"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.78" />

</androidx.constraintlayout.widget.ConstraintLayout>  

LogInFragment.java

public class LogInFragment extends AuthFragment {

  @BindViews(value = {R.id.email_input_edit, R.id.password_input_edit})
  protected List<TextInputEditText> views;
  @BindView(R.id.password_input)
  TextInputLayout inputLayout;
  @BindView(R.id.caption)
  VerticalTextView caption;

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    caption.setText(getString(R.string.log_in_label));
    view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.color_log_in));
    for (TextInputEditText editText : views) {
      if (editText.getId() == R.id.password_input_edit) {
        ButterKnife.bind(this, inputLayout);
        Typeface boldTypeface = Typeface.defaultFromStyle(Typeface.BOLD);
        inputLayout.setTypeface(boldTypeface);
        editText.addTextChangedListener(new TextWatcherAdapter() {
          @Override
          public void afterTextChanged(Editable editable) {
            inputLayout.setPasswordVisibilityToggleEnabled(editable.length() > 0);
          }
        });
      }
      editText.setOnFocusChangeListener((temp, hasFocus) -> {
        if (!hasFocus) {
          boolean isEnabled = editText.getText().length() > 0;
          editText.setSelected(isEnabled);
        }
      });
    }
  }

  @Override
  public int authLayout() {
    return R.layout.login_fragment;
  }

  @Override
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public void fold() {
    lock = false;
    Rotate transition = new Rotate();
    transition.setEndAngle(-90f);
    transition.addTarget(caption);
    TransitionSet set = new TransitionSet();
    set.setDuration(getResources().getInteger(R.integer.duration));
    ChangeBounds changeBounds = new ChangeBounds();
    set.addTransition(changeBounds);
    set.addTransition(transition);
    TextSizeTransition sizeTransition = new TextSizeTransition();
    sizeTransition.addTarget(caption);
    set.addTransition(sizeTransition);
    set.setOrdering(TransitionSet.ORDERING_TOGETHER);
    final float padding = getResources().getDimension(R.dimen.folded_label_padding) / 2;
    set.addListener(new Transition.TransitionListenerAdapter() {
      @Override
      public void onTransitionEnd(Transition transition) {
        super.onTransitionEnd(transition);
        caption.setTranslationX(-padding);
        caption.setRotation(0);
        caption.setVerticalText(true);
        caption.requestLayout();

      }
    });
    TransitionManager.beginDelayedTransition(parent, set);
    caption.setTextSize(TypedValue.COMPLEX_UNIT_PX, caption.getTextSize() / 2);
    caption.setTextColor(Color.WHITE);
    ConstraintLayout.LayoutParams params = getParams();
    params.leftToLeft = ConstraintLayout.LayoutParams.UNSET;
    params.verticalBias = 0.5f;
    caption.setLayoutParams(params);
    caption.setTranslationX((caption.getWidth() / 8) - padding);
  }

  @Override
  public void clearFocus() {
    for (View view : views) view.clearFocus();
  }

}  

然后我在AuthFragment中使用了caption如下。

public abstract class AuthFragment extends Fragment {

  protected Callback callback;

  @BindView(R.id.caption)
  protected VerticalTextView caption;

  @BindView(R.id.root)
  protected ViewGroup parent;

  protected boolean lock;

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
  }

  @Nullable
  @Override
  public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View root = inflater.inflate(authLayout(), container, false);
    ButterKnife.bind(this, root);
    KeyboardVisibilityEvent.setEventListener(getActivity(), isOpen -> {
      callback.scale(isOpen);
      if (!isOpen) {
        clearFocus();
      }
    });
    return root;
  }

  public void setCallback(@NonNull Callback callback) {
    this.callback = callback;
  }

  @LayoutRes
  public abstract int authLayout();

  public abstract void fold();

  public abstract void clearFocus();

  @OnClick(R.id.root)
  public void unfold() {
    if (!lock) {
      caption.setVerticalText(false);
      caption.requestLayout();
      Rotate transition = new Rotate();
      transition.setStartAngle(-90f);
      transition.setEndAngle(0f);
      transition.addTarget(caption);
      TransitionSet set = new TransitionSet();
      set.setDuration(getResources().getInteger(R.integer.duration));
      ChangeBounds changeBounds = new ChangeBounds();
      set.addTransition(changeBounds);
      set.addTransition(transition);
      TextSizeTransition sizeTransition = new TextSizeTransition();
      sizeTransition.addTarget(caption);
      set.addTransition(sizeTransition);
      set.setOrdering(TransitionSet.ORDERING_TOGETHER);
      caption.post(() -> {
        TransitionManager.beginDelayedTransition(parent, set);
        caption.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.unfolded_size));
        caption.setTextColor(ContextCompat.getColor(getContext(), R.color.color_label));
        caption.setTranslationX(0);
        ConstraintLayout.LayoutParams params = getParams();
        params.rightToRight = ConstraintLayout.LayoutParams.PARENT_ID;
        params.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID;
        params.verticalBias = 0.78f;
        caption.setLayoutParams(params);
      });
      callback.show(this);
      lock = true;
    }
  }

  protected ConstraintLayout.LayoutParams getParams() {
    return (ConstraintLayout.LayoutParams) caption.getLayoutParams();
  }

  public interface Callback {
    void show(AuthFragment fragment);

    void scale(boolean hasFocus);
  }
}

当我 运行 该代码时,出现以下错误:

java.lang.IllegalStateException: Required view 'caption' with ID 2131230791 for field 'caption' was not found. How can I fix it?

有时黄油刀不适用于自定义视图。所以也尝试手动完成。

但是在这里,为什么要在登录片段中再次声明标题 属性?

(@BindView(R.id.caption) VerticalTextView caption;)

AuthFragment 是抽象的,您正在将布局 ID 从 child 片段传递给它。

那你为什么要创建另一个 属性 的 VerticalTextView 标题;在 child class 中,当它已经在 parent class.

中时