Inflate into "this"? - android

I've been creating apps without much XML, creating views programmatically. I'd like to switch to XML. So I wrote an XML file for a RelativeLayout, and I need to inflate it into an existing class (a subclass of RelativeLayout, of course) that has all the implementation logic.
How do I inflate into "this" in the constructor?
By the way, what's really the advantage of XML? When I create views in the code, I scale fonts and images and also move views around depending on the screen's size, orientation, aspect ratio, etc. With XML approach, I'd have to create a separate XML for all possible configurations...
Constructor code:
public OrderEditControl()
{
super(LmcActivity.W.getApplicationContext());
Resources res = LmcActivity.W.getResources();
setBackgroundColor(Color.TRANSPARENT);
headers = res.getStringArray(R.array.item_list_columns);
widths = new int[headers.length];
createLabels();
createButtons();
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_TOP);
lp.addRule(RIGHT_OF, labels[LabelType.CUSTOMER.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.FIND_CUSTOMER.ordinal()].getId());
customerView = new TextView(LmcActivity.W.getApplicationContext());
customerView.setTextColor(Color.BLACK);
customerView.setId(400);
customerView.setTypeface(Typeface.DEFAULT_BOLD);
customerView.setGravity(Gravity.CENTER_VERTICAL);
addView(customerView, lp);
lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_TOP, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(ALIGN_BOTTOM, labels[LabelType.SHIP_TYPE.ordinal()].getId());
lp.addRule(RIGHT_OF, labels[LabelType.SHIP_TYPE.ordinal()].getId());
shipSpinner = new Spinner(LmcActivity.W);
shipSpinner.setId(401);
shipSpinner.setAdapter(shipAdapter);
shipSpinner.setOnItemSelectedListener(this);
addView(shipSpinner, lp);
deliveryView = new EditText(LmcActivity.W.getApplicationContext());
deliveryView.setGravity(Gravity.CENTER_VERTICAL);
deliveryView.setSingleLine();
deliveryView.setId(402);
addView(deliveryView);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
lp.addRule(RIGHT_OF, labels[LabelType.COMMENTS.ordinal()].getId());
lp.addRule(LEFT_OF, buttons[ButtonType.ITEMS.ordinal()].getId());
lp.addRule(ALIGN_TOP, labels[LabelType.COMMENTS.ordinal()].getId());
commentView = new EditText(LmcActivity.W.getApplicationContext());
commentView.setGravity(Gravity.TOP);
commentView.setId(403);
addView(commentView, lp);
lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
lp.addRule(BELOW, commentView.getId());
itemList = new ListView(LmcActivity.W.getApplicationContext());
itemList.addHeaderView(createRow(null, null), null, false);
itemList.setOnItemClickListener(this);
itemList.setAdapter(itemAdapter);
itemList.setCacheColorHint(0);
itemList.setBackgroundColor(Color.TRANSPARENT);
itemList.setId(404);
addView(itemList, lp);
lays[0] = new LayParm(false);
lays[1] = new LayParm(true);
}
/** create the view's buttons */
private void createButtons()
{
for (int i = 0; i < N_BUT; ++i)
{
Button but = i == ButtonType.ITEMS.ordinal() ?
new TextGlassButton(2.4f, LmcActivity.W.getResources().getString(R.string.items), Color.WHITE) :
new EffGlassButton(1.2f, butEffects[i]);
but.setId(BUT_ID + i);
but.setOnClickListener(this);
buttons[i] = but;
if (i == ButtonType.DATE.ordinal())
addView(but);
else
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i < 2)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, BUT_ID + i - 2);
if (i % 2 == 0)
lp.addRule(ALIGN_PARENT_RIGHT);
else
lp.addRule(LEFT_OF, BUT_ID + i - 1);
addView(but, lp);
}
}
}
/** create text labels */
private void createLabels()
{
Paint paint = AFDraw.W.textPaint;
paint.setTextSize(Universe.TEXT_SIZE);
paint.setTypeface(LmcActivity.W.defaultTypeface);
String[] titles = LmcActivity.W.getResources().getStringArray(R.array.order_labels);
for (int i = 0; i < titles.length; ++i)
{
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(ALIGN_PARENT_LEFT);
if (i == 0)
lp.addRule(ALIGN_PARENT_TOP);
else
lp.addRule(BELOW, LABEL_ID + i - 1);
TextView tv = new TextView(LmcActivity.W.getApplicationContext());
tv.setText(titles[i]);
tv.setTextColor(Color.BLACK);
tv.setId(LABEL_ID + i);
tv.setTypeface(LmcActivity.W.defaultTypeface);
tv.setGravity(Gravity.CENTER_VERTICAL);
labels[i] = tv;
addView(tv, lp);
labelWidth = Math.max(labelWidth, paint.measureText(titles[i]));
}
labelWidth += Universe.TEXT_SIZE * 0.5f;
dateWidth = paint.measureText("00/00/00") + Universe.TEXT_SIZE * 1.5f;
}

