【问题标题】:Android: Button within ListView not receiving onClick eventsAndroid:ListView 中的按钮未接收到 onClick 事件
【发布时间】:2013-09-02 06:12:02
【问题描述】:

我正在制作一个日期选择器活动,它看起来像一个滚动的 30 天月/日历(想想 Outlook 日历)。日期选择器包含 MonthView 视图的 ListView(用于滚动),每个视图都是各个日期的 TableView。 MonthView 中的每一天都是一个按钮。当 MonthView 被实例化时,我每天都会走动(按钮)并附加一个点击监听器:

final Button b = getButtonAt(i);
b.setOnClickListener(new OnClickListener()
{
      @Override
      public void onClick(View v) {
            setSelectedDate(buttonDayClosure, b);
      }
});

setSelectedDate 做了很多事情,但它也会将按钮的背景变成黄色以表示日期被选中。

在我的模拟器上,一切正常。活动一来,你按一天,天就变黄了。没问题。

但是,在我的一些同行模拟器和物理设备上,当您触摸某一天时,什么也没有发生...直到您滚动 ListView...然后突然选定的日期变为黄色。因此,例如,您触摸“第 3 个”,然后什么也没有发生。等待几秒钟,然后滚动 ListView(触摸日历中不是第三个的区域),一旦 ListView 滚动,第三个就会神奇地变成黄色。

在显示此行为的对等模拟器上,我可以在 onClick 的第一行设置断点,我看到实际上直到 ListView 滚动时才命中 BP。

这种行为对我来说没有任何意义。我希望 onClick 行为与封装 View 的滚动工作无关。

对于为什么会出现这种情况以及如何纠正这种情况以便在触摸按钮时总是立即发生 onClicks 有什么想法吗?

谢谢。

Post Scriptus:请求的 ArrayAdapter 和 ListView 代码:

public class MonthArrayAdapter extends ArrayAdapter<Date> {

    private MonthView[] _views; 

    private Vector<Procedure<Date>> _dateSelectionChangedListeners = new Vector<Procedure<Date>>();

    public MonthArrayAdapter(Context context, int textViewResourceId, Date minSelectableDay, Date maxSelectableDay) {
        super(context, textViewResourceId);
        int zeroBasedMonth = minSelectableDay.getMonth();
        int year = 1900 + minSelectableDay.getYear();

        if(minSelectableDay.after(maxSelectableDay))
        {
            throw new IllegalArgumentException("Min day cannot be after max day.");
        }

        Date prevDay = minSelectableDay;
        int numMonths = 1;
        for(Date i = minSelectableDay; !sameDay(i, maxSelectableDay); i = i.addDays(1) )
        {
            if(i.getMonth() != prevDay.getMonth())
            {
                numMonths++;
            }
            prevDay = i;
        }

        _views = new MonthView[numMonths];

        for(int i = 0; i<numMonths; i++)
        {
            Date monthDate = new Date(new GregorianCalendar(year, zeroBasedMonth, 1, 0, 0).getTimeInMillis());
            Date startSunday = findStartSunday(monthDate);
            this.add(monthDate);
            _views[i] = new MonthView(this.getContext(), startSunday, minSelectableDay, maxSelectableDay);

            zeroBasedMonth++;
            if(zeroBasedMonth == 12)
            {
                year++;
                zeroBasedMonth = 0;
            }
        }

        for(final MonthView a : _views)
        {
            a.addSelectedDateChangedListener(new Procedure<MonthView>()
            {
                        @Override
                    public void execute(MonthView input) {

                        for(final MonthView b: _views)
                        {
                            if(a != b)
                            {
                                b.clearCurrentSelection();
                            }
                        }

                        for(Procedure<Date> listener : _dateSelectionChangedListeners)
                        {
                            listener.execute(a.getSelectedDate());
                        }
                    }
            });
        }




    }

    void addSelectedDateChangedListener(Procedure<Date> listener)
    {
        _dateSelectionChangedListeners.add(listener);
    }

    private boolean sameDay(Date a, Date b)
    {
        return a.getYear() == b.getYear() && a.getMonth() == b.getMonth() &&
               a.getDate() == b.getDate();
    }

    @Override
    public View getView (int position, View convertView, ViewGroup parent)
    {
        return _views[position];
    }


    private Date findStartSunday(Date d)
    {
        return d.subtractDays(d.getDay());
    }

    public void setSelectedDate(Date date)
    {
        for(MonthView mv : _views)
        {
            mv.setSelectedDate(date);
        }
    }



}

和

public class DatePicker extends ActivityBase {

    public static final String CHOSEN_DATE_RESULT_KEY = "resultKey";

    public static final String MIN_SELECTABLE_DAY = DatePicker.class.getName() + "MIN";

    public static final String MAX_SELECTABLE_DAY = DatePicker.class.getName() + "MAX";

    private static final String SELECTED_DATE = UUID.randomUUID().toString();

    private long _selectedDate = -1;

    private MonthArrayAdapter _monthArrayAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Date now = new Date();

        Bundle inputs = this.getIntent().getExtras();

        long min = inputs.getLong(MIN_SELECTABLE_DAY, 0);

