【发布时间】:2012-02-07 16:43:51
【问题描述】:
我有一个包含多个 ImageViews 的布局,其中一些图像需要具有相同的 onClickListener。
我希望我的代码具有灵活性,并且能够获取这些 Views 的集合,在运行时迭代并添加侦听器。
是否有像findViewById 这样的方法可以返回Views 的集合,而不仅仅是一个?
【问题讨论】:
我有一个包含多个 ImageViews 的布局,其中一些图像需要具有相同的 onClickListener。
我希望我的代码具有灵活性,并且能够获取这些 Views 的集合,在运行时迭代并添加侦听器。
是否有像findViewById 这样的方法可以返回Views 的集合,而不仅仅是一个?
【问题讨论】:
在 kotlin 中我们可以这样做是一种非常简单的方式,如下所示(Kotlin 扩展功能):
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)
}
}
现在,您可以在任何 viewGroup 上使用它。例如:
mViewGroup.findViewsByTag("my tag")
【讨论】:
我终于写出了这个方法(更新感谢@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" 属性的视图。享受;)
【讨论】:
这是对 Shlomi Schwartz 上述答案的修改。所有功劳归功于他们。我不喜欢递归的样子,需要能够匹配字符串标签名称。
/**
* 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]+$");
【讨论】:
我将与过滤器分享我的函数式方法,可以与 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));
【讨论】:
此方法提供了一种获取符合给定条件的视图的通用方法。要使用只需实现一个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
}
}
【讨论】:
您可以将switch() 用于多个小部件。
switch(viewobject.getId()) {
case R.id.imageview1:
/* ... */
break;
case R.id.imageview2:
/* ... */
break;
}
【讨论】:
Shlomi Schwartz 的方法有一个缺陷,它不收集 Views 而不是 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;
}
【讨论】:
没有像 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...
}
统一更新: 对于递归视图层次结构(这是来自真实项目的示例,我们在其中递归地进行视图刷新,但是您可以对嵌套视图执行任何操作):
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);
}
}
【讨论】:
@Override
protected void onClick(View view){
switch(view.getId()){
case R.id.whatEverImageViewId :
//....
break ;
case R.id.whatEverImageViewId 2 :
//....
break ;
....
你可以使用for循环来添加监听器
【讨论】: