1

从最近几天开始,我试图让 Spinner 在 ViewGroup 中工作但没有任何成功。:-{。在自定义 ViewGroup 中,我放置了片段容器。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:boxMenu="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TestActivity"
android:id="@+id/frame" >

<com.imper.boxmenulibrary.BoxMenuLayout
    android:id="@+id/box_menu" 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|right"
    boxMenu:boxSize="40dp"
    boxMenu:directionY="bottomToTop"
    boxMenu:directionX="rightToLeft"
    boxMenu:showStart="false"
    android:padding="5dp"
    android:background="#ffffff00">

    <LinearLayout 
        android:id="@+id/fragment_container"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top|center"
        android:orientation="vertical">

    </LinearLayout>

</com.imper.boxmenulibrary.BoxMenuLayout>

在那个片段容器中,我像这样替换片段:

FragmentManager fm = ((Activity) context).getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_container, new TestFragment());
ft.commit();

我的 TestFragment xml 布局如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <Spinner 
        android:id="@+id/test_spinner"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

TestFragment 实现如下所示:

public class TestFragment extends BoxLayout implements OnItemSelectedListener {

Spinner testSpinner;

public TestFragment() {}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
    View view = inflater.inflate(R.layout.test_view, container, false);

    testSpinner = (Spinner) view.findViewById(R.id.test_spinner);
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(container.getContext(), R.array.day, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    testSpinner.setAdapter(adapter);
    testSpinner.setOnItemSelectedListener(this);

    return view;
}

@Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
    Toast.makeText(getActivity(), "Bingo", Toast.LENGTH_SHORT).show();
    Log.d("Bingo", "onItemSelected");
}

@Override
public void onNothingSelected(AdapterView<?> arg0) {
    Toast.makeText(getActivity(), "Bingo Nothing", Toast.LENGTH_SHORT).show();
    Log.d("Bingo", "onNothingSelected");
}

}

现在,问题是当我尝试从 Spinner 下拉列表中选择某些内容时,没有选择任何内容,根本不会触发 OnItemSelected 事件。

当 FragmentContainer 从我的自定义 ViewGroup 中移出时,相同的代码工作正常。为什么会这样???如何让 Spinner 在自定义视图组中正常工作?

4

3 回答 3

2

第二次编辑 --------------

我遇到了一个和你非常相似的问题。(有关更多详细信息,请参阅下面的原始帖子。)在不了解您的自定义 ViewGroup 实施的情况下,我不知道我的解决方案是否适合您。

我的问题原来是在我的“layoutChildren”实现中我引入了一个“优化”,如果我正在重用一个我知道内容没有改变的子视图(并且布局窗口也没有改变)我没有费心重新测量子视图。

事实证明,如果您使用 Spinners,您将无法摆脱这一点。原因与某些 View.mPrivateFlags(PFLAGS_FORCE_LAYOUT 和 PFLAGS_LAYOUT_REQUIRED)的实现方式复杂有关,与跟踪重新处理子视图布局的需要结合起来,Spinner 决定触发 onItemSelected 侦听器的方式。

如下所述,当 Spinner 被要求执行布局并在其中识别出所选项目已更改时,它会触发 onItemSelected 侦听器。如果它不执行布局,它(内部)知道所选项目已更改,但不会调用 onItemSelected 侦听器。

从 Spinner 的角度来看,这没问题,因为当 Spinner 看到新项目被选中时,它专门请求了一个布局传递(requestLayout)。

布局过程开始并向下传播到我的自定义 ViewGroup。当它到达第一个孩子时,该孩子的 PFLAGS_FORCE_LAYOUT 位已设置……但该标志被处理逻辑忽略。相反,它只寻找 PFLAGS_LAYOUT_REQUIRED 位,当它没有看到该标志时,它决定没有必要在视图层次结构中进一步传播布局,因此 Spinner 永远不会被要求执行布局并且永远不会触发onItemSelected 监听器。

修复是删除优化以不测量尺寸已知的子视图。PFLAGS_LAYOUT_REQUIRED (这是包私有的......你不能从你写的任何东西中得到它)只有在子视图被要求测量自己时才被设置,即使在我的情况下测量本身并不是绝对必要的,它是将 mPrivateFlags 操纵到正确状态的唯一方法。

检查以确保您正在测量您的子视图(特别是从之前的布局传递中重复使用的视图),看看这是否解决了您的问题。

第一次编辑---------------

我在这方面取得了一些进展,但仍未解决。Spinner 在调用其 onLayout() 方法时调用 onItemSelected 侦听器,并且它识别出所选项目已更改。

当 Spinner 由 ListView 托管时,在 Spinner 中的选择发生更改后调用 ListView.onLayout(...)(尽管我无法弄清楚如何),并且 ListView onLayout 方法会传播该调用向下到 Spinner,导致 onItemSelected() 调用。

当 Spinner 由我的自定义 AdapterView 托管时,在 Spinner 更改后永远不会调用 onLayout 方法,因此不会进行 onItemSelected() 调用。

我只是还不能弄清楚为什么、在哪里或如何在一种情况下触发布局请求,而在另一种情况下则没有。因为布局请求被发布到消息队列并从那里执行,所以很难确定它的来源。

让我知道这是否对您有任何帮助或给您任何想法。

原帖------------

我有同样的问题。我创建了一个自定义 ViewGroup(扩展 AdapterView),现在包含微调器的子视图不会触发 onItemSelected 侦听器。如果我将相同的子视图附加到 ListView(也从 AdapterView 扩展)它工作正常。我在自定义 ViewGroup 中做错了,但我不知道是什么。

你找到解决问题的方法了吗?如果是这样,你可以发布它吗?

于 2013-10-08T21:16:51.340 回答
0

这篇文章很旧,但它对我帮助很大——尽管我仍然遇到问题(尽管每隔一次我从下拉列表中选择一些东西——而不是每次)。

如果有帮助 - 我的问题的解决方法是不要在显示问题的微调器对象上调用 BringToFront。

于 2015-01-16T21:44:18.833 回答
0

正如史蒂夫已经指出的那样,问题在于需要操纵某些标志才能实际强制布局,而这样做的唯一(公共)方法是调用 measure() ...

作为一种解决方法,将以下方法添加到我的自定义 ViewGroup 为我解决了这个问题,但您可能需要确保不要进行太多额外/冗余的测量,具体取决于实际用例......

public void requestLayout() {
  super.requestLayout();
  measure(MeasureSpec.EXACTLY | getMeasuredWidth(),
          MeasureSpec.EXACTLY | getMeasuredHeight());
}

重现问题的最小代码片段:

package org.kobjects.spinnertestapp;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import java.util.ArrayList;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ArrayList<String> options = new ArrayList<>();
    options.add("Hello");
    options.add("Wutan");
    options.add("Lorem Ipsum");

    ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this,android.R.layout.simple_spinner_dropdown_item, options);
    Spinner spinner = new Spinner(this);
    spinner.setAdapter(arrayAdapter);

    MyLayout layout = new MyLayout(this);
    layout.addView(spinner);
    setContentView(layout);
  }

  static class MyLayout extends ViewGroup {
    public MyLayout(Context context) {
        super(context);
    }
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        for (int i = 0; i < getChildCount(); i ++) {
            getChildAt(i).layout(left, top, right, bottom);
        }
    }
  }
}
于 2018-03-06T21:23:59.857 回答