I have an XML layout similar to this (the XML and code is a lot more complex but I've simplified for brevity):
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageButton
android:id="#+id/button01"
android:src="#drawable/key_01" />
<ImageButton
android:id="#+id/button02"
android:src="#drawable/key_02" />
<ImageButton
android:id="#+id/button03"
android:src="#drawable/key_03" />
</TableRow>
</TableLayout>
</merge>
I have this layout as a custom widget.
public class ThreeButtons extends LinearLayout
{
public ThreeButtons(Context context, AttributeSet attrs)
{
super(context, attrs);
final LayoutInflater inflator = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflator.inflate(R.layout.three_buttons, this);
if (!isInEditMode())
setClickListeners();
}
...
private void setClickListeners()
{
...
}
}
The issue is that I have an extra level of depth that I don't think should be there.
ie: the actual ThreeButtons class is a LinearLayout that only has a single child, which is the inflated XML.
I would have thought that it would make more sense for the widget to be created directly from the XML rather than appending it as a child. Is the way I'm doing it the only way to have custom widgets? Or is there a cleaner way?
Thanks,
Brad
Rather than extending a LinearLayout, you can have your widget extend TableLayout.
Then, within your Merge tag, simply contain the list of rows that you want inflated into your widget.
This eliminates the additional LinearLayout.
Example:
<merge ... >
<TableRow ... />
<TableRow ... />
<TableRow ... />
<TableRow ... />
</merge>
Related
Imagine you had the following design for a card that you'd be using through an app. A title, button, a divider, and then space for dynamic content indicated by the blue box. We can add anything we'd need inside the blue region but the frame for holding the content would be consistent. For example:
Card with Placeholder Region
If I was going to put two TextViews inside it might look like this, with the (trimmed down) view layout below:
Card with Two TextViews
<com.google.android.material.card.MaterialCardView>
<LinearLayout android:orientation="vertical">
<LinearLayout android:orientation="horizontal">
<TextView android:text="My Title" />
<com.google.android.material.button.MaterialButton android:text="ACTION" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000" />
<LinearLayout android:id="+#id/contentGoesHere">
<TextView android:text="First element" />
<TextView android:text="Second element" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Ideally I'd like a custom view so that developers can just do the following and get the consistent stying, or add to the view programatically:
<com.customview.CustomView>
<TextView android:text="First element" />
<TextView android:text="Second element" />
</com.customview.CustomView>
My problem is that just extending LinearLayout and making a custom view wont work - it'll create the layout but there's no way to indicate that the inner LinearLayout is what I want the views to be added to, so any subviews added in the XML are ignored.
Do I need to make a custom ViewGroup and manually inflate the custom holder for the LinearLayout (help! onMeasure and onLayout!?) or is there an easier way to make a custom LinearLayout view with this styled frame around it?
My problem is that just extending LinearLayout and making a custom
view wont work - it'll create the layout but there's no way to
indicate that the inner LinearLayout is what I want the views to be
added to, so any subviews added in the XML are ignored.
You can manually move the child views into nested LinearLayout.
For example:
public class CustomView extends LinearLayout {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
final ViewGroup container = inflate(getContext(), R.layout.card_layout, this)
.findViewById(R.id.inner_content_container);
while (getChildCount() > 1) {
final View child = getChildAt(0);
removeView(child);
container.addView(child, child.getLayoutParams());
}
}
}
card_layout.xml
<com.google.android.material.card.MaterialCardView>
<LinearLayout android:orientation="vertical">
<LinearLayout android:orientation="horizontal">
<TextView android:text="My Title" />
<com.google.android.material.button.MaterialButton android:text="ACTION" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000" />
<LinearLayout
android:id="#+id/inner_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- content goes here -->
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
...
<com.customview.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:text="First element" />
<TextView android:text="Second element" />
</com.customview.CustomView>
I've done this before with no issue so i know my mistake is subtle.
picker_dialog_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|center_horizontal"
android:id="#+id/pickerDialog_title"
android:text="HELLO WORLD!!"/>
<NumberPicker
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/pickerDialog_title"
android:id="#+id/pickerDialog_selector"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/pickerDialog_selector"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="5"
android:id="#+id/pickerDialog_cancel"
android:gravity="center"
android:text="Cancel"/>
<Button
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="5"
android:id="#+id/pickerDialog_set"
android:gravity="center"
android:text="Set"/>
</LinearLayout>
</merge>
During the Custom Class constructor, PickerDialog(Context context, AttributeSet attrs, int defStyle) i call:
LayoutInflater.from(context).inflate(R.layout.picker_dialog_layout, this);
And in the parent XML:
<com.company.simonaddicott.controlpanel_1.PickerDialog
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/pickerDialog" />
The view itself does show, and from using layout bound tools in developer tools i can identify that the ui elements are present, or at least the boundaries are present (see below)
What am i missing from this to make there UI elements appear like they should??
My mistake was that I extended the custom view by LinearLayout, but was using RelativeLayout positioning on the picker_dialog_layout.xml.
The elements inside the layout were not showing as they had no relative parent element to be positioned against
I'm doing a small game on Android 2.3.3 and I want to use openGLES. My question is whether I can GLSurfaceView and TextView, Button in the same layout. My layout xml file is as the following
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_horizontal" >
<com.ecnu.sei.manuzhang.nim.GameView
android:id="#+id/game_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="20dip"
android:layout_weight="1"
/>
<TextView
android:id="#+id/info_turn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginBottom="10dip"
/>
<Button
android:id="#+id/next_turn"
android:text="#string/button_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
/>
When GameView extends GLSurfaceView there will be errors java.lang.NoSuchMethodException: GameView(Context,AttributeSet) but GameView extends GLSurfaceView will do.
If not, is there a way to put those widgets together?
Thx in advance
When extending View or in this case GLSurfaceView you might need to place the correct constructor.
In your case you are missing this one:
public GameView(Context context, AttributeSet attrs)
You can check how it's done inside cocos2d-x with the Cocos2dxGLSurfaceView.
I'm starting to work on a game, which will have 3 views stacked on top of each other, basically a logo/ad row, a score row, and a game row. To start out with, I'm trying to have a class called Level_Score_Bar that uses an XML layout called score_bar_layout. Right now, my main XML code looks like this (Note, I've been editing this based off of the suggestions below, if I get it fixed, I'll stop editing it):
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent" android:layout_width="fill_parent">
<LinearLayout android:layout_height="wrap_content" android:orientation="horizontal" android:layout_width="fill_parent" android:id="#+id/Title_bar">
<ImageView android:layout_height="wrap_content" android:id="#+id/imageView1" android:layout_width="wrap_content" android:src="#drawable/icon"></ImageView>
<LinearLayout android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="#+id/linearLayout4">
</LinearLayout>
</LinearLayout>
<pearsonartphoto.AJEG.Level_Score_bar android:layout_height="fill_parent" android:layout_width="wrap_content" android:id="#+id/Score_Bar">
</pearsonartphoto.AJEG.Level_Score_bar>
<LinearLayout android:layout_height="wrap_content" android:layout_width="fill_parent" android:orientation="vertical" android:id="#+id/Game_Row">
<View android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="#+id/view3"></View>
</LinearLayout>
</LinearLayout>
Level_Score_bar.java looks like this:
public class Level_Score_bar extends RelativeLayout {
public Level_Score_bar(Context context, AttributeSet set, int defStyle) {
this(context,set);
}
public Level_Score_bar(Context context, AttributeSet set) {
super(context, set);
this.addView(View.inflate(context,R.layout.score_bar_layout,null));
Log.d(TAG,"Added view");
}
}
score_bar_layout.xml looks like this
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:text="#string/level" android:id="#+id/Level_text" android:layout_toRightOf="#+id/Level_text"></TextView>
<TextView android:id="#+id/Current_Level" android:layout_width="wrap_content" android:text="TextView" android:layout_height="wrap_content" android:layout_alignParentTop="true"></TextView>
<TextView android:layout_width="wrap_content" android:text="TextView" android:layout_height="wrap_content" android:layout_centerVertical="false" android:layout_centerHorizontal="true" android:id="#+id/Time_Left"></TextView>
<TextView android:id="#+id/Score_Label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:text="#string/score"></TextView>
<TextView android:layout_width="wrap_content" android:text="TextView" android:layout_height="wrap_content" android:layout_toRightOf="#+id/Score_Label" android:id="#+id/Score_Value"></TextView>
</RelativeLayout>
The problem is, I'm not seeing the view at all. I'm sure I'm missing something small, but I can't for the life of me figure it out. I've confirmed that the addView command is being called (Via a Log.d statement), but it's just not seeming to make any difference...
You're using the wrong constructor if I remember correctly, try this one instead:
public Level_Score_bar(Context context, AttributeSet set, int defStyle) {
super(context, set, defStyle);
// ...
}
[Edit]
Forgot to mention that since you're creating the view from XML, Android will call this constructor and it also enables you to use custom attributes if you so wish.
I am not sure of what your trying to do, please add some details :
name of xml files
your intents, what you want to do exactly with both.
But here a re some general advices :
Override other constructors as well in your custom view classes.
One argument parameters are meant to load class from code, with a second, attributeset argument, it will be used for xml constructs.
When you refer to a custom component (child) in xml (parent), use fully qualified class name of your child class. (that will load its child layout). fully qualified is package name + class name
Regards, Stéphane
Did you try to call ?
this.addView(View.inflate(getContext(),R.layout.score_bar_layout,null));
It's so simple, I feel like an idiot...
The problem was, the base layout was oriented horizontal. Simply adding a android:orientation="vertical" was enough to make it work right. Sigh. So, what was happening was the bar was being displayed, but below the screen (Actually, to the right of it), and so it was never visible. Changing the orientation to it's intended method fixed that, so the bar could be seen, and wasn't off screen.
I have a custom view like this
public class ButtonBar extends HorizontalScrollView
{
public View mButtonRows;
public ButtonBar(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mButtonRows = inflater.inflate(R.layout.toolbar, null);
// button click handling code goes here
addView(mButtonRows);
}
}
which is included in my main xml like this
<com.example.ButtonBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="#+id/pagecontent" />
and inflates an xml file like this:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/ButtonsRow"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/button1"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="0.3"
android:text="button1"
/>
<Button
android:id="#+id/button2"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="0.3"
android:text="button2"
/>
<Button
android:id="#+id/button3"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:layout_weight="0.3"
android:text="button3"
/>
</LinearLayout>
(It currently only has three buttons, but more are going to be needed in later versions, hence the HorizontalScrollView.)
Looking in hierarchyviewer, the custom view does seem to be screen wide, but the LinearLayout is only as wide as the buttons it contains (about 2/3 of the screen at the current button size), despite having the fill_parent width set; the buttons don't stretch. If I set the LinearLayout's background to #android:drawable/bottom_bar (which is a png the width of the screen), the buttons properly resize; I realise I could do the same thing by creating my own images to match, but I'd much rather do it without if possible.
What am I doing wrong?
ETA: if I change HorizontalScollView to ScrollView, it works fine. Do HSVs just not allow their children to "fill_parent"?
ETA2: Setting android:fillViewport="true" in the main xml fixed it!
Setting android:fillViewport="true" in the main xml fixed it!
If you change to this, for each of the buttons, does it work?
android:layout_width="0px"
android:layout_weight="1"