Подтвердить что ты не робот

Android - как найти несколько видов с общим атрибутом

У меня есть макет с несколькими ImageView s, некоторые из этих изображений должны иметь один и тот же onClickListener. Я бы хотел, чтобы мой код был гибким и имел возможность получить коллекцию этих View s, повторить и добавить слушателей во время выполнения.

Есть ли метод, подобный findViewById, который вернет коллекцию View, а не только одну?

4b9b3361

Ответ 1

Я наконец-то написал этот метод (Обновлено благодаря @SuitUp (исправленное имя пользователя)):

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

Он вернет все представления, имеющие атрибут android:tag="TAG_NAME". Наслаждаться ;)

Ответ 2

Метод Шломи Шварца имеет один недостаток, он не собирает Views wchich ViewGroups. Вот мое исправление:

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

Ответ 3

Нет такого метода, как findViewById, который возвращает группу представлений, но вы можете перебирать свои представления и устанавливать их onClickListener следующим образом:

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: Для рекурсивной иерархии представлений (это пример из реального проекта, где мы рекурсивно просматриваем обновление, но вместо этого вы можете просто делать все, что хотите, с вложенными представлениями):

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

Ответ 4

Этот метод обеспечивает общий способ получения представлений, соответствующих заданным критериям. Чтобы просто реализовать интерфейс ViewMatcher

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  
    }

}

Ответ 5

Я поделюсь своим методом в функциональном стиле с фильтром, который можно использовать с библиотекой StreamSupport.

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

Пример использования:

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

Ответ 6

Это модификация ответа Шломи Шварца выше. Все заслуга им. Мне не понравилось, как выглядит рекурсия, и мне нужно было иметь возможность сопоставлять регулярное выражение с именем строкового тега.

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

Например:

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

Ответ 7

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

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

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

и вы можете использовать для цикла для добавления слушателей

Ответ 8

Вы можете использовать switch() для нескольких виджетов.

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