Does Android keep the view object hierarchy in memory? - android

When I create a button:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout layout = …;
Button btn = new Button(this);
btn.setText("My Button");
layout.addView(btn);
}
If I don't keep a strong reference to the btn, does Android keep the btn instance alive?
For example, does layout.getChildView(0) return the exact instance (btn)? or does Android create and return a new instance of Button and return it?
I'm not talking about subclassing, (e.g. class MyButton extends Button) which I think it's obvious it must be kept in memory, I am only asking about built-in view classes.

ViewGroup stores strong references on its children in an array. If you take a look into ViewGroup sources you find following field:
private View[] mChildren;
And when you call getChildAt you get an instance from this array:
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}

For any view group when you are adding any child view at a time it will not create new instance.

Related

Android Fragment and logical application design

This is more of a design question and how one would go about designing applications. I have been having fun with fragments, but one thing that doesn't make sense to me something like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
page = getArguments().getInt("someInt", 2);
Button btnOne = (Button) getView().findViewById(R.id.one);
btnOne.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
String currentText = getScreen().getText().toString();
currentText += "1";
getScreen().setText(currentText);
}
});
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.standard_calculator, container, false);
return view;
}
/** Return the screen TextView object for manipulation
* #return screen
*/
public TextView getScreen() {
return screen;
}
Screen title are private variables in the class and this isn't the whole class, but just the part that I need to help my question. There are going to be at least 15 or so buttons that manipulate the screen and it doesn't feel like good practice to and put them all in the onCreate method, I was wondering whether it would be better design to have them in another class that let the methods in the fragment be more specific to the life-cycle of the fragment, although one can say that the buttons are used by the fragment and therefore should be part of the class. Perhaps an "initialising" method is needed.
I was hoping someone might be able to direct me to some design patterns or logical way of thinking about application design, it is quite different.
Many thanks.
Putting them in the XML is less versatile than doing it in code. If you don't want to have XXX anonymous methods, you can make your own Fragment/Class implement View.onClickListener and implement the onClick method:
#Override
public void onClick(final View v) {
if ( v.getId() == R.id.btn_logout ){
// Do One Thing
} else if ( v.getId() == R.id.btn_about) {
// Do Something Else
} else if ( v.getId() == R.id.btn_shutdown) {
// Or Maybe do this :)
}
}
Then in your onViewCreated just assign each button with "this"
final someBtn = (Button) view.findViewById(R.id.btn_logout);
someBtn.setOnClickListener(this);
That can be cleaner looking than a bunch of anonymous methods and you know you have your click listeners in one place.
You don't have to initialize them all in the onCreate() method. In fact, you don't have to initialize them in java code at all. You could simply define them in xml and define an "onClick" property in your xml. The method that you set in "onClick" will be called for that button. It's one way to make your Activities cleaner.

Uniqueness of an ID for View object within a tree