#scriptocalypse is generally right, but subclassing some layouts and inflating custom layout to this class helps to separate different abstractions. There are so many bad tutorials, in which everything is done in the Activity. I see that the world's new comming programmers will code only crap looking applications.
With custom layout you can do in Activity only such a thing:
medicineView.putMedicine(medicineList);
instead of all crappy adapter creations and looking for views...
Firstly you should create some view for your custom View:
<RelativeLayout ...>
<!-- You put all your views here -->
</RelativeLayout>
Secondly if you are sattisfied with your view, you should change the root to merge tag:
<merge ...>
<!-- You put all your views here -->
</merge>
This is very important. We begin design with RelativeLayout tags in order to IDE know how to draw layouts, and how to do completions. But if we leave it as it is, we will end up in two nested RelativeLayouts it will be something like that in the end:
<RelativeLayout ...> <!-- That is your class -->
<RelativeLayout ...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</RelativeLayout>
</RelativeLayout>
If you change your layout to "merge" then it will look like this:
<RelativeLayout ...> <!-- That is your class -->
<merge...> <!-- This is inflated from layout -->
<!-- You put all your views here -->
</merge>
</RelativeLayout>
and will be merged to its root:
<RelativeLayout ...> <!-- That is your class, merged with layout -->
<!-- You put all your views here -->
</RelativeLayout>
At the end you must subclass demanded View or ViewGroup:
public class CustomView extends RelativeLayout {
public CustomView(Context context) {
super(context);
initialize();
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
private void initialize() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.id.your_layout, this, true);
// find your views, set handlers etc.
}
}
Usage
Just like #scriptocalypse already said. In another layout you use this like that:
<SomeLayout>
<com.foo.CustomView>
</SomeLayout>

First, to answer your main question:
you would not want to inflate an XML RelativeLayout into your RelativeLayout class. You'd extend RelativeLayout and then declare an instance of your RelativeLayout in an XML file, like so:
// com.foo.MyRelativeLayout.java
public class MyRelativeLayout extends RelativeLayout{
/**
* Implement MyRelativeLayout
*/
}
and...
// layout_example.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Put More Views in here... -->
<TextView
android:id="#+id/customer_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/customer_name_placeholder" />
<!-- and on... -->
</com.foo.MyRelativeLayout>
But more to the point, if you're using the XML to lay out your file, you don't need any of those instantiations or .addRule() method invocations inside your MyRelativeLayout file because you've done it declaratively in XML instead.
To answer your second question of "Why do you want to use XML anyway?"
There are many reasons. Maybe these apply to you, and maybe they don't, but they're ones that I can think of fairly easily that have been relevant in my work.
You don't actually have to create a new layout file for every separate screen size or use case. For the most part, a single layout file will suffice for most screens. You might find that you will have size/resolution/orientation specific dimens.xml or style.xml files, but unless you want a dramatically different arrangement for your different possibilities then the layouts themselves don't repeat themselves too often.
You can use a visual editor. This is important if you're working in teams, and your teammates don't like to or want to use only Java to lay out their screens. While I and others gladly create View and Layout subclasses to fit our needs, I know of literally nobody who prefers to use Java as their primary layout language. Finding people who will work with you (or a job where everyone else uses the XML tools) could be challenging.
If you're creating tools for other people to use (like the above-mentioned folks who prefer XML) you can actually give them custom attributes to work with, that make positioning and layout more powerful. These attributes could be hard-coded in the XML, or they could be references to any of the other Android resources (drawable/string/color/integer/boolean/etc...). As a contrived example, but one based on your code, you could give your users the ability to specify a number of buttons to create rather than rely on the N_BUT variable. You could give it a default value, but offer users a way to change it in XML.
Here is an example:
// somelayout.xml
<?xml version="1.0" encoding="utf-8"?>
<com.foo.MyRelativeLayout
xmlns:param="http://schemas.android.com/apk/res-auto"
style="#style/MyRelativeLayoutStyle"
param:numberOfButtons="3">
</com.foo.MyRelativeLayout>
and in a different file...
//attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyRelativeLayout">
<attr name="numberOfButtons" format="reference|integer" />
</declare-styleable>
</resources>
and in your MyRelativeLayout, you access those attributes from the AttributeSet in its constructor (the one called by Android when it uses XML to create a layout).
Using the style="#style/foo" syntax can allow you to create "classes" of styles that can apply to all kinds of views without actually making a View subclass. Let's say you know that you always want to have a set of parameters that hold true for all your Button elements but don't want to subclass Button.
For example:
// styles.xml
<style name="BaseButton">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:background">#drawable/bg_common_button</item>
<item name="android:textColor">#color/white</item>
<item name="android:textSize">#dimens/base_button_text_size</item>
<!-- ^^ that dimen value could vary from screen size to screen size, but the style likely won't -->
</style>
// button_layout.xml
<Button
android:id="#+id/styled_button"
style="#style/BaseButton" /> <!-- and you're done -->
// some_other_layout.xml
<LinearLayout
style="#style/BaseLinearLayout">
<Button style="#style/BaseButton" android:text="Button1" />
<Button style="#style/BaseButton" android:text="Button2" />
<Button style="#style/BaseButton" android:text="Button3" />
</LinearLayout>
If you would like to instantiate that button using code, then you can use the LayoutInflater to inflate that specific button's layout and use that wherever you want. In fact, you can create all manner of components in XML and then inflate them at runtime.
LayoutInflater inflater = LayoutInflater.from(YourActivity.this);
Button theInflatedButton = inflater.inflate(R.layout.button_layout.xml, null);
Of course, the canonical example is ListViews and the items that you wish to populate them. You'd create a listview item layout xml and then inflate that whenever your Adapter is in need of a new convertView instance.

