Обработка сенсорных событий для RecyclerView с часто меняющимися данными - PullRequest
0 голосов
/ 30 октября 2019

Сценарий:

  • RecyclerView с RecyclerView.Adapter. notifyDataSetChanged() -метод вызывается каждые 10 мс, потому что отображается чувствительная ко времени информация, поэтому ViewHolders (класс расширяется RecyclerView.ViewHolder) часто обновляется.
  • Базовые данные поступают из собственной C-библиотеки, поэтомуметоды адаптера переопределяются для запроса данных из собственной библиотеки.
  • , полученный из этого наилучшего видео интерфейс был реализован в классе адаптера для передачи onTouch-Events изАдаптер для деятельности. Поэтому класс ViewHolder реализует View.OnTouchListener и передает все события касания и позицию listItem (запрашивается getAdapterPosition()) через интерфейс в Activity.
  • различные события onTouch (Click, LongClick, Swipe и т. Д. .) для каждого ListItem должен быть распознан в действии.

Проблема:

При касании ListItem вскоре после вызова notifyDataSetChanged(),получены следующие значения:

1 getAdapterPosition(): -1

2 motionEvent.getAction(): ACTION_DOWN, за которыми следует ACTION_CANCEL

android-документация гласит:

Обратите внимание, что если вы вызывали notifyDataSetChanged (), до следующего прохода макета возвращаемое значение этого метода будетбыть NO_POSITION.

Так что я предполагаю, что ошибка возникает каждый раз, когда происходит касание listItem, в то время как notifyDatasetChanges() обновляет viewHolders: getAdapterPosition() возвращает -1 в соответствии с документацией. И, возможно, из-за того, что элементы в viewHolder обновляются, onTouchEvent выдает ACTION_CANCEL.

, что я пытался:

  1. Я пытался остановитьобновление RecyclerView на ACTION_DOWN, но из-за события ACTION_CANCEL я не получаю никакого события ACTION_UP и не могу перезапустить обновление данных.
  2. Я добавил RecyclerView.OnItemTouchListener() кпросмотрщик в действии для получения TouchEvent в onInterceptTouchEvent() перед его передачей в listItem и его onTouchListener и прекращением обновления там просмотра. Но, поскольку мне по-прежнему нужна информация о том, по какому элементу щелкнули, мне все еще нужны элементы на TouchListener, который по-прежнему возвращает -1 и ACTION_CANCEL.

Вопрос:

Как лучше всего обрабатывать события onTouch в RecyclerView, который часто обновляет свои данные?

Класс активности

public class Activity extends AppCompatActivity implements DataStoreDataAdapter.OnListItemTouchListener {



    Handler dataUpdateHandler = new Handler();

    Runnable timerRunnable = new Runnable() {
        @Override
        public void run() {
            dataStoreDataAdapter.notifyDataSetChanged();

            dataUpdateHandler.postDelayed(this, 10);
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity);

        dataStoreDataAdapter = new DataStoreDataAdapter(getApplicationContext(),this);

        recyclerView = findViewById(R.id.list_view);
        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(dataStoreDataAdapter);

        recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
            @Override
            public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
                Log.i(TAG, "onInterceptTouchEvent: " + " touched by type " + e);
                int action = e.getAction();
                if (action == MotionEvent.ACTION_DOWN) {
                    dataUpdateHandler.removeCallbacks(timerRunnable);
                } else if (action == MotionEvent.ACTION_UP) {
                    dataUpdateHandler.post(timerRunnable);
                }
                return false;
            }

            @Override
            public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
                Log.i(TAG, "onTouchEvent: " + " touched by type " + e);
            }


            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

            }
        });


        //set update Handler
        dataUpdateHandler.post(timerRunnable);
    }

    @Override
    public void onListItemTouch(int position, MotionEvent motionEvent) {
        Log.i(TAG, "onListItemTouch: ListItem position " + position + " motionEvent: " + motionEvent);
    }
}

Класс адаптера

public class DataStoreDataAdapter extends RecyclerView.Adapter<DataStoreDataAdapter.ViewHolder> {

      private OnListItemTouchListener onListItemTouchListener;

    static class ViewHolder extends RecyclerView.ViewHolder implements View.OnTouchListener{
        View view;
        TextView Name;
       // ...  

        ViewHolder(@NonNull View itemView, OnListItemTouchListener onListItemTouchListener) {
            super(itemView);
            this.Name = itemView.findViewById(R.id.NameListItem);
            // ...

            this.onListItemTouchListener = onListItemTouchListener;
            itemView.setOnTouchListener(this);
        }

        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            onListItemTouchListener.onListItemTouch(getAdapterPosition(), motionEvent);
            return true;
        }
    }

    public interface OnListItemTouchListener{
        void onListItemTouch(int position, MotionEvent motionEvent);
    }

    public DataStoreDataAdapter(Context context, OnListItemTouchListener onListItemTouchListener) {
        super();
        this.onListItemTouchListener = onListItemTouchListener;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view = inflater.inflate(R.layout.list_item,parent,false);

        return new ViewHolder(view, onListItemTouchListener);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        // get the stored data for this position
        // ...get stuff from native library

        // put data into view elements...

    }

    @Override
    public int getItemCount() {
        return // ... itemCount from native library;
    }

}

Ответы [ 2 ]

0 голосов
/ 04 ноября 2019

Я понял это сам. Вот ответы:

  1. getAdapterPosition(): -1: Это происходит, как я уже упоминал в вопросе, потому что notifyDataSetChanged() перестраивает весь макет. Таким образом, если элемент списка перемещается между перестроением макета и проходом макета, функция вернет -1. Финн Маркуарт предложил одно решение: используйте DiffUtils, чтобы уменьшить количество обновлений до минимума для каждого элемента в списке. Это не сработало для меня, потому что мне нужно обновлять элемент очень часто. Таким образом, мое решение было использовать notifyItemRangeChanged(0,devicedataDataAdapter.getItemCount()) вместо notifyDataSetChanged(), потому что это только обновляет элементы в окне просмотра и не воссоздает весь список.
  2. во избежание перехватасобытие касания в держателе вида : некоторое более высокое представление перехватило жест касания *, так что реализованный метод onTouch() для каждого держателя вида получает событие ACTION_CANCEL и больше не был бы проинформирован о жесте (*Событие 1018 * может быть получено только в родительских представлениях, таких как OnItemTouchListener() из recyclerview). Ответственный за это был элемент Аниматор , который является членом рециркуляции. После отключения itemAnimator путем установки его в null, сенсорные жесты больше не перехватываются:

recyclerView.setItemAnimator(null);

Надеюсь, этот ответ поможет кому-нибудь там. Я трачу много времени, чтобы понять это:)


* Я определяю "жест" как сумму всех событий касания между ACTION_DOWN и событием ACTION_UP.

0 голосов
/ 31 октября 2019

Что происходит, я думаю, что notifyDataSetChanged все еще выполняется, в то время как касание происходит во время него, у элементов нет позиции, и я не думаю, что это можно исправить.

Слушатель в порядке, на мой взгляд,что вы действительно должны изменить, так это часть notifyDataSetChanged ().

вы могли бы использовать вместо этого DiffUtil, чтобы обновлялись только те элементы, которые действительно изменены (как в позиции, так и в контенте).

здесьбольше информации, как это реализовать: https://medium.com/@iammert/using-diffutil-in-android-recyclerview-bdca8e4fbb00

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...