Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create Android subviews like iOS subviews

I'm trying to programmatically (not using XML files) create custom subviews in Android (that's what I call it in iOS) that is a basically a number of basic views (labels, buttons, text fields etc) put together into a reusable subview class so I can use it inside my UIViewControllers or Activity in Android.

I don't know what is the correct terminology in Android. There seems to be a million different terminologies.

Custom View, ViewGroups, Layouts, Widgets, Components, whatever you want to call it.

In iOS this is simply done like this:

CustomView.h

@interface CustomView : UIView

@property (nonatomic, strong) UILabel *message;
@property (nonatomic, strong) UIButton *button;

@end

CustomView.m

@implementation CustomView

-(id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];

    if(self)
    {
        [self initViews];
        [self initConstraints];
    }

    return self;
}

-(void)initViews
{
    self.message = [[UILabel alloc] init];
    self.button = [[UIButton alloc] init];

    [self addSubview:self.message];
    [self addSubview:self.button];
}

-(void)initConstraints
{
    id views = @{
                 @"message": self.message,
                 @"button": self.button
                 };

    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[message]|" options:0 metrics:nil views:views]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[button]|" options:0 metrics:nil views:views]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[message][button]|" options:0 metrics:nil views:views]];
}

@end

Now I can reuse this custom view in any ViewController (Android Activity) I chose.

How does one achieve something like that in Android?

I've been looking around and from what I gather in Android, to add subviews, I add them to Layouts:

RelativeLayout relativeLayout = new RelativeLayout(...);
TextView textView = new TextView(...);

relativeLayout.addSubview(textView);

Does that mean I need extend RelativeLayout or ViewGroup?

Looking at this page: http://developer.android.com/reference/android/view/ViewGroup.html

It seems like we need to write some really complicated logic to layout the custom view such as:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();

        // These keep track of the space we are using on the left and right for
        // views positioned there; we need member variables so we can also use
        // these for layout later.
        mLeftWidth = 0;
        mRightWidth = 0;

        // Measurement will ultimately be computing these values.
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        // Iterate through all children, measuring them and computing our dimensions
        // from their size.
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                // Measure the child.
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

                // Update our size information based on the layout params.  Children
                // that asked to be positioned on the left or right go in those gutters.
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp.position == LayoutParams.POSITION_LEFT) {
                    mLeftWidth += Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                } else if (lp.position == LayoutParams.POSITION_RIGHT) {
                    mRightWidth += Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                } else {
                    maxWidth = Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                }
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
            }
        }

        // Total width is the maximum width of all inner children plus the gutters.
        maxWidth += mLeftWidth + mRightWidth;

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Report our final dimensions.
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
    }

All I'm trying to do is use multiple basic android labels, views, buttons in a custom view like the iOS example above, why is it so hard in Android ?

I was hoping for something simple like this:

public class CustomView extends View
{
    public RelativeLayout mainLayout;

    public TextView message;
    public Button button;

    // default constructor
    public CustomView()
    {
        ...

        initViews();
        initLayouts();
        addViews();
    }

    public initViews()
    {
        mainLayout = new RelativeLayout(this);

        message = new TextView(this);

        button = new Button(this);

        ...
    }

    public initLayouts()
    {
        // --------------------------------------------------
        // use Android layout params to position subviews 
        // within this custom view class
        // --------------------------------------------------
    }

    public addViews()
    {
        mainLayout.addView(message);
        mainLayout.addView(button);

        setContentView(mainLayout);  
    }
}

I'm sorry I am sincerely trying to learn and build a basic Android application and not trying to bash Android's way of doing things.

I know how to add and layout subviews inside an Activity and have been doing so for the past two days but not inside a custom View/View Group/Layout. I don't want to end up constructing the exact same subview for each of my Activity in the Android app, that just goes against good coding practice right ? :D

Just need a bit of guidance here from others who have done both iOS and Android development.

Edit

It seems like what I'm looking for is called a Compound Control: http://developer.android.com/guide/topics/ui/custom-components.html

I'll keep digging and hopefully achieve the result I'm after :D

Just need to work out this Inflater business.

like image 264
Zhang Avatar asked May 10 '15 10:05

Zhang


2 Answers

To add a simple answer for the general visitor to this question...

You can't add subviews to an Android View like you can with an iOS UIView.

If you need to add subviews in Android, then use one of the ViewGroup subclasses (like LinearLayout, RelativeLayout, or even your own custom subclass).

myViewGroup.addView(myView);
like image 131
Suragch Avatar answered Nov 07 '22 20:11

Suragch


OK, I think I got it, not sure if it's the best solution but it does what I want.

So it goes something like this:

public class CustomView extends RelativeLayout
{
    private Context context;

    public TextView message;
    public Button button;

    public CustomView(Context context)
    {
        super(context);

        // ---------------------------------------------------------
        // store context as I like to create the views inside 
        // initViews() method rather than in the constructor
        // ---------------------------------------------------------
        this.context = context;

        initViews();
        initLayouts();
        addViews();
    }

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

        // ---------------------------------------------------------
        // store context as I like to create the views inside 
        // initViews() method rather than in the constructor
        // ---------------------------------------------------------
        this.context = context;

        initViews();
        initLayouts();
        addViews();
    }

    public initViews()
    {
        // ----------------------------------------
        // note "context" refers to this.context
        // that we stored above.
        // ----------------------------------------
        message = new TextView(context);
        ...

        button = new Button(context);
        ...

    }

    public initLayouts()
    {
        // --------------------------------------------------
        // use Android layout params to position subviews 
        // within this custom view class
        // --------------------------------------------------

        message.setId(View.generateViewId());
        button.setId(View.generateViewId());

        RelativeLayout.LayoutParams messageLayoutParams = new RelativeLayout.LayoutParams(
                LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT
        );

        message.setLayoutParams(messageLayoutParams);


        RelativeLayout.LayoutParams buttonLayoutParams = new RelativeLayout.LayoutParams(
                LayoutParams.MATCH_PARENT,
                LayoutParams.WRAP_CONTENT
        );

        button.setLayoutParams(buttonLayoutParams);
    }

    public addViews()
    {
        // adding subviews to layout
        addView(message);
        addView(button); 
    }
}

Now I can use this custom view in any of my Activity:

public class MainActivity extends ActionBarActivity {

    // custom view instance
    protected CustomView approvalView;

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            ...
            initViews();
        }

        public initViews()
        {
            ...

            approvalView = new CustomView(this);
            approvalView.message.setText("1 + 1 = 2");
            approvalView.button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Log.d("Logger", "Math formula approved! :D");
                }
            });

        }
}

Inflater is used if we create our layout using XML which isn't something I like to do, so I generated my view's layout programmatically :D

The above "RelativeLayout" in "extends RelativeLayout" can be replace with "LinearLayout" or other layouts of course.

like image 1
Zhang Avatar answered Nov 07 '22 20:11

Zhang