34

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?

fernandohur
  • 7,014
  • 11
  • 48
  • 86
Shlomi Schwartz
  • 8,693
  • 29
  • 109
  • 186
  • possible duplicate of [Find all views with tag?](http://stackoverflow.com/questions/5062264/find-all-views-with-tag) – johnkavanagh Mar 05 '14 at 17:34

9 Answers9

60

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 ;)

Morgoth
  • 4,935
  • 8
  • 40
  • 66
Shlomi Schwartz
  • 8,693
  • 29
  • 109
  • 186
  • 1
    It does not collect ViewGroup Views. – SuitUp Jun 04 '12 at 20:13
  • 4
    You can use Objects.equal(tagObj, tag) for cleaner code and also allowing querying for views with no tags. Also you can make the method more general by letting the tag parameter be an object, since the tags may not be Strings. (And returning a List or Collection would be better practice). – Asaf Jan 23 '13 at 15:06
  • 4
    Pretty awesome, +1. I created a variation of this that returns the root view as well, if it has the tag. https://gist.github.com/orip/5566666 – orip May 13 '13 at 07:23
  • Why do you use final on each variable? See https://stackoverflow.com/questions/4279420/does-use-of-final-keyword-in-java-improve-the-performance – Lev Leontev Sep 29 '19 at 20:34
19

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;
}
SuitUp
  • 3,112
  • 5
  • 28
  • 41
6

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);
    }
}
justadreamer
  • 2,410
  • 2
  • 21
  • 24
3

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  
    }

}
fernandohur
  • 7,014
  • 11
  • 48
  • 86
2

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));
TachikomaGT
  • 940
  • 10
  • 10
1

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]+$");
Jeffrey Blattman
  • 22,176
  • 9
  • 79
  • 134
0

In kotlin we can do it is a very simplistic way as following (Kotlin Extension Function):

fun ViewGroup.findViewsByTag(tag: String): ArrayList<View> = ArrayList<View>().also {
    this.children.forEach { child ->
        if (child is ViewGroup) it.addAll(child.findViewsByTag(tag))
        if (child.tag == tag) it.add(child)
    }
}

Now, you can use it on any viewGroup. For example:

mViewGroup.findViewsByTag("my tag")
protanvir993
  • 2,759
  • 1
  • 20
  • 17
0
@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

Its not blank
  • 3,055
  • 22
  • 37
0

You can use switch() for multiple widgets.

switch(viewobject.getId()) {    
 case R.id.imageview1:    
   /* ... */    
   break;    
 case R.id.imageview2:    
   /* ... */      
   break;    
}
Chris Stillwell
  • 10,266
  • 10
  • 67
  • 77
Ranjit Mishra
  • 440
  • 4
  • 8