Reputation: 8903
I have a layout with multiple ImageView
s, 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 View
s, iterate and add the listeners at run-time.
Is there a method like findViewById
that will return a collection of View
s rather than just a single one?
Upvotes: 34
Views: 20031
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
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
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
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
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
Reputation: 450
You can use switch()
for multiple widgets.
switch(viewobject.getId()) {
case R.id.imageview1:
/* ... */
break;
case R.id.imageview2:
/* ... */
break;
}
Upvotes: 0
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
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
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