Home » Android » Android – how to find multiple views with common attribute

Android – how to find multiple views with common attribute

Posted by: admin April 23, 2020 Leave a comment

Questions:

I have a layout with multiple ImageViews, some of those images need to have the same onClickListener.
I would like my code to be flexible and to be able to get a collection of those Views, iterate and add the listeners at run-time.

Is there a method like findViewById that will return a collection of Views rather than just a single one?

How to&Answers:

I’ve finally wrote this method (Updated thanks to @SuitUp (corrected username)):

private static ArrayList<View> getViewsByTag(ViewGroup root, String tag){
    ArrayList<View> views = new ArrayList<View>();
    final int childCount = root.getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = root.getChildAt(i);
        if (child instanceof ViewGroup) {
            views.addAll(getViewsByTag((ViewGroup) child, tag));
        } 

        final Object tagObj = child.getTag();
        if (tagObj != null && tagObj.equals(tag)) {
            views.add(child);
        }

    }
    return views;
}

It will return all views that have android:tag="TAG_NAME" attribute. Enjoy 😉

Answer:

Shlomi Schwartz’s method has one flaw, it does not collect Views wchich are ViewGroups. Here is my fix:

private static ArrayList<View> getViewsByTag(ViewGroup root, String tag){
    ArrayList<View> views = new ArrayList<View>();
    final int childCount = root.getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = root.getChildAt(i);
        if (child instanceof ViewGroup) {
            views.addAll(getViewsByTag((ViewGroup) child, tag));
        } 

        final Object tagObj = child.getTag();
        if (tagObj != null && tagObj.equals(tag)) {
            views.add(child);
        }

    }
    return views;
}

Answer:

There is no such method as findViewById that returns a group of views, but you can iterate over your views and set them an onClickListener like this:

for (int i = 0;i < layout.getChildCount();i++) {
  View child = layout.getChildAt(i);
  //you can check for view tag or something to see if it satisfies your criteria
  //child.setOnClickListener...
}

UPD:
For recursive view hierarchy (this is example from a real project, where we do view refresh recursively, but instead you could just do whatever you want with the nested views):

private void recursiveViewRefresh(ViewGroup view) {
    for (int i = 0;i < view.getChildCount();i++) {
        View child = view.getChildAt(i);
        try {
            ViewGroup viewGroup = (ViewGroup) child;
            recursiveViewRefresh(viewGroup);
        } catch (ClassCastException e) {
            //just ignore the exception - it is used as a check
        }
        singleViewRefresh(child);
    }
}

Answer:

This method provides a general way of obtaining views that match a given criteria. To use simply implement a ViewMatcher interface

private static List<View> getMatchingViews(ViewGroup root, ViewMatcher viewMatcher){
    List<View> views = new ArrayList<View>();
    final int childCount = root.getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = root.getChildAt(i);
        if (child instanceof ViewGroup) {
            views.addAll(getMatchingViews((ViewGroup) child, viewMatcher));
        }

        if(viewMatcher.matches(child)){
            views.add(child);
        }
    }
    return views;
}

public interface ViewMatcher{
    public boolean matches(View v);
}

// Example1: matches views with the given tag
public class TagMatcher implements ViewMatcher {

    private String tag;

    public TagMatcher(String tag){
        this.tag = tag;
    }

    public boolean matches(View v){
        String tag = v.getTag();
        return this.tag.equals(tag);
    }

}


// Example2: matches views that have visibility GONE
public class GoneMatcher implements ViewMatcher {

    public boolean matches(View v){
        v.getVisibility() == View.GONE  
    }

}

Answer:

I will share my functional-style method with a filter, can be used with StreamSupport library.

@NonNull
public static <T> Function<View, Stream<T>> subViews(
    @NonNull final Function<View, Optional<T>> filter
) {
    return view -> RefStreams.concat(
        // If current view comply target condition adds it to the result (ViewGroup too)
        filter.apply(view).map(RefStreams::of).orElse(RefStreams.empty()),

        // Process children if current view is a ViewGroup
        ofNullable(view).filter(__ -> __ instanceof ViewGroup).map(__ -> (ViewGroup) __)
                .map(viewGroup -> IntStreams.range(0, viewGroup.getChildCount())
                        .mapToObj(viewGroup::getChildAt)
                        .flatMap(subViews(filter)))
                .orElse(RefStreams.empty()));
}

@NonNull
public static <T> Function<View, Optional<T>> hasType(@NonNull final Class<T> type) {
    return view -> Optional.ofNullable(view).filter(type::isInstance).map(type::cast);
}

@NonNull
public static Function<View, Optional<View>> hasTag(@NonNull final String tag) {
    return view -> Optional.ofNullable(view).filter(v -> Objects.equals(v.getTag(), tag));
}

Usage example:

Optional.ofNullable(navigationViewWithSubViews)
            .map(subViews(hasType(NavigationView.class)))
            .orElse(RefStreams.empty())
            .forEach(__ -> __.setNavigationItemSelectedListener(this));

Answer:

This is a modification of Shlomi Schwartz’s answer above. All credit to them. I didn’t like how the recursion looked, and needed to be able to regex match a string tag name.

  /**
   * Find all views with (string) tags matching the given pattern.
   *
   */
  protected static Collection<View> findViewsByTag(View root, String tagPattern) {
    List<View> views = new ArrayList<>();

    final Object tagObj = root.getTag();
    if (tagObj instanceof String) {
      String tagString = (String) tagObj;
      if (tagString.matches(tagPattern)) {
        views.add(root);
      }
    }

    if (root instanceof ViewGroup) {
      ViewGroup vg = (ViewGroup) root;
      for (int i = 0; i < vg.getChildCount(); i++) {
        views.addAll(findViewsByTag(vg.getChildAt(i), tagPattern));
      }
    }

    return views;
  }

For example:

Collection<View> itemNameViews = findViewsByTag(v, "^item_name_[0-9]+$");

Answer:

@Override
protected void onClick(View view){
switch(view.getId()){

case R.id.whatEverImageViewId :
//....
break ;

case R.id.whatEverImageViewId 2 :
//....
break ;
....

and you can use for loop to add listeners

Answer:

You can use switch() for multiple widgets.

switch(viewobject.getId()) {    
 case R.id.imageview1:    
   /* ... */    
   break;    
 case R.id.imageview2:    
   /* ... */      
   break;    
}