Exploring the view tree in Android - android

It's maybe a bit abstract but I'd like to know if some of you have a decent solution to that problem:
I have a complicated layout and I need to find ALL the instances of a certain type of view.
I have few solutions but I find none of them perfect and I'd like to know if there is another way or a way to improve them.
MY FIRST OPTION
We can iterate in the view tree with getChildCount() and getChildAt() and then check with instanceof like in lots of SO answers.
for (int i = 0; i<parentContainer.getChildCount(); i++){
View child = getChildAt(i);
if (child instanceof BlaBla){
// Do something wonderful
}
}
It is highly inefficient because I have these instances in many places and in particular in nested places so I need to make this method recursive.
MY SECOND OPTION
It would be to work with dynamic tags or ids and use findViewById or findViewWithTag. But the issue is that it makes something more to configure and as always it makes the software more complicated.
So my question is: how can I do a complete search in the view tree in
order to find all instances of a component without doing the search
myself (because it would be probably be very inefficient)? Is that
somehow possible?

So, I'm not sure second option is possible as in this case you'll need to create this views in runtime and assign some generated ID with some bit mask to recognize them later. If to create your views from layout you will end up with traversing tree view and assigning these special IDs which is pretty much accends to 1st option.
In my project I also have to dynamically apply colors to some views and I do it without recursion. Pattern is following:
ArrayList<View> views = new ArrayList<>();
views.add(getWindow().getDecorView());
do {
View v = views.remove(0);
if (v instanceof ViewGroup) {
ViewGroup group = (ViewGroup) v;
for (int i = 0; i < group.getChildCount(); i++) {
views.add(group.getChildAt(i));
}
}
if (v instanceof MyCustomView) {
//do whatever you need here
}
} while(!views.isEmpty());
So you get rid of using recursion and replace it with own stack and iteration through it. This solution quite efficient especially if you can skip things like ListView, RecyclerView.

Related

Performance: How to select all ListView elements programmatically in efficient way?

In my application I use the ListView container with quite a lot of data. In order to provide the possiblity of item management, I use the MultipleChoiceMode to let user choose several items and copy/delete them.
I do it by setting the:
listView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(listener);
Where listener implements a AbsListView.MultiChoiceModeListener.
Since it is a useful feature, I decided to add the possibility to select/deselect all list elements at once.
Currently it is done with following code (I simplified the code to show the main concept):
private void selectAll() {
for(int i = 0; i < listView.getCount(); i++) {
listView.setItemChecked(i, true);
}
}
Unfortunatelly this solution's performance is strictly item amount-dependent.
Could you, please advise me how to do it better/more scallable?
For, let's say 15000 elements, current solution is extremely laggy.
PS. I can't change the container type (e.g. to RecyclerView)... :(

Remove selected children from parent TableView