Related

Dynamic Textview scrollbar not showing

I have created a textview dynamically and want to make it scrollable.
final RelativeLayout.LayoutParams params = parseLayoutParams(
frameMargins, context);
tv.setText(Utility.getpropertyString(controls.getText()));
final String textColor = Utility.getpropertyString(controls.getTextcolor());
tv.setTextColor(Color.parseColor(textColor));
tv.setTextSize(12);
tv.setGravity(Gravity.CENTER_VERTICAL);
tv.setTextSize(tSize);
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setMaxLines(controls.getMaxlines());
tv.setTag(controls.getTagId());
tv.setLayoutParams(params);
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setVisibility(controls.getVisibility());
tv.setVerticalScrollBarEnabled(isScrollable);
tv.setScroller(new Scroller(context));
tv.setMovementMethod(new ScrollingMovementMethod());
tv.setScrollBarFadeDuration(0);
But I am not able to see scrollbar in the textview niether when not scrolling nor when we scroll it. Please help!
From Api 21, the View scrollbar visibility is only reserved for xml layouts because an important function called initializaScrollBars was removed due to an issue while passing TypeArray variable as a parameter.
So, to accomplish what you need programmatically, you can do it like this
Create an xml layout called scrolltextview.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
Now, to create it programmatically
TextView tv = (TextView) LayoutInflater.from(this).inflate(R.layout.scrolltextview, null, false);
// now decorate it as your needs
tv.setText(m);
tv.setTextColor(Color.BLUE);
tv.setTextSize(23);
...
tv.setMovementMethod(new ScrollingMovementMethod());
// this is needed only if you want to show scrollbar also when text is not scrolling
tv.setScrollBarFadeDuration(0);
// thecontainer = the layout you want to add your new textview
thecontainer.addView(tv);
As Ferran noted, initializeScrollBars() has been removed. See here for the bug report and the rationale for its removal. As far as I can tell, there is no other strictly programmatic way to specify scollbars for a view. All paths go through XML. :-(
I think that Ferran's answer is a good way to go: it works, is easy to understand and should be easy to document. There are, however, other ways to programatically create a TextView with scrollbars with a slight assist from styles.
For API 21 and above
Define a style called "ViewWithScrollBars" as follows:
<style name="ViewWithScrollbars">
<item name="android:scrollbars">vertical</item>
</style>
We can now use the four-argument constructor for TextView to apply the style.
TextView tv = new TextView(this, null, 0, R.style.ViewWithScrollbars);
This method will create a TextView with scrollbars. There is at least one caveat however. When a TextView is created with a single argument
new TextView(Context)
The constructor telescopes through other constructors that add additional arguments. One of these constructors is defined as follows:
public TextView(Context context, #Nullable AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
The third argument com.android.internal.R.attr.textViewStyle is an Android internal style that will pick up a default textViewStyle from the theme. The call I suggest uses zero for the third argument, so any textViewStyle defined in the theme will not be applied.
A reasonable fix for this might be to do the following:
tv = new TextView(this, null, android.R.attr.textViewStyle, R.style.ViewWithScrollbars);
Unfortunately, if the third argument (defStyleAttr) is defined in the theme, then the fourth argument (defStyleRes) is not used. As a result, the scrollbars will not appear.
If you use textViewStyle then you will have to make adjustments or just use the following approach.
For all APIs
Using the style "ViewWithScrollBars" from above, we can define a ContextThemeWrapper that will install scrollbars into the theme that will be used to create the TextView.
ContextThemeWrapper ctw = new ContextThemeWrapper(this, R.style.ViewWithScrollbars); // "this" is the Activity
tv = new TextView(ctw);
I refer you to an article by Chris Banes entitled "Theme vs Style" that explains how theme overlays work.
The following puts all this together.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = new TextView(this);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// This will actually work for API 21 and above.
ContextThemeWrapper ctw = new ContextThemeWrapper(this, R.style.ViewWithScrollbars);
tv = new TextView(ctw);
} else {
tv = new TextView(this, null, 0, R.style.ViewWithScrollbars);
}
tv.setText(R.string.lorem);
tv.setTextColor(Color.parseColor("red"));
tv.setTextSize(12);
tv.setGravity(Gravity.CENTER_VERTICAL);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setMaxLines(7);
tv.setTag(View.generateViewId());
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
tv.setLayoutParams(params);
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setVisibility(View.VISIBLE);
tv.setVerticalScrollBarEnabled(true);
tv.setScroller(new Scroller(this));
tv.setMovementMethod(new ScrollingMovementMethod());
tv.setScrollBarFadeDuration(0);
((RelativeLayout) findViewById(R.id.layout)).addView(tv);
}
}

