Given a new screen in android i would like to iterate through all the viewgroups and views in order to discover all buttons,text fields, spinner etc... is this possible ?
I get the view count and then use that as a
counter to call getChildAt(int index)
This question may have been long answered, but I wrote this recursive function to set onClickListeners for any buttons I find in my layout, but it could be repurposed:
private void recurseViews(ViewGroup v) {
View a;
boolean isgrp = false;
for(int i = 0; i < v.getChildCount(); i++) { //attach listener to all buttons
a = v.getChildAt(i);
if(a instanceof ViewGroup) setcl((ViewGroup) a);
else if(a != null) {
//do stuff with View a
}
}
return;
}
EDIT: Casting a View as ViewGroup does not raise an exception as had I previously thought, so the code is much shorter now
You could use this to get all child views inside a parent layout, returns an array list of views.
public List<View> getAllViews(ViewGroup layout){
List<View> views = new ArrayList<>();
for(int i =0; i< layout.getChildCount(); i++){
views.add(layout.getChildAt(i));
}
return views;
}
if you want to get a specific view you can use this example. It takes all TextView inside a layout.
public List<TextView> getAllTextViews(ViewGroup layout){
List<TextView> views = new ArrayList<>();
for(int i =0; i< layout.getChildCount(); i++){
View v =layout.getChildAt(i);
if(v instanceof TextView){
views.add((TextView)v);
}
}
return views;
}
As long as the object you're trying to get derives from View class, it will work.
Related
I am working on a project where I have a let's say 5x5 grid of TextViews and I want to check if an entire row or column has equal elements. I am using an Adapter class to inflate my gridview with simply one textview element. Here is the code that I have tried but I cannot seem to make it work:
final int size = gridView.getCount(); //25
int temp = 0;
for (int i = 0; i < size; i++) {
ViewGroup gridChild = (ViewGroup) gridView.getChildAt(i);
childSize = gridChild.getChildCount();
for (int j = 0; j < childSize; j++) {
if (gridChild.getChildAt(j) instanceof TextView &&
((TextView) gridChild.getChildAt(j)).getText().toString().equals("x")) {
temp++;
}
The thing is when i tried to debug, debugger showed null values for childSize variable and could not properly get the value from getChildAt. Basically, what I am trying to do is get inside the if statement. Also this is the first time I am working with ViewGroup calss, and the methods that I call. Any help would be appreciated.
Edit:I am looking for a way to do this outside the getView method in the adapter class and not in a onClick method as well. (Code sample answers would be highly appreciated). Also, the getChildAt method call returns null so the code I have shown would not work because I am assigning a null value to the gridChild.
This is the onClick that I use for the TextViews:
`
public void numberFill(View view) {
if (((TextView) view).getText().toString().isEmpty()) {
((TextView) view).setText(String.valueOf(numbCounter + 1));
numbCounter++;
}
else if (!((TextView) view).getText().toString().isEmpty() && numbCounter >= 16) {
((TextView) view).setText("x");
}
}
This is my adapter class:
public class GridAdapter extends BaseAdapter {
private final Context mContext;
private String[] numbers;
public GridAdapter(Context context, String[] numbers) {
this.mContext = context;
this.numbers = numbers;
}
#Override
public int getCount() {
return numbers.length;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public Object getItem(int position) {
return numbers[position];
//return null;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater)
mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
if (convertView == null) {
gridView = new View(mContext);
gridView = inflater.inflate(R.layout.textview_layout, null);
TextView textView = (TextView) gridView.findViewById(R.id.cell);
textView.setText(numbers[position]);
} else {
gridView = (View) convertView;
}
return gridView;
}
}
numberFill reworked:
public void numberFill(View view) {
int index = (Integer) view.getTag();
if (numbers[index].toString().isEmpty()) {
numbers[index] = String.valueOf(numbCounter + 1);
numbCounter++;
}
else if (!numbers[index].toString().isEmpty() && numbCounter >= 25) {
numbers[index] = "x";
}
gridAdapter.notifyDataSetChanged();
}
`
When using an AdapterView – such as your GridView – you generally don't want to directly access and manipulate its child Views outside of its Adapter. Instead, the dataset backing the Adapter should be updated, and the GridView then refreshed.
In your case, you presumably have a setup similar to this in your Activity:
private GridAdapter gridAdapter;
private String[] numbers;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
numbers = new String[25];
gridAdapter = new GridAdapter(this, numbers);
}
Here, the numbers array is what you want to directly modify, rather than the text on the GridView's child TextViews. That array is then easily iterated over to do your row and column value checks.
Since the array will be modified in the Activity, we need a way to pass the clicked TextView's position in the Adapter to the Activity's click method, as we'll need it to access the correct array element. For this, we can utilize the tag property available on all View's, via the setTag() and getTag() methods. For example, in GridAdapter's getView() method:
...
TextView textView = (TextView) gridView.findViewById(R.id.cell);
textView.setText(numbers[position]);
textView.setTag(position);
...
In the click method, the position can be easily retrieved with getTag(), and used as the index to get the clicked TextView's text from the numbers array. You can then do the necessary processing or calculation with that text, set the modified value back to the array element, and trigger a refresh on the Adapter.
public void numberFill(View view) {
int index = (Integer) view.getTag();
// Do your processing with numbers[index]
numbers[index] = "new value";
gridAdapter.notifyDataSetChanged();
}
The notifyDataSetChanged() call will cause the GridView to update its children, and your new value will be set in the appropriate TextView. The numbers array now also has the current values, and is readily available in the Activity to perform the necessary checks there.
I am using two different ExpandableListViews in my fragment inside a scroll view, one right below the other.
The problem is that only one ExpandableListView heading is displayed when the activity is called. Please refer the image below:
Also, when I click the expandable list view, the list view expands and the other ExpandableListView also displays. Refer the image below:
I want the both the Expandable list views to display when the activity gets called for the first time.
This is my xml:
<ExpandableListView
android:id="#+id/exLInTheMoodFor"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:scrollbars="none"
android:groupIndicator="#null"
android:layout_below="#+id/lblInTheMoodFor"
android:layout_marginTop="10dp"/>
And this is the java code for defining and initializing the Expandable list view and setting height:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View convertView = inflater.inflate(R.layout.cafes_more_fragment, container, false);
final ExpandListChild1 items = new ExpandListChild1();
exLInTheMoodFor = (ExpandableListView) convertView.findViewById(R.id.exLInTheMoodFor);
ExpListItems = SetStandardGroups();
ExpAdapter = new ExpandListAdapterProduct(activity, ExpListItems);
exLInTheMoodFor.setAdapter(ExpAdapter);
exLInTheMoodFor.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
#Override
public boolean onGroupClick(ExpandableListView expandableListView, View view, int i, long l) {
setListViewHeight(exLInTheMoodFor, i);
return false;
}
});
return convertView;
}
//for set height show method in expnadable list view
private void setListViewHeight(ExpandableListView listView, int group) {
android.widget.ExpandableListAdapter listAdapter = (android.widget.ExpandableListAdapter) listView.getExpandableListAdapter();
int totalHeight = 0;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(),
View.MeasureSpec.EXACTLY);
for (int i = 0; i < listAdapter.getGroupCount(); i++) {
View groupItem = listAdapter.getGroupView(i, false, null, listView);
groupItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += groupItem.getMeasuredHeight();
if (((listView.isGroupExpanded(i)) && (i != group))
|| ((!listView.isGroupExpanded(i)) && (i == group))) {
for (int j = 0; j < listAdapter.getChildrenCount(i); j++) {
View listItem = listAdapter.getChildView(i, j, false, null,
listView);
listItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += listItem.getMeasuredHeight();
}
}
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
int height = totalHeight
+ (listView.getDividerHeight() * (listAdapter.getGroupCount() - 1));
if (height < 10)
height = 200;
params.height = height;
listView.setLayoutParams(params);
listView.requestLayout();
}
Expanding all groups in the list
for(int i=0; i < myAdapter.getGroupCount(); i++)
mexpandableListView.expandGroup(i);
if Expanding one item in the list
mexpandableListView.expandGroup(0);
One item Expanding and balance item compressed
mexpandableListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener() {
#Override
public void onGroupExpand(int i) {
ExpandableAdapter myAdapter= (ExpandableHomeworkAdapter) mexpandableListView.getExpandableListAdapter();
if (myAdapter== null){
return;
}
for (int k=0;k<myAdapter.getGroupCount();k++){
if (k!=i){
mexpandableListView.collapseGroup(k);
}
}
}
});
The problem was using listview/expandablelistview inside scrollview. Generally this combination causes the visibility issue of listview existing inside the scrollview. Full explanation can be found from this answer. This link and this link should also be helpful for solving your problem. I hope this helps.
as in topic, when I use adapter.notifyDataSetChanged() text color in a cell which i have already changed is seting back to default value. I don't know why it happens im putting here me method for changing color:
for(int l=0;l<list.size();l++){
System.out. println("kolorujemy! "+ list.size() );
LinearLayout root = (LinearLayout) getViewByPosition(l,listView);
((TextView) root.findViewById(R.id.wartosc_calosci)).setTextColor(Color.YELLOW);
I would also add that this part of code is in loop in other thread because the vaules of the cells is updating every 30 seconds. Here is method getViewByPosition:
public View getViewByPosition(int pos, ListView listView) {
final int firstListItemPosition = listView.getFirstVisiblePosition();
final int lastListItemPosition = firstListItemPosition + listView.getChildCount();
if (pos < firstListItemPosition || pos > lastListItemPosition ) {
return listView.getAdapter().getView(pos, null, listView);
} else {
final int childIndex = pos - firstListItemPosition+1;
return listView.getChildAt(childIndex);
}
}
getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ListViewHolder listViewHolder;
if(convertView == null){
listViewHolder = new ListViewHolder();
convertView = activity.getLayoutInflater().inflate(R.layout.lista_wlasnych_spolek, null);
listViewHolder.txtFirst = (TextView) convertView.findViewById(R.id.nazwa_spolki);
listViewHolder.txtSecond = (TextView) convertView.findViewById(R.id.wartosc_akt);
listViewHolder.txtThird = (TextView) convertView.findViewById(R.id.wartosc_kupna);
listViewHolder.txtFourth = (TextView) convertView.findViewById(R.id.wartosc_calosci);
convertView.setTag(listViewHolder);
} else {
listViewHolder = (ListViewHolder) convertView.getTag();
}
First of all this line
return listView.getAdapter().getView(pos, null, listView);
makes no sense, because with this call by hand you will internally always create and inflate new row for the list view but this view is never used within your ListView. See that you are always passing second parameter convertView null so internally this method will create new view but this view will be never used inside your ListView.
Tip 1. Don't call getView() method yourself
As you may know ListView stores in memory only as many rows/view as they are visible on the screen when you use ViewHolder pattern properly.
So for now you are setting color for every row that is visible and even those not visible that really not exist in ListView.
Tip 2.
Best way to color or change anything about any of your rows, is to do it just inside getView() method implementation depend on your adapter item state. Don't do it from outside because it looks like some kind of a hack or wrong architecture.
I have an app that gathers points from checkboxes in a listview. The checkboxes are dynamically added along with the items of the listview. Each time a checkbox is clicked, I add points to a total. The manner in which I do this works fine as long as all the list items fit on the screen. When the list gets long enough to cause it to scroll, I lose the values I had previously checked when I scroll down the list. So, my scrolling the list causes the points to reset. I feel pretty confident it has something to do with losing focus from the checkboxes and/or gaining focus from the click to the listview itself, that causes this reset in points.
IMPORTANT EDIT: Ok, so it doesn't actually take a simple click and SLIGHT scroll of the listview to cause this to happen. I have to actually get the previous CHECKBOX scrolled out of view just enough to make the points reset. WTF?
Here's some code...
Here is my entire custom adaptor that handles the checkbox:
public class ScoreListAdapter extends BaseAdapter {
private ArrayList<ScoringInfo> data;
Context c;
ScoringInfo scr;
ScoreListAdapter (ArrayList<ScoringInfo> data, Context c){
this.data = data;
this.c = c;
}
public int getCount() {
// TODO Auto-generated method stub
return data.size();
}
public Object getItem(int pos) {
// TODO Auto-generated method stub
return data.get(pos);
}
public long getItemId(int pos) {
// TODO Auto-generated method stub
return pos;
}
public View getView(int pos, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View v = convertView;
if (v == null)
{
LayoutInflater vi = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.score_list_row, null);
}
TextView subtaskView = (TextView)v.findViewById(R.id.subtask);
TextView maxPointsView = (TextView)v.findViewById(R.id.max_points);
scr = data.get(pos);
subtaskView.setText(scr.subtask);
maxPointsView.setText("Points: " + Integer.toString(scr.maxPoints));
final CheckBox checkBox = (CheckBox) v.findViewById(R.id.score_box);
checkBox.setTag(R.string.subtask_num, scr.subtaskNum);
checkBox.setTag(R.string.score, scr.maxPoints);
checkBox.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
int subNum = Integer.parseInt(checkBox.getTag(R.string.subtask_num).toString());
int score = (Integer) checkBox.getTag(R.string.score);
if (((CheckBox)v).isChecked()) {
score =(Integer) checkBox.getTag(R.string.score);
Challenge.subtaskScores.put(subNum, score);
scr.addToTotalPoints(score);
updatePoints(scr.getTotalPoints());
}
else {
if (Challenge.subtaskScores.containsKey(subNum))
Challenge.subtaskScores.remove(subNum);
scr.addToTotalPoints(-score);
updatePoints(scr.getTotalPoints());
}
}
});
return v;
}
public void updatePoints(int total){
TextView scrUpdate = (TextView) ((Activity)c).findViewById(R.id.curr_score_view);
Challenge.totalPoints1 = total;
int grandTotal = Challenge.totalPoints1 + Challenge.totalPoints2;
scrUpdate.setText("Current Score: " + grandTotal);
}
}
Here is what I feel is the relevant code from Challenge.class:
public void createScoringList() {
// Builds two lists: one for the tasks that do not allow partial points, and
// another for the tasks that DO allow partial points. The lists are stacked
// on top of each other. This was the only way I could come up with to present
// two types of layouts for the two types of point input. This may need to be
// reconsidered.
ListView scoreList = (ListView) findViewById(R.id.score_list);
ListView scoreListPartial = (ListView) findViewById(R.id.score_list_partial);
ArrayList<ScoringInfo> objList = new ArrayList<ScoringInfo>();
ArrayList<ScoringInfo> objListPartial = new ArrayList<ScoringInfo>();
ScoringInfo scrInfo;
// The ScoringInfo object holds the various fields that are associated with each subtask.
infoView = (TextView) findViewById(R.id.chall_team_config_show);
infoView.setText(chall_name + " (id: " + challenge_id + ")\nTeam: " + team_num +
"\nConfiguration: " + randomConfig);
for (int i = 0; i < subTaskList.size(); i++) {
subtask_num = subTaskList.get(i).subtask_num;
max_points = subTaskList.get(i).max_points;
partial_points_allowed = subTaskList.get(i).partial_points_allowed;
task_name = subTaskList.get(i).task_name;
scrInfo = new ScoringInfo();
scrInfo.setMaxPoints(max_points);
scrInfo.setSubtask(task_name);
scrInfo.setSubtaskNum(subtask_num);
if (partial_points_allowed == 1)
objListPartial.add(scrInfo);
else
objList.add(scrInfo);
}
// There is a custom adapter for both possible lists should the challenge need it.
scoreList.setAdapter(new ScoreListAdapter(objList , this));
scoreListPartial.setAdapter(new ScoreListAdapter2(objListPartial, this));
}
No doubt I forgot something. If there's confusion over my question, please ask for clarification. This is driving me nuts, and keeping me up all night.
Your problem is as stated by #Patrick on the first comment that your are not saving the CheckBox state. You will need to save it to somewhere, a boolean array for example.
Then when you recreate the view you will get the saved value from the array and check/uncheck the CheckBox.
I want to add a footer view for GridView.
I find in the documentation GridView has 2 inherited addView(View child) method.
From class android.widgetAdapterView
void addView(View child)
This method is not supported and throws an UnsupportedOperationException when called.
and
From class android.view.ViewGroup
void addView(View child)
Adds a child view.
It seems I should use the latter. But how can I call this particular inherited method?
You don't. It overwrites the original with a UnsupportedOperationException because it's.. well.. not supported.
You should be editing the adapter instead. Depending on your implementation, this will look different. But, you just have to add more data to the adapter, and call .notifyDataSetChanged() on the adapter, and your GridView will add the view by itself.
A footer view should either be a separate View after your GridView, or you will have to maintain its position in the adapter's list to always be last whenever you add new items.
Providing an example of Erics solution, the adapter could maintain two extra members for tracking the
position of the "footer", and its event handler:
class ImageViewGridAdapter : ArrayAdapter<int>
{
private readonly List<int> images;
public int EventHandlerPosition { get; set; }
public EventHandler AddNewImageEventHandler { get; set; }
public ImageViewGridAdapter(Context context, int textViewResourceId, List<int> images)
: base(context, textViewResourceId, images)
{
this.images = images;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
ImageView v = (ImageView)convertView;
if (v == null)
{
LayoutInflater li = (LayoutInflater)this.Context.GetSystemService(Context.LayoutInflaterService);
v = (ImageView)li.Inflate(Resource.Layout.GridItem_Image, null);
// ** Need to assign event handler in here, since GetView
// is called an arbitrary # of times, and the += incrementor
// will result in multiple event fires
// Technique 1 - More flexisble, more maintenance ////////////////////
if (position == EventHandlerPosition)
v.Click += AddNewImageEventHandler;
// Technique 2 - less flexible, less maintenance /////////////////////
if (position == images.Count)
v.Click += AddNewImageEventHandler;
}
if (images[position] != null)
{
v.SetBackgroundResource(images[position]);
}
return v;
}
}
Then, before assigning the adapter to the grid view, just assign those values (position doesn't have to be at end, but for a footer it should be):
List<int> images = new List<int> {
Resource.Drawable.image1, Resource.Drawable.image2, Resource.Drawable.image_footer
};
ImageViewGridAdapter recordAttachmentsAdapter = new ImageViewGridAdapter(Activity, 0, images);
recordAttachmentsAdapter.EventHandlerPosition = images.Count;
recordAttachmentsAdapter.AddNewImageEventHandler += NewAttachmentClickHandler;
_recordAttachmentsGrid.Adapter = recordAttachmentsAdapter;