I have a TableView that I programmaticly add in rows to using a simple loop like the follows
LayoutInflater inflater = LayoutInflater.from(getActivity());
for (int i = 0; i < dataList.size(); ++i)
{
DataEntry dataEntry = dataList.valueAt(i);
// Add the row
TableRow row = (TableRow)inflater.inflate(R.layout.data_row, _table, false);
_table.addView(row);
row.setTag("dataRow");
// Code that looks up the 'column' views and sets their value to the dataEntry
}
This is working great and populates the list just fine. But when I am changing data sets I want to clear out all the added rows except the header (first) row and then add in a new set of rows (ie run the code above). So I have this little bit of code:
final int childCount = _table.getChildCount();
for (int i = 1; i < childCount; ++i)
{
View view = _table.getChildAt(i);
_table.removeView(view);
}
This is the most simple version of this loop I could do. I was originally checking for null views because they showed up on resuming the task. I would then check to see if the tag for the view was == "dataRow" and only remove the data rows. But this all gives me the same result. It will skip the header (I am starting at 1 instead of 0) and remove the next row and then every other row from there on down. If I run this loop enough times it Will clear the list but adding a while(_table.getChildCount()>1) just seems like I am clearly just doing this wrong. _table.removeAllChildren() will not work as the header is then removed as well (And it likes to rip apart view hierarchies which is also not what I want done).
I am just not seeing what it is I am doing wrong. I am hoping a second (or second hundred) set of eyes may see the flaw in my thinking.
And I figured it out around 4min after I asked this.
I am looping through the children and removing them. The parent is then sliding all of the entries down. Changing the for loop to the following works (and is ironicly turning this for loop into a while (>1) I thought seemed like a bad idea.
for (int i = 1; i < childCount; ++i)
_table.removeView(_table.getChildAt(1));
This makes sense now that I look at it.. I should probably remove from the bottom up for a bit cleaner code and should have remembered this is the proper way to remove items from a linked list which may very well be what the children list is.

Bunch of questions about View ids

i would like to ask a lot of questions about how this whole id system works in android. I looked up the View documentation, but the description was too shallow for my taste.
Is there a pattern, how the IDE (Eclipse/Netbeans) generates the ids
when i use android:id="#+id/..."? Or is it completely random?
If i set ids programmatically, then will it be found by the Context
classes findViewById() function?
If the answer for the previous question is yes, then if i want to
create a large amount of Views, but i want them to have distinct ids
for later identification, then wich one is better to use? (To answer
this question, it would be really useful to know the answer for the
first two)
For example generating random ids in the largest possible range:
Random random = new Random();
for(int i=0; i<100; i++)
{
View view = new View(someContext);
view.setId(random.nextInt(Integer.MAX_VALUE));
}
Or setting the ids in some sort of order, for example:
final int addToId = 5670;
for(int i=0; i<100; i++)
{
View view = new View(someContext);
view.setId(i+addToId);
}
Also i would like to know, what happens, when you use a
LayoutInflater for example to populate a ListView using a
pre-defined xml layout for every item in the list. Then you get your
sub-views in the getView() function by the findViewById(). So i
assume, that all the identical Views across your listitems have the
same id. If so, then is it a good practice to use the tag
attribute to distinguish the items in an inflated layout?
Any clear explanation for these question would be highly appreciated!
#+id/.... creates an id value that lives within the applications namespace. Contrast this with #android:id/.... which lives in the android namespace.
When you set the id in code and add the view element to the layout it will then become available to access through the code. You won't be able to reference it from the xml
Not sure you want to be using random to generate your ids? think sequential would be better but even then what is the point of a random id? How do you know which view you are referring to?
Definitely use the tag option and look to use the ViewHolder pattern for smoother list scrolling. You could add the id to the view holder class if you need access to it but it would be available anyway through the data set being used to populate the list. A quick search will give you plenty of examples for this.

How to determine which RelativeLayout is the parent of a button?

Lets say that I have two different types of RelativeLayouts. That is to say these 2 RelativeLayouts differ because they contain different views. One might have textviews, an image view etc and the other might have also have some textviews which mean something completely different than the other set of textviews in the other relativelayout. Lets say however that both have a Submit Button. So to make my point more clear here is some code:
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
ViewParent parent = v.getParent();
if(parent instanceof CustomRelativeLayout){
CustomRelativeLayout aRelativeLayout = (CustomRelativeLayout)parent;
for(int childrenIndex = 0; childrenIndex < r.getChildCount(); childrenIndex++){
View childView = aRelativeLayout.getChildAt(childrenIndex);
}
}
}
The problem here is is that we don't know which CustomRelativeLayout aRelativeLayout is referring to. Depending on which CustomRelativeLayout it is will depend on what specific childViews i will want to search for and what logic I implement relevant to those views. I would like to be able to have a switch statement to check which type of CustomRelativeLayout is the parent.
So the questions that i would like to ask is:
how do i get more information about which instance of CustomRelativeLayout refers to the button that was clicked? Is there a way to get the instance variable name?
Once i have found out that information how do i get specific information about the children of the parent view that i am working on? The thought is, is that i might have 30 child views in the parent but i am only interested in one specific view(i might want to get the text of one specific textview as an example). I will know to look for it specifically because i would have done a switch statement on the different instances of my CustomRelativeLayouts(the first question) and therefore i know which view i want to look at, which logic to perform or what other methods that i need to call.
Would appreciate any thoughts or help with this.
For your first question, there are couple options:
Use separate OnClickListeners for each button. Then, each button will only trigger its own listener's onClick() method.
You can give each button a different id either in XML (via the android:id property) or in code (via setId(int id)). Then in onClick() you can check the id of the View that was passed as the argument.
For your second question:
Since you have the parent ViewGroup, you can find specific views within it by using:
TextView interestingView = (TextView) parent.findViewById(R.id.interesting);
This will only search the children of the parent view.
If you need to get an unknown number of views the best strategy is probably iterating through them like you are now. You can identify groups of views by setting a tag either in XML (android:tag) or code (setTag(Object tag) and check them as you iterate. For example, if you have a set of TextViews and in each one is either a color or an animal, you might handle that like this:
// defined elsewhere
private static final String TAG_COLOR = "color";
private static final String TAG_ANIMAL = "animal";
...
int count = parent.getChildCount();
for (int i = 0; i < count; i++){
View view = parent.getChildAt(i);
if (TAG_COLOR.equals(view.getTag()) {
// handle color
} else if (TAG_ANIMAL.equals(view.getTag()) {
// handle animal
}
}
There are several ways to do this.
Use "id" to identify views. You can give different id to different views, and then it will be possible to identify them. See http://developer.android.com/reference/android/view/View.html#getId() (the API documentation of View.getId()) for more information.
Mark each view with different tags, and identify them through tags. See http://developer.android.com/reference/android/view/View.html#getTag() (the API documentation of View.getTag()) for more information.
If you want to customize more, just inherit default Android views, and use "instanceof" to identify them.

How to get all Views in an Activity?

is there a way to get every view that is inside my activity?
I have over 200 views including buttons, and images, so i want to be able to access them by using a loop
for example something like
for (View v : this)
{
//do something with the views
//depending on the types (button, image , etc)
}
is there a way to get every view that
is inside my activity?
Get your root View, cast it to a ViewGroup, call getChildCount() and getChildAt(), and recurse as needed.
I have over 200 views including
buttons, and images, so i want to be
able to access them by using a loop
That is a rather large number of Views.
To be specific:
private void show_children(View v) {
ViewGroup viewgroup=(ViewGroup)v;
for (int i=0;i<viewgroup.getChildCount();i++) {
View v1=viewgroup.getChildAt(i);
if (v1 instanceof ViewGroup) show_children(v1);
Log.d("APPNAME",v1.toString());
}
}
And then use the function somewhere:
show_children(getWindow().getDecorView());
to show all Views in the current Activity.
Try to find all view associated with the Activity.
give the following command.
ViewGroup viewgroup=(ViewGroup)view.getParent();
viewgroup.getchildcount();
iterate through the loop.
We will get the Result.
Nice way to do this in Kotlin recursivelly:
private fun View.getAllViews(): List<View> {
if (this !is ViewGroup || childCount == 0) return listOf(this)
return children
.toList()
.flatMap { it.getAllViews() }
.plus(this as View)
}
You can use the hierarchyviewer, It allows you to see the view hierarchy including those created in code. It's primary reason is for debugging things like this. The latest Android Studio now has this feature in the Device Monitor that lets you make a dump of the UI to debug it.

Categories

Resources