Custom Android Components with XML children

I'm an iOS developer that is relatively new with Android and I'm trying to create reusable components that separate the controller logic and View definition. I want a pattern similar to iOS IBOutlets where you define a class that can be used with different xib files or storyboard layouts.
For example, say I want to create a custom progress bar component. I want the user to be able to provide the required children and design and style them separately in xml.
Here's some pseudo code of what I'm trying to accomplish:
layout.xml
<FrameLayout>
<!-- A vertical progress bar -->
<CustomProgressBar>
<LinearLayout orientation="vertical">
<ImageView id="#+id/bar" src="#drawable/bar_image" />
<TextView id="#+id/label" text="Bar 1"/>
</LinearLayout>
</CustomProgressBar>
<!-- A horizontal bar using same controller class -->
<CustomProgressBar>
<LinearLayout orientation="horizontal">
<ImageView src="#drawable/background_image" />
<ImageView id="#+id/bar" src="#drawable/bar_image" />
<TextView id="#+id/label" text="Bar 1"/>
</LinearLayout>
</CustomProgressBar>
<FrameLayout>
Then my custom class might look like:
public class CustomProgressBar extends FrameLayout {
private ImageView bar;
private TextView label;
.
.
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// Store the references of the components
bar = (ImageView) findViewById(R.id.bar);
label = (TextView) findViewById(R.id.label);
// Now I should be able to write general code for this component
// using the reference components I found
}
}
In the above example, the developer is instantiating 2 CustomProgressBars in the same xml file. But each bar's layout is drastically different (child display tree and orientation is different). The obvious problem here is that the xml won't compile because I'm using the same ids for different views in the xml. To fix the compilation issue, I could change the id names, but then the controller class won't know how to find references to those children.
Is there another way to approach this problem?
There are methods in ViewGroup called getChildCount and getChildAt which will allow you to pull the child views by index.
So what you will have to do is something like:
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getProgressViews(this);
}
private ImageView bar;
private TextView label;
private void getProgressViews(ViewGroup viewGroup) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
if (view.getClass().getSuperclass() == ViewGroup.class) {
getProgressViews((ViewGroup) view);
}
if (view instanceof ImageView) {
bar = (ImageView) view;
}
if (view instanceof TextView) {
label = (TextView) view;
}
}
}
If you are looking to use these to also manually draw them to screen you need to override the onLayout method.