        Date minSelectableDate;
        if(min == 0)
        {
            minSelectableDate = new Date(now);
        }
        else
        {
            minSelectableDate = new Date(min);
        }
        Log.i(DatePicker.class.getName(), "min date = " + minSelectableDate.toString());
        long max = inputs.getLong(MAX_SELECTABLE_DAY, 0);

        Date maxSelectableDate;
        if(max == 0)
        {
            maxSelectableDate = new Date(now.addDays(35).getTime());
        }
        else
        {
            maxSelectableDate = new Date(max);
        }


        setContentView(R.layout.date_picker);

        Button doneButton = (Button) findViewById(R.id.DatePickerDoneButton);

        if(doneButton == null)
        {
            Log.e(this.getClass().getName(), "Could not find doneButton from view id.");
            finish();
            return;
        }

        doneButton.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v) {
                Intent result = new Intent();
                result.putExtra(CHOSEN_DATE_RESULT_KEY, _selectedDate);
                setResult(RESULT_OK, result);
                finish();
            }
        });

        Button cancelButton = (Button) findViewById(R.id.DatePickerCancelButton);

        if(cancelButton == null)
        {
            Log.e(this.getClass().getName(), "Could not find cancelButton from view id.");
            finish();
            return;
        }

        cancelButton.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v) {
                setResult(RESULT_CANCELED, null);
                finish();
            }
        });

        ListView lv = (ListView)findViewById(R.id.DatePickerMonthListView);
        lv.setDividerHeight(0);


        _monthArrayAdapter = 
            new MonthArrayAdapter(this, android.R.layout.simple_list_item_1, minSelectableDate, maxSelectableDate);

        _monthArrayAdapter.addSelectedDateChangedListener(new Procedure<Date>()
                {

                    @Override
                    public void execute(Date input) {
                        _selectedDate = input.getTime();

                    }


                });

       lv.setAdapter(_monthArrayAdapter);
    }

    @Override 
    public void onRestoreInstanceState(Bundle savedInstanceState)
    {
        if(savedInstanceState.containsKey(SELECTED_DATE))
        {
            _selectedDate = savedInstanceState.getLong(SELECTED_DATE);
            _monthArrayAdapter.setSelectedDate(new Date(_selectedDate));
        }
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState)
    {
        savedInstanceState.putLong(SELECTED_DATE, _selectedDate);
    }

 }

【问题讨论】:

  • 您能否发布您的适配器和您的 ListActivity 的完整代码?
  • 这里是适配器。等待 ListView。
  • 好的,nbarraille,希望对您有所帮助。

标签: android


【解决方案1】:

遇到同样的问题,求解答。当我在滚动列表之前没有得到我的 onClick 方法时,我完全不相信。如果我找到答案,我会在这里发布。

现在,我最好的猜测是尝试除点击之外的不同事件(因为滚动空间正在吞噬变成点击事件的复杂触摸事件):

“downView”是一个静态变量,用于跟踪被点击的元素。

view.setOnTouchListener(new View.OnTouchListener() {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      downView = v;
      return true;
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
      if (downView == v) {
        handleClick(v);
        return true;
      }
      downView = null;
    }
    return false;
  }
});

【讨论】:

    【解决方案2】:

    主要原因是 ListView 不喜欢具有视图数组的适配器。

    所以问题是由

    触发的
    public View getView (int position, View convertView, ViewGroup parent)
    {
        return _views[position];
    }
    

    查看 ListView 代码(或者更确切地说它是父级 AbsListView.obtainView 方法)时,您会看到类似

    的代码
        if (scrapView != null) {
            ...
            child = mAdapter.getView(position, scrapView, this);
            ...
            if (child != scrapView) {
                mRecycler.addScrapView(scrapView);
    

    可能会发生getView(position,...) 被scrapView != _views[position] 调用,因此scrapView 将被回收。另一方面,很可能同一个视图也被再次添加到 ListView 中,导致视图具有奇怪的状态(参见this issue)

    最终,这应该在 ListView IMO 中修复,但我暂时建议不要使用包含视图数组的适配器。

    【讨论】:

      【解决方案3】:

      因此,除了从触摸事件中手动组合您自己的点击事件之外,我将为此添加一个完全独立的答案。

      我与 Android 团队交换了一些电子邮件(googly 消耗了一些好处),他们建议我尝试手动实现 ListAdapter 效率低下,如果我没有正确连接数据观察器方法它可能会导致“事件处理的有趣问题”。

      所以我做了以下事情:

      1) 用覆盖必要功能的 BaseAdapter 的子类替换了我的 ListAdapter 实现。

      2) 停止使用 list.invalidateViews() 并开始使用 adapter.notifyDataChanged()

      这个错误似乎已经消失了。

      这比手动编写点击事件需要更多的工作,但从长远来看,它也是更正确的代码。

      【讨论】:

        【解决方案4】:

        回答是:

        public View getView(int position, View convertView, ViewGroup parent) {
            View v=makeMyView(position);
            v.setFocusableInTouchMode(false);
            return v;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-03-04
          • 1970-01-01
          • 2023-03-09
          • 1970-01-01
          相关资源
          最近更新 更多