安卓:ListView控件按钮在未接受的onclick事件 [英] Android: Button within ListView not receiving onClick events

查看:156
本文介绍了安卓:ListView控件按钮在未接受的onclick事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想提出一个日期选择器的活动,看起来像一个滚动的每月30天/日历(想想Outlook日历)。日期选择器包含一个ListView(滚动)的MonthView认为每一个都是单独的天TableView中。每个单独的一天在MonthView是一个按钮。当MonthView被实例化我走各的日子(按钮),并附加一个点击监听器:

 最后键b = getButtonAt(I)
b.setOnClickListener(新OnClickListener()
{
      @覆盖
      公共无效的onClick(视图v){
            setSelectedDate(buttonDayClosure,B);
      }
});

setSelectedDate做各种各样的事情,但它也变为按钮的背景为黄色,以表示的日期被选择

在我的模拟器,一切都按你期望的那样。活动出现时,你preSS一天,这一天变成黄色。没有问题。

不过,我的一些同行的仿真器和在物理设备上,当你触摸了一天什么也没发生...直到您滚动的ListView ...然后突然选定日期变成黄色。因此,例如,你触摸第三,然后什么也没有发生。等待几秒钟,然后滚动ListView控件(触摸这不是第3日历的区域),并尽快的ListView滚动第三神奇变成黄色。

在,显示这种行为我同行模拟器,我可以设置的onClick的拳头线路断点,我看到BP其实不是打,直到ListView的滚动。

此行​​为没有任何意义,我。我期望的onClick行为是无关的封装视图的滚动工作。

这是为什么会出现这种情况,我该如何纠正这种情况,这样,当按钮被触动onClicks总是立即发生的任何想法?

感谢。

发表Scriptus:ArrayAdapter和ListView code要求:

 公共类MonthArrayAdapter扩展ArrayAdapter<日期和GT; {    私人MonthView [] _​​views;    私人矢量<&程序LT;日期和GT;> _dateSelectionChangedListeners =新的矢量<&程序LT;日期和GT;>();    公共MonthArrayAdapter(上下文的背景下,INT textViewResourceId,日期minSelectableDay,日期maxSelectableDay){
        超(背景下,textViewResourceId);
        INT zeroBasedMonth = minSelectableDay.getMonth();
        年整型= 1900 + minSelectableDay.getYear();        如果(minSelectableDay.after(maxSelectableDay))
        {
            抛出新抛出:IllegalArgumentException(闽天不能最大一天。);
        }        日期prevDay = minSelectableDay;
        INT NUMMONTHS = 1;
        为(日期I = minSelectableDay;!即日(ⅰ,maxSelectableDay); I = i.addDays(1))
        {
            如果(i.getMonth()!= prevDay.getMonth())
            {
                NUMMONTHS ++;
            }
            prevDay = I;
        }        _views =新MonthView [NUMMONTHS]        的for(int i = 0; I< NUMMONTHS;我++)
        {
            日期monthDate =新的日期(新的GregorianCalendar(年,zeroBasedMonth,1,0,0).getTimeInMillis());
            日期startSunday = findStartSunday(monthDate);
            this.add(monthDate);
            _views [I] =新MonthView(this.getContext(),startSunday,minSelectableDay,maxSelectableDay);            zeroBasedMonth ++;
            如果(zeroBasedMonth == 12)
            {
                今年++;
                zeroBasedMonth = 0;
            }
        }        对于(最终MonthView一:_views)
        {
            a.addSelectedDateChangedListener(新程序< MonthView>()
            {
                        @覆盖
                    公共无效的execute(MonthView输入){                        对于(最终MonthView乙:_views)
                        {
                            如果(一个!= b)的
                            {
                                b.clearCurrentSelection();
                            }
                        }                        对于(程序和LT;日期和GT;监听器:_dateSelectionChangedListeners)
                        {
                            listener.execute(a.getSelectedDate());
                        }
                    }
            });
        }
    }    无效addSelectedDateChangedListener(程序<日期和GT;监听器)
    {
        _dateSelectionChangedListeners.add(监听);
    }    私人布尔即日(日期一,日期二)
    {
        返回a.getYear()== b.getYear()及&放大器; a.getMonth()== b.getMonth()及&放大器;
               a.getDate()== b.getDate();
    }    @覆盖
    公共查看getView(INT位置,查看convertView,父母的ViewGroup)
    {
        返回_views [位置]
    }
    私人日期findStartSunday(日期D)
    {
        返回d.subtractDays(d.getDay());
    }    公共无效setSelectedDate(日期为准)
    {
        对于(MonthView MV:_views)
        {
            mv.setSelectedDate(日期);
        }
    }}

 公共类的DatePicker扩展ActivityBase {    公共静态最后弦乐CHOSEN_DATE_RESULT_KEY =resultKey;    公共静态最后弦乐MIN_SELECTABLE_DAY = DatePicker.class.getName()+MIN;    公共静态最后弦乐MAX_SELECTABLE_DAY = DatePicker.class.getName()+MAX;    。私有静态最后弦乐SELECTED_DATE = UUID.randomUUID()的toString();    私人长期_selectedDate = -1;    私人MonthArrayAdapter _monthArrayAdapter;    @覆盖
    公共无效的onCreate(捆绑savedInstanceState){
        super.onCreate(savedInstanceState);        现在日期=新的日期();        捆绑投入= this.getIntent()getExtras()。        龙敏= inputs.getLong(MIN_SELECTABLE_DAY,0);        日期minSelectableDate;
        如果(分== 0)
        {
            minSelectableDate =新的日期(现在的);
        }
        其他
        {
            minSelectableDate =新的日期(分钟);
        }
        Log.i(DatePicker.class.getName(),最小日期=+ minSelectableDate.toString());
        长最大= inputs.getLong(MAX_SELECTABLE_DAY,0);        日期maxSelectableDate;
        如果(最大== 0)
        {
            maxSelectableDate =新的日期(now.addDays(35).getTime());
        }
        其他
        {
            maxSelectableDate =新的日期(最大值);
        }
        的setContentView(R.layout.date_picker);        按钮doneButton =(按钮)findViewById(R.id.DatePickerDoneButton);        如果(doneButton == NULL)
        {
            Log.e(this.getClass()的getName(),无法从视图id找到doneButton。);
            完();
            返回;
        }        doneButton.setOnClickListener(新OnClickListener()
        {
            @覆盖
            公共无效的onClick(视图v){
                意向书结果=新的Intent();
                result.putExtra(CHOSEN_DATE_RESULT_KEY,_selectedDate);
                的setResult(RESULT_OK,结果);
                完();
            }
        });        按钮cancelButton =(按钮)findViewById(R.id.DatePickerCancelButton);        如果(cancelButton == NULL)
        {
            Log.e(this.getClass()的getName(),无法从视图id找到cancelButton。);
            完();
            返回;
        }        cancelButton.setOnClickListener(新OnClickListener()
        {
            @覆盖
            公共无效的onClick(视图v){
                的setResult(RESULT_CANCELED,NULL);
                完();
            }
        });        LV的ListView =(ListView控件)findViewById(R.id.DatePickerMonthListView);
        lv.setDividerHeight(0);
        _monthArrayAdapter =
            新MonthArrayAdapter(这一点,android.R.layout.simple_list_item_1,minSelectableDate,maxSelectableDate);        _monthArrayAdapter.addSelectedDateChangedListener(新程序<日期及GT;()
                {                    @覆盖
                    公共无效执行(数据输入){
                        _selectedDate = input.getTime();                    }
                });       lv.setAdapter(_monthArrayAdapter);
    }    @覆盖
    公共无效onRestoreInstanceState(捆绑savedInstanceState)
    {
        如果(savedInstanceState.containsKey(SELECTED_DATE))
        {
            _selectedDate = savedInstanceState.getLong(SELECTED_DATE);
            _monthArrayAdapter.setSelectedDate(新日期(_selectedDate));
        }
    }    @覆盖
    公共无效的onSaveInstanceState(捆绑savedInstanceState)
    {
        savedInstanceState.putLong(SELECTED_DATE,_selectedDate);
    } }


