Search code examples
androidandroid-layoutandroid-custom-view

Custom View - Move Views into nested Layout


I want to create a custom View, which shows a Card with follwing Contents:

  • TextView (Caption)
  • TextView (Description)
  • LinearLayout (innerLayout)

So i just extended a LinearLayout and inflated my Layout file with it:

public class FrageContainerView extends LinearLayout {
    private TextView objTextViewCaption;
    private TextView objTextViewDescription;

    private String caption;
    private String description;

    private LinearLayout objLayoutInner;

    public FrageContainerView(Context context) {
        this(context, null);
    }

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

        initialize(context, attrs);
    }

    public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initialize(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        initialize(context, attrs);
    }

    private void initialize(Context context, AttributeSet attrs) {
        TypedArray a =
                context.obtainStyledAttributes(attrs, R.styleable.options_frageContainerView, 0, 0);

        caption = a.getString(R.styleable.options_frageContainerView_caption);
        description = a.getString(R.styleable.options_frageContainerView_description);

        a.recycle();

        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER_VERTICAL);

        LayoutInflater inflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.view_fragecontainer, this, true);

        objLayoutInner = (LinearLayout) findViewById(R.id.linearlayout_inner);
        objTextViewCaption = (TextView) findViewById(R.id.textview_caption);
        objTextViewDescription = (TextView) findViewById(R.id.textview_description);

        objTextViewCaption.setText(caption);
        objTextViewDescription.setText(description);
    }

A user which uses my custom View should be able to add his own Components preferably inside the XML like this:

    <FrageContainerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        custom:caption="Hallo"
        custom:description="LOLOLOL"
        android:background="#FF00FF00">
        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="sdsdfsdf"/>
        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="sdsdfsdf"/>
        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="sdsdfsdf"/>
        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="sdsdfsdf"/>

    </FrageContainerView>

The current State is, that the defined EditText's are inflated inside my custom View. I want the EditTexts in my Example to be added to the InnerLayout instead to append them to Custom View.

What's the best approach to do this?


Solution

  • The essence of this problem is how to add child Views to a GroupView that is itself a child of the custom layout.

    This is relatively straightforward programmatically, but more of an issue in XML.

    Androids LayoutInflater logically interprets the nested levels in an XML file and builds the same structure in the hierarchy of Views it creates.
    Your example XML defines 4 EditText Views as first tier children of FrageContainerView, but you want them to be created as second tier children of FrageContainerView sitting inside your LinearLayout. This would mean changing Androids LayoutInflater which is a core component of the whole Android system.

    To do this programmatically you could do something like the following:

    public class FrageContainerView extends LinearLayout {
        private TextView objTextViewCaption;
        private TextView objTextViewDescription;
    
        private String caption;
        private String description;
    
        private LinearLayout objLayoutInner;
    
        public FrageContainerView(Context context) {
            this(context, null);
        }
    
        public FrageContainerView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            initialize(context, attrs);
        }
    
        public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            initialize(context, attrs);
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
    
            initialize(context, attrs);
        }
    
        private void initialize(Context context, AttributeSet attrs) {
    
            // Create your 3 predefined first tier children
    
            // Create the Caption View
            objTextViewCaption = new TextView(context);
            // You can add your new Views to this LinearLayout
            this.addView(objTextViewCaption)
    
            // Create the Description View
            objTextViewDescription = new TextView(context);
            // You can also provide LayoutParams when you add any of your new Views if you want to
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            this.addView(objTextViewDescription, params);
    
            // Create your inner LinearLayout
            objLayoutInner = new LinearLayout(context);
            objLayoutInner.setOrientation(VERTICAL);
            // Add it
            this.addView(objLayoutInner);
    
            TypedArray a =
                    context.obtainStyledAttributes(attrs, R.styleable.options_frageContainerView, 0, 0);
    
            caption = a.getString(R.styleable.options_frageContainerView_caption);
            description = a.getString(R.styleable.options_frageContainerView_description);
    
            a.recycle();
    
            setOrientation(LinearLayout.HORIZONTAL);
            setGravity(Gravity.CENTER_VERTICAL);
    
    /** 
     * Oops! Only just spotted you're inflating your three predefined views 
     * here. It's fine to do this instead of programmatically adding them as I
     * have above. Obviously they should only be added once, so I've commented out
     * your version for the moment.
     **/
    
    //        LayoutInflater inflater =
    //                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    //        inflater.inflate(R.layout.view_fragecontainer, this, true);
    //
    //        objLayoutInner = (LinearLayout) findViewById(R.id.linearlayout_inner);
    //        objTextViewCaption = (TextView) findViewById(R.id.textview_caption);
    //        objTextViewDescription = (TextView) findViewById(R.id.textview_description);
    
            objTextViewCaption.setText(caption);
            objTextViewDescription.setText(description);
        }
    }
    
    /** Public method for adding new views to the inner LinearLayout **/
    public void addInnerView(View view) {
        objLayoutInner.addView(view);
    
    }
    
    /** Public method for adding new views to the inner LinearLayout with LayoutParams **/
    public void addInnerView(View view, LayoutParams params) {
        objLayoutInner.addView(view, params);
    }
    

    You would use this in your code with something like the following:

    FrageContainerView fragContainerView = (FrageContainerView) findViewById(R.id.my_frag_container_view);
    
    TextView newView = new TextView(context);
    newView.setText("whatever");
    
    fragContainerView.addInnerView(newView);