Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom ViewGroup with children inserted at specific spot

I have several Activities in my Android app that have the same basic structure, and I'm trying to make my layouts DRY. The duplicated code looks like the below. It contains a scrollable area with a footer that has "Back" and "Dashboard" links. There's also a FrameLayout being used to apply a gradient on top of the scrollable area.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="689px">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <!-- THE REAL PAGE CONTENT GOES HERE -->

            </LinearLayout>
        </ScrollView>
        <ImageView
            android:src="@drawable/GradientBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom" />
    </FrameLayout>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="50px"
        android:background="?attr/primaryAccentColor">
        <Button
          android:layout_width="wrap_content"
          android:layout_height="26px"
          android:layout_gravity="center_vertical"
          local:MvxBind="Click GoBackCommand" />
        <Button
          android:layout_width="wrap_content"
          android:layout_height="26px"
          local:MvxBind="Click ShowDashboardHomeCommand" />
    </FrameLayout>
</LinearLayout>

To de-dupcliate my Activities, I think what I need to do is create a custom ViewGroup inherited from a LinearLayout. In that code, load the above content from an XML file. Where I am lost is how to get the child content in the Activity to load into the correct spot. E.g. let's say my Activity now contains:

<com.myapp.ScrollableVerticalLayoutWithDashboard
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- THE REAL PAGE CONTENT GOES HERE -->
    <TextView android:text"blah blah blah" />

</com.myapp.ScrollableVerticalLayoutWithDashboard>

Now how do I cause the "blah blah blah" to appear in the correct place? I'm pretty sure if I did this, I would either end up with "blah blah blah" at the top or bottom of the page, not in the middle of the ScrollView as desired.

I'm using API 21 / v5.0+. Technically I'm doing all this with Xamarin, but hopefully that's irrelevant to the answer?

EDIT: An example of what the result would look like is this. The footer and gradient are part of the custom ViewGroup, but the rest would be content within the custom ViewGroup.

Mockup

like image 760
pbarranis Avatar asked Feb 22 '16 18:02

pbarranis


1 Answers

I don't know Xamarin so this is an native android solution, but should be easy to translate.

I think what I need to do is create a custom ViewGroup inherited from a LinearLayout.

Yes, you could extend the LinearLayout class.

Where I am lost is how to get the child content in the Activity to load into the correct spot.

In your custom implementation you need to handle the children manually. In the constructor of that custom class inflate the layout manually:

private LinearLayout mDecor;

public ScrollableVerticalLayoutWithDashboard(Context context, AttributeSet attrs) {
      super(context, attrs);
      // inflate the layout directly, this will pass through our addView method
      LayoutInflater.from(context).inflate(R.layout.your_layout, this);
}

and then override the addView()(which a ViewGroup uses to append it's children) method to handle different types of views:

    private LinearLayout mDecor;

    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        // R.id.decor will be an id set on the root LinearLayout in the layout so we can know
        // the type of view
        if (child.getId() != R.id.decor) {
            // this isn't the root of our inflated view so it must be the actual content, like
            // the bla bla TextView
            // R.id.content will be an id set on the LinearLayout inside the ScrollView where
            // the content will sit
            ((LinearLayout) mDecor.findViewById(R.id.content)).addView(child, params);
            return;
        }
        mDecor = (LinearLayout) child; // keep a reference to this static part of the view
        super.addView(child, index, params); // add the decor view, the actual content will
        // not be added here
    }

In Xamarin you're looking for the https://developer.xamarin.com/api/member/Android.Views.ViewGroup.AddView/p/Android.Views.View/System.Int32/Android.Views.ViewGroup+LayoutParams/ method to override. Keep in mind that this is a simple implementation.

EDIT: Rather than putting a LinearLayout inside a LinearLayout, you could just use the 'merge' tag. Here's the final layout you'd want:

<?xml version="1.0" encoding="utf-8" ?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto">
  <FrameLayout
      android:id="@+id/svfFrame1"
      android:layout_width="match_parent"
      android:layout_height="0dp"
      android:layout_weight="1">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="689px">
      <LinearLayout
          android:id="@+id/svfContentLayout"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical"
          android:paddingBottom="23px" />
    </ScrollView>
    <ImageView
        android:src="@drawable/GradientBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />
  </FrameLayout>
  <FrameLayout
      android:id="@+id/svfFrame2"
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:local="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="50px"
      android:background="?attr/primaryAccentColor">
    <Button
      android:id="@+id/FooterBackButton"
      android:layout_width="wrap_content"
      android:layout_height="26px"
      android:layout_gravity="center_vertical"
      android:layout_marginLeft="24px" />
    <Button
      android:id="@+id/FooterDashboardButton"
      android:layout_width="wrap_content"
      android:layout_height="26px"
      android:layout_gravity="center_vertical|right"
      android:layout_marginRight="24px" />
  </FrameLayout>
</merge>

And here's the final working C# view for Xamarin based on that layout:

public class ScrollableVerticalLayoutWithDashboard: LinearLayout
{
    public ScrollableVerticalLayoutWithDashboard(Context context, IAttributeSet attrs) : base(context, attrs)
    {
        LayoutInflater.From(context).Inflate(Resource.Layout.ScrollableVerticalFooter, this);
        base.Orientation = Orientation.Vertical;
    }

    public override void AddView(View child, int index, ViewGroup.LayoutParams @params)
    {
        // Check to see if the child is either of the two direct children from the layout
        if (child.Id == Resource.Id.svfFrame1 || child.Id == Resource.Id.svfFrame2)
        {
            // This is one of our true direct children from our own layout.  Add it "normally" using the base class.
            base.AddView(child, index, @params);
        }
        else
        {
            // This is content coming from the parent layout, not our own inflated layout.  It 
            //   must be the actual content, like the bla bla TextView.  Add it at the appropriate location.
            ((LinearLayout)this.FindViewById(Resource.Id.svfContentLayout)).AddView(child, @params);
        }
    }
}
like image 137
user Avatar answered Sep 22 '22 04:09

user