Setting views dynamically with LayoutParams.addRule setting to the last position of the view

I'm dynamically adding views to a relative layout and programatically defining them. The views can be moved around the screen so their position is changing.
When I try to set a view (button2) to sit below another view (button1), button2 gets placed in the old location of button1 (the default location of where views get added before moved). I've linked images to hopefully convey this better.
This is the Original Layout
Layout after Button2 is re-positioned
I have a background LinkedList keeping track of all view changes and view attributes for the layout if that makes a difference.
Here are the code functions:
How i'm re-positioning Button1:
Buttons b = (Buttons) viewIndex;
positioningLayout = new RelativeLayout.LayoutParams(b.getLayoutParams());
positioningLayout.addRule(RelativeLayout.CENTER_VERTICAL);
b.setLayoutParams(positioningLayout);
baseLayout.addView(b);
Repositioning views below another view Code fragment:
Buttons b = (Buttons) viewIndex;
positioningLayout = new RelativeLayout.LayoutParams(b.getLayoutParams());
positioningLayout.addRule(RelativeLayout.BELOW, viewIdFromList.intValue());
b.setLayoutParams(positioningLayout);
b.invalidate();
How I'm adding the views to the layout.
uiList.addView(new Buttons(this), "BUTTON");
setDialogView(uiList.getLast());
showDialog(SET_ID);
reloadUI();
setDialogView is just passing the view to the Dialog SET_ID so that I can manually assign an ID to the view (for testing).
reloadUI() just finds the last view added to the background LinkedList and adds it to the relativeLayout using .addView;
If you require more code please let me know. Am I missing a call to update the view layouts after making a change to the relativeLayout child views? It seems like the view is getting re-positioned visually but the actual LayoutParams are not updating so when you set Button2 to Button1 it's getting the old position.
Is there a way to force a relative layout view re-position?
I think you should try simple solution - replace b.invalidate() with b.requestLayout(). For more background check this link. I didn't test it but I hope it will work. So the code should look like:
Buttons b = (Buttons) viewIndex;
positioningLayout = new RelativeLayout.LayoutParams(b.getLayoutParams());
positioningLayout.addRule(RelativeLayout.BELOW, viewIdFromList.intValue());
b.setLayoutParams(positioningLayout);
b.requestLayout();
Or maybe you can simplify this to:
Buttons b = (Buttons) viewIndex;
((RelativeLayout.LayoutParams) b.getLayoutParams()).addRule(RelativeLayout.BELOW, viewIdFromList.intValue());
b.requestLayout();
You seem to be in the right track but is doing a small mistake. Without more code it is quite difficult to provide the actual solution. But I will try to point out the mistake.
See, to relatively position items programmatically in a RelativeLayout you must assign unique ids to each of them.
Like,
Button b = new Button(this);
b.setId(1);
Button c = new Button(this);
c.setId(2);
Each element should have unique ids.
Now if you want to place the Button b vertically in the center,
layout.addRule(RelativeLayout.CENTER_VERTICAL);
b.setLayoutParams(layout);
Now if you want to place Button c below it,
layout.addRule(RelativeLayout.BELOW, b.getId());
layout.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
b.setLayoutparams(layout);
This will certainly place the button c below button b. As you said you can use a any data structure of your choice to keep track of the ids of each elements.
This is only a detail example on how you move the button correctly. So first, create a unique id for the View, i.e. create a new XML resource file in res/values/, and name it ids.xml:
And the content of ids.xml is:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="button_1" type="id"/>
<item name="button_2" type="id"/>
</resources>
We have to set an id to your main layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/layout_main"> <!-- the id -->
<!-- content of this layout -->
</RelativeLayout>
Then, let's create the Buttons:
RelativeLayout relativeLayout = (RelativeLayout) findViewById(R.id.layout_main);
final Button button1 = new Button(this);
button1.setId(R.id.button_1);
button1.setText("BUTTON 1");
button1.setTextColor(Color.BLACK);
final Button button2 = new Button(this);
button2.setId(R.id.button_2);
button2.setText("BUTTON 2");
button2.setTextColor(Color.BLACK);
relativeLayout.addView(button1, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT));
relativeLayout.addView(button2, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT));
RelativeLayout.LayoutParams params1 = (RelativeLayout.LayoutParams) button1.getLayoutParams();
params1.addRule(RelativeLayout.CENTER_IN_PARENT); // set to the center of screen
button1.setLayoutParams(params1);
final RelativeLayout.LayoutParams params2 = (RelativeLayout.LayoutParams) button2.getLayoutParams();
params2.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params2.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
button2.setLayoutParams(params2);
button2.setOnClickListener(new View.OnClickListener() {
#SuppressLint("NewApi")
#Override
public void onClick(View v) {
/* We need to remove the existing rules and update it!
* For API 16 and earlier, set the rule to 0, e.g. --> layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
* For API 17 and higher, call layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
*/
int api = android.os.Build.VERSION.SDK_INT;
if (api >= Build.VERSION_CODES.JELLY_BEAN_MR1) { // API level 17
params2.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
params2.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
}else if (api < Build.VERSION_CODES.JELLY_BEAN_MR1) {
params2.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0);
params2.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
}
params2.addRule(RelativeLayout.CENTER_IN_PARENT);
params2.addRule(RelativeLayout.BELOW, R.id.button_1); // place below button1
button2.setLayoutParams(params2);
}
});
So once you click on button2, it will move to below button1.

