如何从自定义视图与其所在的Activity进行正确通信?

18

我有一个自定义的View类,它继承了Spinner。当用户进行选择时,我试图找出与其嵌入的Activity交互的正确方式。我发现OnItemSelected监听器可以获得对Adapter的引用,但我不确定是否应该使用此Adapter并在其父级链上进行操作,还是直接与上下文通信(尽管我无法立即想到任何可能失败的情况,但这种方法似乎不安全)。

4个回答

37

正确的做法是通过暴露一个接口来“监听”您的自定义视图,使您的视图持有对该实例的引用,您的主机活动应该实现该接口。就像OnItemSelected接口和任何Android视图公开的事件一样实现。这是观察者设计模式。

例如:

public class MyCustomSpinner extends Spinner {
    public MyCustomSpinner(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public interface IMyEventListener {
        public void onEventOccurred();
    }

    private IMyEventListener mEventListener;

    public void setEventListener(IMyEventListener mEventListener) {
        this.mEventListener = mEventListener;
    }

    protected void someMethodWhichDoingSomthingAndShouldRaiseAlsoTheEvent() {

        /*
         * Some Code which the function doing //more code...
         */

        if (mEventListener != null) {
            mEventListener.onEventOccurred();
        }
    }
}

这是您在活动中使用它的方式:

            mMyCustomSpinner.setEventListener(new IMyEventListener() {

                @Override
                public void onEventOccurred() {
                    // TODO Auto-generated method stub

                }
            });

这不是设置一个会泄漏的循环引用吗? - Sojurn
@Sojurn:观察者模式并不完美。实际上,没有什么是完美的,但在Java世界(和Android)中,它是迄今为止最好的解决方案。“它会导致内存泄漏吗?”- 是的,但只有在您没有正确使用它时才会发生。例如-如果您在视图的托管活动之外实现侦听器,则可能会在活动生命周期中未在正确时间删除侦听器而导致泄漏。通常,当您想要对视图事件做出反应时,您会在活动中实现回调,因此您不需要担心这个问题。 - Tal Kanel

5
当用户进行选择时,我正在尝试找出与所嵌入的Activity通信的正确方法。
你不需要“与所嵌入的Activity通信”,你需要与负责View的控制器进行通信。今天,这可能是一个Activity。明天,这可能是一个Fragment。
我看到OnItemSelected监听器会得到Adapter的引用,但我不确定是否应该使用此Adapter并向上遍历其父级链。
这意味着View知道特定类型的Adapter,因为Adapter接口没有任何形式的getContext()方法。此外,它将您绑定到与Activity交互,这在此时不是一个好计划,如上所述。
就我个人而言,我对首先拥有自定义Spinner子类持怀疑态度。但是,假设有很好的理由,那么您应该遵循Tal Kanel的建议(在我写这篇文章时发布),为自定义View声明的此自定义事件设计自定义侦听器接口。让控制器(Activity或Fragment)提供该接口的实现-这可以直接在控制器上实现,也可以作为匿名内部类实现(如Tal Kanel的答案中)。让您的自定义View根据需要调用侦听器接口上的方法。

也许我没有表达清楚。我之所以问这个问题,正是因为我想让“Spinner是哪个子项”的含义模糊不清。实际上,我自定义Spinner的原因是,我认为Android Spinner完全没用,因为它只有一个索引和一个字符串,如果所选项目需要伴随某种类型的对象(在HTML世界中称为“数据”),则始终需要大量混乱的代码与之配合。我不知道Google的开发人员为什么会忽略这个功能(或者我错过了什么?) - Yevgeny Simkin
所以,我需要一种方法,在Spinner驻留在(当前的)Activity后添加监听器,并使其能够在用户选择某些内容时回调到Activity,此时Activity将接管并根据选定的项目执行必要的操作。因此,鉴于我正在Activity中构建此监听器,我认为我可以只需创建一个Context引用并使用它?是吗?还是由于某些原因不建议这样做? - Yevgeny Simkin
1
@Dr.Dredel:“Android Spinner完全没用,因为它只有一个索引和一个字符串”--一个AdapterView有一个索引和一个AdapterAdapter适配Object,而不是String。当然,你可以有一个适配StringVideoRestaurantResolveInfo或其他任何东西的AdapterAdapterView(其中包括Spinner)与String无关。 - CommonsWare
2
@Dr.Dredel:“还是因为某些原因不建议这样做?”-- 这就是我的答案的关键。Tal Kanel的答案和MisterSquonk的评论也都表达了同样的意见。现在,你已经有三个人告诉你要为你的自定义View的自定义事件创建一个自定义监听器接口。无论你将此监听器接口实现在Activity上还是在Activity传递给自定义View的其他东西上,都由你决定。 - CommonsWare
@Dr.Dredel: “根据经验,创建自定义适配器比创建自定义下拉框需要更多的工作”-- 实际上应该是更少的工作。此外,它也更加健壮(组合优于继承)。但最重要的是,这就是 AdapterView 的工作方式。我从未见过任何一个例子,有人会为了避免创建“适配器”而子类化 Spinner 这样的东西。 - CommonsWare
显示剩余5条评论

1

正确的方法是使用某种监听器。我认为你可以直接引用,但你的代码就不能在另一个项目中重复使用了...


就像我说的,我有一个监听器...它是Spinner.OnItemSelectedListener。当用户选择某些内容时,我想在父Activity中执行一些操作。我正在Activity中创建一个自定义监听器(确切地说,我希望它可以重复使用),并将该监听器传递到我的自定义Spinner中。我不太清楚的关键点是如何拥有一个通用的Spinner,以便我可以动态地让它与它所在的任何Activity进行通信。 - Yevgeny Simkin
3
当用户选择某物时,我想在父 Activity 中执行一些操作。"view"或任何其他UI元素控制"Activity"并非其责任。 "view"应尽量不知道其"父级"(使用通用术语)的存在。当监听器回调方法被调用时,Activity有责任知道它想要做什么,并相应地采取行动。 - Squonk
@MisterSquank,我们在谈论同一件事情。当用户更改下拉菜单时,我需要在该下拉菜单所在的 Activity 中发生某些事情。我使用 OnItemSelectedListener 来调用此操作。您是否建议有更好的方法让 Activity 知道有人对下拉菜单做出了更改? - Yevgeny Simkin

1
一个简单的解决方案 -
((ParentClass) context).functionToRun();

其中ParentClass是该活动的类名。


这是一个简单的解决方案,但应该避免使用,因为它将视图与其父级耦合在一起。理想情况下,您希望视图是可重用的,并且独立于使用视图的任何活动/片段。 - tronman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接