From Android developer documentation, a statement reads as under:
An ID need not be unique throughout the entire tree, but it should be
unique within the part of the tree you are searching (which may often
be the entire tree, so it's best to be completely unique when
possible).
Please help me understand, with an example, what is meant by 'part of the tree you are searching'?
Example, given following:
<AnOutterLayout>
<Button android:id="#+id/my_button".../>
<ASuitableInnerLayout>
<Button android:id="#+id/my_button".../>
</ASuitableInnerLayout>
</AnOutterLayout>
If I have:
Button myButton = (Button) findViewById(R.id.my_button);
What will be search tree here?
Thanks!
The "part of the tree you are searching" is typically the children of the ViewGroup you're calling findViewById on.
In an Activity, the findViewById method is implemented like this (source):
public View findViewById(int id) {
return getWindow().findViewById(id);
}
Ok, so how does a Window implement findViewById (source)?
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
getDecorView returns a View - and all that the implementation of View does is return itself (if the views ID matches the one passed in), or null (source):
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}
It's much more interesting if we look at the implementation for a ViewGroup (source):
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
So you see a ViewGroup traverses its children searching for the ID you pass in. I'm not certain of the order of mChildren, but I suspect it'll be in the order you add the views to the hierarchy (just checked - addView(View child) does add views to the end of the mChildren list, where as addView(View child, int index) adds the view at index position in the list).
So, for your example, which button was returned would depend on which ViewGroup you were calling findViewById on.
If you called anOutterLayout.findViewById(R.id.my_button), you'd get the first button - as this is the first child element it comes across that contains that id.
If you called anInnerLayout.findViewById(R.id.my_button), you'd get the second button.
However, if your layout file looked like this:
<AnOutterLayout>
<ASuitableInnerLayout>
<Button android:id="#+id/my_button".../>
</ASuitableInnerLayout>
<Button android:id="#+id/my_button".../>
</AnOutterLayout>
Then anOutterLayout.findViewById(R.id.my_button) would actually return the button inside the inner layout - as this view was added to the hierarchy earlier, and is therefore earlier in the list of children for that view.
This assumes that views are added in the order they're present in the XML view hierarchy.
The button in the outter layer will be called first.
This post has a well-explained answer for your question
Are Android View id supposed to be unique?

How does findViewById initialise a view

I just wrote an answer for someone confused by findViewById and I realised that I have a gap in my understanding. This question is for knowledge and curiosity only.
Consider this:
button = (Button)findViewById(R.id.button);
findViewById returns an instance of View, which is then cast to the target class. All good so far.
To setup the view, findViewById constructs an AttributeSet from the parameters in the associated XML declaration which it passes to the constructor of View.
We then cast the View instance to Button.
How does the AttributeSet get passed in turn to the Button constructor?
[EDIT]
So I was the confused one :). The whole point is that when the layout is inflated, the view hierarchy already contains an instance of the view descendant class. findViewById simply returns a reference to it. Obvious when you think about it - doh..
findViewById does nothing. It just looks through view hierarchy and returns reference to a view with requested viewId. View is already created and exists. If you do not call findViewById for some view nothing changes.
Views are inflated by LayoutInflator. When you call setContentView xml layout is parsed and view hierarchy is created.
attributes passed to Button's constructor by LayoutInflater. check LayoutInflator source code.
I don't think findViewById() constructs or instantiates a View. It will search in View hierarchy of already inflated layout, for a View with matching id.This method works differently for a View and for a ViewGroup.
from Android Source code:
View.findViewById() returns the same View object if this view has the given id or null, it calls:
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}
ViewGroup.findViewById() iterates through child views and calls same method on these Views, it calls:
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}

Using a class to set up a view in android

I am writing an android application where the user can add and remove fields interactively. Each field the user add has some buttons, and value which the user should be able to interact with. I thought to create a subclass to handle the field I can add which will hold it's own onClickListener but I'm not sure how to do so.
Here is some pseudo code which should make my intention clear.
Say I have a class , vClass:
public class sClass extends View implements onClickListener{
this.setContextView(R.layout.vClass);//how do I do this in a correct way?
#Override
public void onClick(View v){ //add code here
}
}
and aClass which is the main class of the application.
public class aClass extends Activity implements onClickListener{
Button b;
LayoutInflater i;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.b =(Button)this.findViewById(R.id.btn);
b.setOnClickLister(this);
}
#Override
public void onClick(View v){
//this is what I have now to add View
final LinearLayout canvas =(LinearLayout)aClass.this.findViewById(R.id.main);
View cv =this.inflater.inflate(R.layout.counter, canvas, false);
canvas.addView(cv);
}
}
how can I use the vClass to add elements to the aClass.
Typing this is I thought about another solution.
If I keep track of the id's of all the views I have added (without the subcomponents) can I do something of that kind:
View vv = findViewById(id);
Button bb = vv.findViewByIf(R.id.xmlId);
where id is an id I have assigned to the view which I know and xmlId is a string I have specified in the xml file?
Thanks
Yotam
For solution, read the discussion below
IDs used in layouts are not necessarily unique, so i guess you should keep the added Views in an ArrayList, as
View cv =this.inflater.inflate(R.layout.counter, canvas, false);
this.viewList.add(cv);
canvas.addView(cv);
or you could declare an index member inside your sClass implementation, and store the added indices in an ArrayList:
private int index;
public sClass(final int index)
{
this.index = index;
}
public int getIndex()
{
return this.index;
}
#override
public boolean equals(Object obj)
{
return ((obj instanceof sClass) && (((sClass)obj).getIndex() == this.index));
}
Both ways you have access to the view you want.
The button that lays inside the view is accessible via the findViewById() method
Button bb = vv.findViewById(R.id.buttonId);
where R.id.buttonId was declared in the vv view's layout xml file, as follows:
<Button android:id="#+id/buttonId" [...] />

