2010-12-01 5 views
2

Я делаю операцию выбора даты, которая выглядит как прокручиваемый 30-дневный месяц/календарь (подумайте о календаре Outlook). Средство выбора даты содержит ListView (для прокрутки) видов MonthView, каждый из которых представляет собой TableView отдельных дней. Каждый отдельный день в MonthView - это кнопка. Когда MonthView инстанциируется я хожу каждый из дней (кнопки) и прикрепить щелчок слушатель:Android: кнопка в ListView не принимает события onClick

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

setSelectedDate делает множество вещей, но он также включает фон кнопки на желтый для обозначения выбирается дата.

На моем эмуляторе все работает так, как вы ожидали. Активность появляется, вы нажимаете день, день становится желтым. Нет проблем.

Тем не менее, на некоторых эмуляторах моего партнера и на физических устройствах, когда вы касаетесь дня, ничего не происходит ... пока вы не прокрутите ListView ... а затем вдруг выбранный день станет желтым. Так, например, вы касаетесь «третьего», а затем ничего не происходит. Подождите несколько секунд, а затем прокрутите список ListView (касаясь области календаря, которая НЕ является третьей), и как только ListView прокручивает 3-й, магически становится желтым.

На моих одноранговых эмуляторах, которые показывают это поведение, я могу установить точку останова в первой строке onClick, и я вижу, что BP фактически не попадает, пока прокручивается ListView.

Такое поведение для меня не имеет никакого смысла. Я бы предположил, что поведение onClick не связано с инкапсулирующими усилиями прокрутки View.

Любые мысли о том, почему это может быть так, и как я могу исправить ситуацию, так что onClicks всегда происходит сразу же при нажатии кнопки?

Спасибо.

сообщение Священное писание: 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); 
    } 

} 
+0

Вы можете разместить полный код вашего адаптера и вашего ListActivity пожалуйста? – nbarraille 2010-12-01 18:40:12

ответ

2

Имея такую ​​же проблему, ищет ответ. Я полностью не верил, когда не получил мой метод onClick, пока не прокрутил список. Я отправлю ответ здесь, если найду его.

Прямо сейчас, моя догадка, чтобы попробовать различные события, кроме мыши (потому свитка пространство ест сложные сенсорные события, которые превращаются в событие Click):

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

Таким образом, я добавлю полностью отдельный ответ на этот вопрос вне ручного составления ваших собственных событий кликов из событий касания.

Я обменял некоторые электронные письма с командой Android (есть несколько преимуществ от использования googly), и они предположили, что моя попытка реализовать ListAdapter вручную была неэффективной и что, если я не правильно подключу наблюдателя данных методы адаптера могут вызвать «смешные проблемы с обработкой событий»."

Так что я сделал следующее:.

1) Заменил мою реализацию ListAdapter с подклассом BaseAdapter, что перегрузили необходимые функции

2) Остановился с помощью list.invalidateViews() и начал использовать адаптер .notifyDataChanged()

и ошибка, кажется, ушли.

это больше работы, чем вручную сочинял событие щелчка, но это также более правильный код в долгосрочной перспективе.

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, но временно, я советую против использования адаптера, содержащего массив представлений.

0

Aswer является:

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