0

I have a view that inherits from ConstraintLayout. Inside this layout I place the children by use of a ConstraintSet.

This works as long as use a given AttributeSet form outside in the constructor:

public AnswerView(Context context, AttributeSet attrs) {
    super(context, attrs);

It does not work, if I try to assign the layout otherwise, when I don't have attrs available.

public AnswerView(Context context) {
    super(context);
    setLayoutParams(new LayoutParams(
                             ViewGroup.LayoutParams.MATCH_PARENT,
                             ViewGroup.LayoutParams.MATCH_PARENT));

In the first case the children get a pretty distribution like defined by the ConstraintSet, in the second case the all line up at the left border.

In both cases the layout stretches full width, as I can prove by setting a background color.

What is missing in my code?

Blcknx
  • 1,921
  • 1
  • 12
  • 38

1 Answers1

0

This is not a direct answer to the question. A direct answer is still missing. This answer is of the type, "take a better approach!".

When there are few or no answers to a question, I am usually on a track, that few people go. Then there are reasons why they don't.

My approach to programatically nest views within other views is cool, but it turns out to be difficult to do this without the use of layouts. It's too expensive to set up all the benefits of the configurations programatically, that can easily done within a layout. The API of Android is not well prepared for this.

So I turned back to the approach, to create the view classes based on layouts. This means, I create the view with the two parameter constructor for layouts.

In the enclosing view class I can't create the nested view directly any more, as there is no constructor for this. Instead I read the configured view from a layout.

I created a small class to assist extracting configured subparts of a layout:

public class Cloner {

    LayoutInflater inflater;
    int layoutId;

    public Cloner of(Context context) {
        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        return this;
    }

    public Cloner from(@LayoutRes Integer layoutId) {
        this.layoutId = layoutId;
        return this;
    }

    public View clone(@IdRes int id) {
        assert inflater != null;
        View layout = inflater.inflate(layoutId, null);
        View view = layout.findViewById(id);
        ((ViewManager) view.getParent()).removeView(view);
        return view;
    }
}

It is used like this:

MultipleChoiceAnswerView mcav = 
   (MultipleChoiceAnswerView) new Cloner().of(getContext())
       .from(layoutId).clone(R.id.multipleChoiceAnswerView);
mcav.plugModel(challenge.getAnswer());

This already shows, how I connect the model in a second step, because I can't feed it into by the constructor any more.

In the constructor I first evaluate the given attributes. Then I set up the view by inflating an accompanying second layout file, that I don't show here. So there are two layouts involved, one to configure the input to the constructor, one for the internal layout.

When the call to plugModel happens, the inflated internal layout is used and extended by objects matching the given model. Again I don't create this objects programatically, but read them from a third (or the second) layout file as templates. Again done with the assistance of the Cloner given above.

private Button getButton(final Integer index, String choice) {
    Button button = (Button) new Cloner().of(getContext()).from(layoutId).clone(R.id.button);
    button.setId(generateViewId());
    button.setText(choice);
    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            answer.choiceByIndex(index);
        }
    });
    return button;
}

In practice I put this object templates (like a button) as children into the second layout file. So I can style it as a whole in Android Studio Designer.

Programatically I remove the templates before I actually fill the layout with the cloned objects. By this approach I only need to maintain one layout file per view class. (The configuration of the constructor happens in the layout file of the enclosing view.)

Blcknx
  • 1,921
  • 1
  • 12
  • 38