Add an array of buttons to a GridView in an Android application

I have an application that will have 5-15 buttons depending on what is available from a backend. How do I define the proper GridView layout files to include an array of buttons that will each have different text and other attributes? Each button will essentially add an item to a cart, so the onClick code will be the same except for the item it adds to the cart.
How can I define an array so I can add a variable number of buttons, but still reference each of them by a unique ID? I've seen examples of the arrays.xml, but they have created an array of strings that are pre-set. I need a way to create an object and not have the text defined in the layout or arrays xml file.
Update - Added info about adding to a GridView
I want to add this to a GridView, so calling the [addView method](http://developer.android.com/reference/android/widget/AdapterView.html#addView(android.view.View,%20int) results in an UnsupportedOperationException. I can do the following:
ImageButton b2 = new ImageButton(getApplicationContext());
b2.setBackgroundResource(R.drawable.img_3);
android.widget.LinearLayout container = (android.widget.LinearLayout) findViewById(R.id.lay);
container.addView(b2);
but that doesn't layout the buttons in a grid like I would like. Can this be done in a GridView?
In the following code, you should change the upper limits of the for to a variable.
public class MainActivity
extends Activity
implements View.OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TableLayout layout = new TableLayout (this);
layout.setLayoutParams( new TableLayout.LayoutParams(4,5) );
layout.setPadding(1,1,1,1);
for (int f=0; f<=13; f++) {
TableRow tr = new TableRow(this);
for (int c=0; c<=9; c++) {
Button b = new Button (this);
b.setText(""+f+c);
b.setTextSize(10.0f);
b.setTextColor(Color.rgb( 100, 200, 200));
b.setOnClickListener(this);
tr.addView(b, 30,30);
} // for
layout.addView(tr);
} // for
super.setContentView(layout);
} // ()
public void onClick(View view) {
((Button) view).setText("*");
((Button) view).setEnabled(false);
}
} // class
Here's a nice sample for you:
https://developer.android.com/guide/topics/ui/layout/gridview.html
You should just create buttons instead of imageviews in getView adapter method.
If you are using a GridView, or a ListView (etc), and are producing Views to populate them via the adapter getView(pos, convertView, viewGroup), you might encounter confusion (i did once).
If you decide to re-use the convertView parameter, you must reset everything inside of it. It is an old view being passed to you by the framework, in order to save the cost of inflating the layout. It is almost never associated with the position it was in the layout before.
class GridAdapter extends BaseAdapter // assigned to your GridView
{
public View getView(int position, View convertView, ViewGroup arg2) {
View view;
if (convertView==null)
{
view = getLayoutInflater().inflate(R.layout.gd_grid_cell, null);
}
else
{
// reusing this view saves inflate cost
// but you really have to restore everything within it to the state you want
view = convertView;
}
return view;
}
// other methods omitted (e.g. getCount, etc)
}
I think this represents one of those Android things where the concept is a little difficult to grasp at first, until you realize there's a significant optimization available within it (have to be nice to CPU on a little mobile device)

Categories

Resources