Create Android subviews like iOS subviews
Asked Answered
A

3

10

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.

Accompanist answered 10/5, 2015 at 10:15 Comment(1)
You have a VERY BASIC PROBLEM !! "Views" can't have a subview in droid. FrameLayout can have a subview. The answer to your question is trivial. You just extend FrameLayout, not View.Devote
A
1

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.

Accompanist answered 10/5, 2015 at 14:5 Comment(2)
You don't have to store the context in the constructors. The super implementation already does this for you, and you can use getContext() during and after the constructor.Corrupt
I cannot thank you enough! I finished my iOS app without using storyboard at all and I absolutely loved it. When I tried to develop the Android version of my app, I couldn't find a single tutorial out there that does everything programmatically, I was thinking to myself, well, this sucks ...... until I found your post here! Man, I was about to give up on making the Android version if I had to create my views using XML, you saved me, thank you!Morven
G
1

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);
Gastrovascular answered 18/5, 2017 at 11:29 Comment(0)
M
0

Android and ios app development are two different concepts, each have its own way to perform your task. Sometimes its difficult to develop a piece of code in android and sometimes in ios. To create your view screen/design/GUI in android you can create XML file (recommended) or by code (which is somehow difficult to maintain w.r.t XML).

For your question you don't need to create your own ViewGroup or RelativeLayout or LinearLayout e.g. if you want to use RelativeLayout as a parent for your view than by using XML you can use.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Some Text"/>
</RelativeLayout>

If you want to create your view pragmatically than use

    RelativeLayout parentRelativeLayout = new RelativeLayout(context);
    RelativeLayout.LayoutParams parentParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    parentRelativeLayout.setLayoutParams(parentParams);
    TextView childTextView = new TextView(context);
    childTextView.setText("Some Text");
    mRelativeLayout.addView(childTextView);    

Its just a sample code both have identical output but with programmatic approach it will be difficult as your view grows.

Looking your code you are creating custom view (why?) in android we only need custom views if default views not are providing some functionally which we need to use/implement in our code. As far as i understand you want to use custom views for reuse. Its good approach but if android is providing some functionality than why you are trying to invent wheel again, just use different layouts, use only custom views if you want something extra.

Mirthless answered 10/5, 2015 at 11:37 Comment(4)
Why not ? When you develop apps, there are times when you need a reusable component in your Interface, e.g. a custom looking popup alert confirmation dialog that may include a title label, a question label, a text field and a confirm button. You don't want to duplicate your code and construct that same popup view in every, single, activity. You write a reusable custom view and import it into the activity and create an instance of it. It's not reinventing the wheel, it's about building more sophisticated reusable views using the basic components.Accompanist
I understand your point of you view, i recommend it. I am saying that you should use custom views if and only if you want something more/extra which is not available by default. Yes if you want to use same pop up in your whole app than you can create popup alert and make a function/method in your code and call anytime, anywhere. Android provides a popup dialog if you will go and create custom dialog extending with dialog which is same as default than its inventing the wheel again but if you will use default dialog and use its properties for makeup and use this is re usability.Mirthless
I don't think you do. I think I it's called Compound Control in Android: developer.android.com/guide/topics/ui/custom-components.html. The basic Android dialog view (a label with a Cancel and Confirm button) is a compound view Google wrote for us. It's taking TextViews and Button views, combine them into a new reusable class called "Dialog". This concept is like Object Oriented Programming extending a base class. The child class inherits from base class and adds it own properties and functions, it isn't reinventing the wheel.Accompanist
For example, if I got a "Square" class, it's got a width and height. If I inherit from that class and add a new property "length" to form a new class called "Cube", is that reinventing the wheel ? Likewise, I inherit from the Android View class and add new "properties" e.g. a TextView, a Yes Button and a No button and call this class "TrueOrFalseView". That isn't reinventing the wheel, that's extending the View class to form more sophisticated class, a custom View in this case (at least in my own terminology of it, Android calls it compound views/controls I guess?)Accompanist

© 2022 - 2024 — McMap. All rights reserved.