Android - как найти несколько видов с общим атрибутом
У меня есть макет с несколькими ImageView
s, некоторые из этих изображений должны иметь один и тот же onClickListener.
Я бы хотел, чтобы мой код был гибким и имел возможность получить коллекцию этих View
s, повторить и добавить слушателей во время выполнения.
Есть ли метод, подобный findViewById
, который вернет коллекцию View
, а не только одну?
Ответы
Ответ 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;
}