Shlomi Schwartz
Shlomi Schwartz

Reputation: 8903

Android - how to find multiple views with common attribute

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?

Upvotes: 34

Views: 20031

Answers (9)

protanvir993
protanvir993

Reputation: 3109

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

Upvotes: 1

Shlomi Schwartz
Shlomi Schwartz

Reputation: 8903

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

Upvotes: 61

Jeffrey Blattman
Jeffrey Blattman

Reputation: 22637

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]+$");

Upvotes: 1

TachikomaGT
TachikomaGT

Reputation: 1009

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

Upvotes: 2

fernandohur
fernandohur

Reputation: 7144

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  
    }

}

Upvotes: 3

Ranjit Mishra
Ranjit Mishra

Reputation: 450

You can use switch() for multiple widgets.

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

Upvotes: 0

SuitUp
SuitUp

Reputation: 3182

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

Upvotes: 19

justadreamer
justadreamer

Reputation: 2440

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

Upvotes: 6

Its not blank
Its not blank

Reputation: 3095

@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

Upvotes: 0

Related Questions