解决方案

主要的原因是,ListView控件不喜欢有意见阵列的适配器。

所以,问题是由

触发

 公共查看getView(INT位置,查看convertView,父母的ViewGroup)
{
    返回_views [位置]
}

当在ListView控件code寻找(或相反,它的父母AbsListView.obtainView方法),你会看到code像

 如果(scrapView!= NULL){
        ...
        孩子= mAdapter.getView(位置,scrapView,这一点);
        ...
        如果(孩子!= scrapView){
            mRecycler.addScrapView(scrapView);

这可能发生 getView(位置,...)被调用scrapView!= _views [位置],因此scrapView将被回收。在另一方面,它很可能是同样的看法也再次加入到ListView控件,导致有一个奇怪的状态意见(见<一href=\"http://stackoverflow.com/questions/11257357/listview-haswindowfocus-true-but-child-views-haswindowfocus-false\">this问题)

这最终应固定在ListView的IMO,但暂时,我建议不要使用含有意见阵列的适配器。

I am making a date picker activity that looks like a scrolling 30 day month/calendar (think Outlook calendar). The date picker contains a ListView (for scrolling) of MonthView views each of which is a TableView of the individual days. Each individual day in the MonthView is a button. When the MonthView is instantiated I walk each of the days (buttons) and attach a click listener:

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

setSelectedDate does a variety of things, but it also turns the button's background to yellow to signify the date is selected.

On my emulator, everything works as you would expect. Activity comes up, you press a day, the day turns yellow. No problems.

However, on some of my peer's emulators and on physical devices when you touch a day nothing happens... until you scroll the ListView... and then all of a sudden the selected day turns yellow. So, for example, you touch "the 3rd" and then nothing happens. Wait a few seconds and then scroll the ListView (touching an area of the calendar that is NOT the 3rd) and as soon as ListView scrolls the 3rd magically turns yellow.

On my peer emulators that show this behavior, I can set a breakpoint on the fist line of onClick and I see that the BP is in fact not hit until the ListView is scrolled.

This behavior doesn't make any sense to me. I would expect the onClick behavior to be unrelated to the encapsulating View's scrolling efforts.

Any thoughts on why this might be the case and how I can rectify the situation so that onClicks always happen immediately when the button is touched?

Thanks.

Post Scriptus: ArrayAdapter and ListView code requested:

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);
        }
    }



}

and

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);
    }

 }

解决方案

The main reason is that ListView doesn't like an adapter having an array of views.

So the problem is triggered by

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

When looking at the ListView code (or rather it's parents AbsListView.obtainView method) you'll see code like

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

It can happen that getView(position,...) is called with scrapView != _views[position] and hence scrapView will be recycled. On the other hand, it is quite likely that the same view is also added again to ListView, resulting in views having a weird state (see this issue)

Ultimately, this should be fixed in ListView IMO, but temporarily, I advise against using an adapter containing an array of views.

这篇关于安卓:ListView控件按钮在未接受的onclick事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