Android: Custom View with object reference from attrs.xml, always null

I'm trying to setup a relationship hierarchy between objects. Every object has a parent of the same type as itself, or null.
I have a main.xml that contains some of these:
<com.morsetable.MorseKey
android:id="#+id/bi"
android:layout_weight="1"
custom:code=".."
custom:parentKey="#id/be"
android:text="#string/i" />
a res/values/attrs.xml that contains one of these:
<declare-styleable name="MorseKey">
<attr name="code" format="string"/>
<attr name="parentKey" format="reference"/>
</declare-styleable>
and a class (that is not my activity) that contains this:
public class MorseKey extends Button {
public MorseKey(Context context, AttributeSet attrs) {
super(context, attrs);
initMorseKey(attrs);
}
private void initMorseKey(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.MorseKey);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.MorseKey_code:
code = a.getString(attr);
break;
case R.styleable.MorseKey_parentKey:
parent = (MorseKey)findViewById(a.getResourceId(attr, -1));
//parent = (MorseKey)findViewById(R.id.be);
Log.d("parent, N:", ""+parent+","+N);
break;
}
}
a.recycle();
}
private MorseKey parent;
private String code;
}
This isn't working. Every MorseKey instance reports N == 2 (good) and parent == null (bad). More, parent == null even when I explicitly try setting it to some arbitrary value (see comment). I have also tried custom:parentKey="#+id/be" (with the plus sign) but that didn't work either. What am I doing wrong?
If your MorseKey class is in a separate java file, which I assume is the case from your statment "a class (that is not my activity)". Then I believe the problem is in your use of findViewById(). findViewById() will look for a resource within the MorseKey view itself rather than the main.xml file.
Maybe try getting the parent of the MorseKey instance and calling parent.findViewById().
case R.styleable.MorseKey_parentKey:
parent = this.getParent().findViewById(a.getResourceId(attr, -1));
Though this will only work if your MorseKey parent and child are in the same layout.
<LinearLayout ...>
<MorseKey ..../><!-- parent -->
<MorseKey ..../><!-- child -->
</LinearLayout>
But it would be quite difficult to find the view if your layout is something like this with the parent and child in separate layouts.
<LinearLayout ...>
<MorseKey ..../><!-- parent -->
</LinearLayout>
<LinearLayout ...>
<MorseKey ..../><!-- child -->
</LinearLayout>

Custom Android control with children

