How can I monitor multiple views in my Android application. The reason I'm interested in this, is cause, my app has a view that has lot of fields EditTexts, Spinners, CheckBoxes, RadioButtons and some EditTexts are hidden unless a particular Spinner Item is selected or a Radio Button is checked. All I want the observer to do is to check all fields are filled before the submit Button is enabled and if any View was un-hidden it must check if the Fields have been filled too. This is where I'm stuck on what to do: I have a four boolean checks that check for the four hidden Views, but then I don't want to write a long if/else statement. Thanks
The following code will help you to check an unknown number of Views of different types contained in a single ViewGroup. In your case with just four views, I think you are likely prefer four 'if' clauses.
You could mark all Views which you want to check by adding a tag in the layout file:
android:tag="CheckMe"
Tags don't have to be unique, so you can use the same tag for all Views.
In addition to that, give an id to the ViewGroup containing your Views (this could be a LinearLayout)
android:id="#+id/myLayout"
Then in your activity you can first get the ViewGroup
ViewGroup myLayout = (ViewGroup) findViewById(R.id.mylayout);
and then cycle over the child Views:
for (int i = 0; i < myLayout.getChildCount(); i++)
{
View v = myLayout.getChildAt(i);
if (v.getTag().toString().equals("CheckMe")
{
if (v instanceof EditText)
{ // do something
}
else if (v instanceof SomeOtherView)
{ // do something else
}
}
}
Related
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.
I have some content in an Android app which is shown in a list. Each list entry has similar fields - let's say a picture, some text, and a text box. However, some list entries are different than others. The order of the content is based on the result of a server call.
The list itself needs to be fairly dynamic, and I'm currently using a linearlayout rather than a listview for a few reasons. My code looks something like this:
LinearLayout list = findViewById(android.R.id.list);
while (more content to add) {
switch (content type) {
case A:
View v = layoutInflater.inflate(R.layout.list_item_type_a, list, false);
EditText editText = (EditText)v.findViewById(android.R.id.edit);
// Do stuff with editText
list.addView(v);
break;
case B:
View v = layoutInflater.inflate(R.layout.list_item_type_b, list, false);
....
}
}
This works great. Except - when I put this in a fragment, and rotate the screen, now my app crashes because I have multiple fields with the same android.R.id.edit identifier.
I had thought this was a fairly elegant solution and the Android gods seem to disagree. Do I need to rip out the ID for all of my xml sublayouts? If I go this route, how should I grab references to the content?
So obviously, ListView or RecyclerView would be preferable to use here, but since you've stated you have reasons not to, I'd suggest that you disable automatic state saving for each of the views.
You can just call editText.setSaveEnabled(false), which will fix the issue, but have the side effect of not automatically retaining the view's state (e.g. input data will be lost). If you're maintaining this data yourself and restoring it on configuration changes or state restoration, this should be a totally workable solution.
I believe you could also just call setSaveFromParentEnabled(false) on the containing LinearLayout (although I haven't used that flag myself), which should disable state saving for any view in the sub-hierarchy. Same caveat applies.
I would suggest using a ListView instead of the LinearLayout and creating a custom adapter to fill the ListView. You could still have the list_item layouts that you have and then add them to the list in the newView method of your adapter. Pass your content type through a method, say getItemViewType(). Something like this:
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// Choose the layout type
int contentType = getItemViewType();
int layoutId = -1;
switch (contentType) {
case A: {
View v = layoutInflater.inflate(R.layout.list_item_type_a, list, false);
EditText editText = (EditText)v.findViewById(android.R.id.edit);
// Do stuff with editText
list.addView(v);
break;
}
case B: {
View v = layoutInflater.inflate(R.layout.list_item_type_b, list, false);
....
}
}
I think this problem occur due to device screen rotation
When the phone rotates and the screen changes orientation, Android usually destroys your application’s existing Activities and Fragments and recreates them. Android does this so that your application can reload resources based on the new configuration.
The most important aspect of handling orientation changes is saving state. In most cases this involves implementing the onSaveInstanceState method (this could be in your Activity, Fragment or both) and placing the values you need to save in the Bundle argument that gets passed to the method.
For more details and code examples...
Please read this article
If you have a dynamic list, you should be using a ListView or RecyclerView.
I am new to Android development and reading through some example code. I have copied one method from the sample code in an Adapter class (derived from ArrayAdapter), the derived class has a checkbox in addition to the text view:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View listItem = super.getView(position, convertView, parent);
CheckedTextView checkMark = null;
ViewHolder holder = (ViewHolder) listItem.getTag();
if (holder != null) {
checkMark = holder.checkMark;
} else {
checkMark = (CheckedTextView) listItem.findViewById(android.R.id.text1);
holder = new ViewHolder(checkMark);
listItem.setTag(holder);
}
checkMark.setChecked(isInCollection(position));
return listItem;
}
private class ViewHolder {
protected final CheckedTextView checkMark;
public ViewHolder(CheckedTextView checkMark) {
this.checkMark = checkMark;
}
}
The sample code is to optimize the getView by caching the View within a ViewHolder object.
Where I am confused is I thought the convertView, if not null, would be re-purposed and then the View data is populated into it and returned.
If this is the case, then how could the setTag / getTag methods called in the code be relied upon? It would seem that the same object would have to be retrieved in order for it to work?
perhaps view returned from getTag on a subsequent call is for a different list item, and returns the wrong view
Adapters use a RecycleBin. This class allows the ListView to only create as many row layouts as will fit on the screen, plus one or two for scrolling and pre-loading. So if you have a ListView with 1000 rows and a screen that only displays 7 rows, odds are the ListViiew will only have 8 unique Views.
Now to your question using my example above: only eight row layouts and 8 subsequent ViewHolders are ever created. When the users scrolls no new row layouts are ever created; only the content of the row layout changes. So getTag() will always have a valid ViewHolder that references the appropriate View(s).
(Does that help?)
You're on the right track, here's some information that may help make more sense of how ListViews work:
A simple implementation of the getView() method has two goals. The first is inflating the View to be shown on the list. The second is populating the View with the data that needs to be shown.
As you stated, ListViews re-purpose the Views that compose the list. This is sometimes referred to as view recycling. The reason for this is scalability. Consider a ListView that contains the data of 1000 items. Views can take up a lot of space, and it would not be feasible to inflate 1000 Views and keep them all in memory as this could lead to performance hits or the dreaded OutOfMemoryException. In order to keep ListViews lightweight, Android uses the getView() method to marry Views with the underlying data. When the user scrolls up and down the list, any Views that move off the screen are placed in a pool of views to be reused. The convertView parameter of getView() comes from this list. Initially, this pool is empty, so null Views are passed to getView(). Thus, the first part of getView should be checking to see if convertView has been previously inflated. Additionally, you'll want to configure the attributes of convertView that will be common to all list items. That code will look something like this:
if(convertView == null)
{
convertView = new TextView(context);
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
The second part of an implementation of getView() looks at your underlying data source for the list and configures this specific instance of the View. For example, in our test list, we may have an Array of Strings to set the text of the view, and want to set the tag as the current position in the Data of this View. We know which item in the list we're working with based on the position parmeter. This configuration comes next.
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
This allows us to minimize the amount of time we spend inflating/creating new views, a costly operation, while still being able to quickly configuring each view for display. Putting it all together, your method will look like this:
#Override
public View getView(int position, View convertView, ViewGroup)
{
if(convertView == null)
{
convertView = new TextView(context);
//For more complex views, you may want to inflate this view from a layout file using a LayoutInflator, but I'm going to keep this example simple.
//And now, configure your View, for example...
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
//Configure the View for the item at 'position'
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
//Finally, we'll return the view to be added to the list.
return convertView;
}
As you can see, a ViewHolder isn't needed because the OS handles it for you! The Views themselves should be considered temporary objects and any information they need to hold onto should be managed with your underlying data.
One further caveat, the OS does nothing to the Views that get placed in the pool, they're as-is, including any data they've been populated with or changes made to them. A well-implemented getView() method will ensure that the underlying data keeps track of any changes in the state of views. For example, if you change text color of your TextView to red onClick, when that view is recycled the text color will remain red. Text color, in this case, should be linked to some underlying data and set outside of the if(convertView == null) conditional each time getView() is called. (Basically, static setup common for all convertViews happens inside the conditional, dynamic setup based on the current list item and user input happens after) Hope this helps!
Edited - Made the example simpler and cleaned up the code, thanks Sam!
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.
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.