I'm trying to create a custom Android control that contains a LinearLayout. You can think of it as an extended LinearLayout with fancy borders, a background, an image on the left...
I could do it all in XML (works great) but since I have dozens of occurences in my app it's getting hard to maintain. I thought it would be nicer to have something like this:
/* Main.xml */
<MyFancyLayout>
<TextView /> /* what goes inside my control's linear layout */
</MyfancyLayout>
How would you approach this? I'd like to avoid re-writing the whole linear layout onMeasure / onLayout methods. This is what I have for the moment:
/* MyFancyLayout.xml */
<TableLayout>
<ImageView />
<LinearLayout id="container" /> /* where I want the real content to go */
</TableLayout>
and
/* MyFancyLayout.java */
public class MyFancyLayout extends LinearLayout
{
public MyFancyLayout(Context context) {
super(context);
View.inflate(context, R.layout.my_fancy_layout, this);
}
}
How would you go about inserting the user-specified content (the TextView in main.xml) in the right place (id=container)?
Cheers!
Romain
----- edit -------
Still no luck on this, so I changed my design to use a simpler layout and decided to live with a bit of repeated XML. Still very interested in anyone knows how to do this though!
This exact question bugged me for some time already but it's only now that I've solved it.
From a first glance, the problem lies in the fact that a declarative content (TextView in Your case) is instantiated sometime after ctor (where we're usually inflating our layouts), so it's too early have both declarative and template content at hand to push the former inside the latter.
I've found one such place where we can manipulate the both: it's a onFinishInflate() method. Here's how it goes in my case:
#Override
protected void onFinishInflate() {
int index = getChildCount();
// Collect children declared in XML.
View[] children = new View[index];
while(--index >= 0) {
children[index] = getChildAt(index);
}
// Pressumably, wipe out existing content (still holding reference to it).
this.detachAllViewsFromParent();
// Inflate new "template".
final View template = LayoutInflater.from(getContext())
.inflate(R.layout.labeled_layout, this, true);
// Obtain reference to a new container within "template".
final ViewGroup vg = (ViewGroup)template.findViewById(R.id.layout);
index = children.length;
// Push declared children into new container.
while(--index >= 0) {
vg.addView(children[index]);
}
// They suggest to call it no matter what.
super.onFinishInflate();
}
A labeled_layout.xml referenced above is not unlike something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation ="vertical"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:layout_marginBottom ="3dip"
android:layout_weight ="1"
android:duplicateParentState ="true">
<TextView android:id ="#+id/label"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:singleLine ="true"
android:textAppearance ="?android:attr/textAppearanceMedium"
android:fadingEdge ="horizontal"
android:duplicateParentState="true" />
<LinearLayout
android:id ="#+id/layout"
android:layout_width ="fill_parent"
android:layout_height ="wrap_content"
android:layout_marginLeft ="8dip"
android:layout_marginTop ="3dip"
android:duplicateParentState="true" />
</LinearLayout>
Now (still omitting some details) elsewhere we might use it like this:
<com.example.widget.LabeledLayout
android:layout_width ="fill_parent"
android:layout_height ="wrap_content">
<!-- example content -->
</com.example.widget.LabeledLayout>
This approach saves me a lot of code! :)
As esteewhy explains, just swap the xml-defined contents into where you want them internally in your own layout, in onFinishInflate(). Example:
I take the contents that I specify in the xml:
<se.jog.custom.ui.Badge ... >
<ImageView ... />
<TextView ... />
</se.jog.custom.ui.Badge>
... and move them to my internal LinearLayout called contents where I want them to be:
public class Badge extends LinearLayout {
//...
private LinearLayout badge;
private LinearLayout contents;
// This way children can be added from xml.
#Override
protected void onFinishInflate() {
View[] children = detachChildren(); // gets and removes children from parent
//...
badge = (LinearLayout) layoutInflater.inflate(R.layout.badge, this);
contents = (LinearLayout) badge.findViewById(R.id.badge_contents);
for (int i = 0; i < children.length; i++)
addView(children[i]); //overridden, se below.
//...
super.onFinishInflate();
}
// This way children can be added from other code as well.
#Override
public void addView(View child) {
contents.addView(child);
}
Combined with custom XML attributes things gets very maintainable.
You can create your MyFancyLayout class by extending LinearLayout. Add the three constructors which call a method ("initialize" in this case) to set up the rest of the Views:
public MyFancyLayout(Context context) {
super(context);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public MyFancyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
Within initialize, you do anything you need to to add the extra views. You can get the LayoutInflater and inflate another layout:
final LayoutInflater inflator = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflator.inflate(R.layout.somecommonlayout, this);
Or you can create Views in code and add them:
ImageView someImageView = new ImageView(getContext());
someImageView.setImageDrawable(myDrawable);
someImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(someImageView);
If you're going to use the Context a lot, you can store a reference to it in your constructors and use that rather than getContext() to save a little overhead.
just use something like this:
<org.myprogram.MyFancyLayout>
...
</org.myprogram.MyFancyLayout>
Useful link - http://www.anddev.org/creating_custom_views_-_the_togglebutton-t310.html

Categories

Resources