可选择的RecyclerView性能问题

时间:2017-10-30 13:28:49

标签: android performance android-recyclerview

我目前正在尝试使用应用来编辑每周时间范围。 就像插座的机械定时器一样,但就我的每个工作日来说都是如此。

enter image description here

粒度在第一位是次要的(我猜它将是15或30分钟)。

我的方法是RecyclerView GridLayoutManagerArrayAdapter,其中包含每个单元格的项目。

要选择更多单元格,您可以长按单元格并拖动其他单元格。为此,我使用了以下库DragSelectRecyclerView的监听器。

它工作得很好,你可以选择相当不错的项目,但特别是在模拟器或较旧的手机上它非常慢和滞后。在调试logcat中,您还可以看到Choreographer在渲染视图和选择多个单元格时必须跳过很多帧。

enter image description here

是否有更好的方法来实现这种行为。或者代码中是否存在任何非常缓慢且糟糕的错误?

修改

notifyItemChanged(pos);更改为notifyItemRangeChanged(pos, pos);之后,其方式不那么迟钝,但仍然没有达到应有的效果。

我还删除了所有负责自动滚动的内容(这是我上面提到的库的一个功能),以使代码更简单。

这是Fragment

的来源
    public class TestFragment extends Fragment
    {
        @BindView(R.id.gridView_hours) GridView gridView_hours;
        @BindView(R.id.weekdays_container) LinearLayout weekdays_container;

        private String[] hours = new String[]{"00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"};
        private String[] hoursShort = new String[]{"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"};

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

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
        {
            View view = inflater.inflate(R.layout.fragment_test, container, false);
            ButterKnife.bind(this, view);

            for (int i = 1; i <= 7; i++)
            {
                RecyclerView recyclerView = new RecyclerView(getActivity());
                initAdapter(recyclerView);

                GridLayoutManager glm = new GridLayoutManager(getActivity(), 48, GridLayoutManager.VERTICAL, false);
                recyclerView.setLayoutManager(glm);

                weekdays_container.addView(recyclerView);

            }
            gridView_hours.setAdapter(new ArrayAdapter<String>(getActivity(), R.layout.hour_view, hoursShort));

    //        GridLayoutManager glm = new GridLayoutManager(getActivity(), 48, GridLayoutManager.VERTICAL, false);
    //        recyclerView.setLayoutManager(glm);

            return view;
        }

        private void initAdapter(RecyclerView recyclerView)
        {
            TestAutoDataAdapter adapter = new TestAutoDataAdapter(getActivity(), 48);
            recyclerView.setAdapter(adapter);

            DragSelectionProcessor dragSelectionProcessor = new DragSelectionProcessor(new DragSelectionProcessor.ISelectionHandler() {
                @Override
                public HashSet<Integer> getSelection() {
                    return adapter.getSelection();
                }

                @Override
                public boolean isSelected(int index) {
                    return adapter.getSelection().contains(index);
                }

                @Override
                public void updateSelection(int start, int end, boolean isSelected, boolean calledFromOnStart) {
                    adapter.selectRange(start, end, isSelected);
                }
            });

            DragSelectTouchListener dragSelectTouchListener = new DragSelectTouchListener()
                    .withSelectListener(dragSelectionProcessor);
            recyclerView.addOnItemTouchListener(dragSelectTouchListener);

            adapter.setClickListener(new TestAutoDataAdapter.ItemClickListener()
            {
                @Override
                public void onItemClick(View view, int position)
                {
                    adapter.toggleSelection(position);
                }

                @Override
                public boolean onItemLongClick(View view, int position)
                {
                    dragSelectTouchListener.startDragSelection(position);
                    return true;
                }
            });
        }
    }

RecyclerView.Adapter

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

        private int dataSize;
        private Context context;
        private ItemClickListener clickListener;

        private HashSet<Integer> selected;

        public TestAutoDataAdapter(Context context, int size)
        {
            this.context = context;
            dataSize = size;
            selected = new HashSet<>();
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
        {
            View view = LayoutInflater.from(context).inflate(R.layout.test_cell, parent, false);
            ViewHolder viewHolder = new ViewHolder(view);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position)
        {
            holder.tvText.setText("");
            if (selected.contains(position))
                holder.tvText.setBackgroundColor(Color.RED);
            else
                holder.tvText.setBackgroundColor(Color.WHITE);
        }

        @Override
        public int getItemCount()
        {
            return dataSize;
        }

        // ----------------------
        // Selection
        // ----------------------

        public void toggleSelection(int pos)
        {
            if (selected.contains(pos))
                selected.remove(pos);
            else
                selected.add(pos);
            notifyItemChanged(pos);
        }

        public void select(int pos, boolean selected)
        {
            if (selected)
                this.selected.add(pos);
            else
                this.selected.remove(pos);
            notifyItemRangeChanged(pos, pos);
        }

        public void selectRange(int start, int end, boolean selected)
        {
            for (int i = start; i <= end; i++)
            {
                if (selected)
                    this.selected.add(i);
                else
                    this.selected.remove(i);
            }
            notifyItemRangeChanged(start, end - start + 1);
        }

        public void deselectAll()
        {
            // this is not beautiful...
            selected.clear();
            notifyDataSetChanged();
        }

        public void selectAll()
        {
            for (int i = 0; i < dataSize; i++)
                selected.add(i);
            notifyDataSetChanged();
        }

        public int getCountSelected()
        {
            return selected.size();
        }

        public HashSet<Integer> getSelection()
        {
            return selected;
        }

        // ----------------------
        // Click Listener
        // ----------------------

        public void setClickListener(ItemClickListener itemClickListener)
        {
            clickListener = itemClickListener;
        }

        public interface ItemClickListener
        {
            void onItemClick(View view, int position);
            boolean onItemLongClick(View view, int position);
        }

        // ----------------------
        // ViewHolder
        // ----------------------

        public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener
        {
            public TextView tvText;

            public ViewHolder(View itemView)
            {
                super(itemView);
                tvText = itemView.findViewById(R.id.tvText);
                itemView.setOnClickListener(this);
                itemView.setOnLongClickListener(this);
            }

            @Override
            public void onClick(View view)
            {
                if (clickListener != null)
                    clickListener.onItemClick(view, getAdapterPosition());
            }

            @Override
            public boolean onLongClick(View view)
            {
                if (clickListener != null)
                    return clickListener.onItemLongClick(view, getAdapterPosition());
                return false;
            }
        }
    }

SelectTouchListener

    public class DragSelectTouchListener implements RecyclerView.OnItemTouchListener
    {
        private static final String TAG = "DSTL";

        private boolean mIsActive;
        private int mStart, mEnd;
        private int mLastStart, mLastEnd;

        private OnDragSelectListener mSelectListener;

        public DragSelectTouchListener()
        {
            reset();
        }

        /**
         * sets the listener
         * <p>
         *
         * @param selectListener the listener that will be notified when items are (un)selected
         */
        public DragSelectTouchListener withSelectListener(OnDragSelectListener selectListener)
        {
            this.mSelectListener = selectListener;
            return this;
        }

        // -----------------------
        // Main functions
        // -----------------------

        /**
         * start the drag selection
         * <p>
         *
         * @param position the index of the first selected item
         */
        public void startDragSelection(int position)
        {
            setIsActive(true);
            mStart = position;
            mEnd = position;
            mLastStart = position;
            mLastEnd = position;
            if (mSelectListener != null && mSelectListener instanceof OnAdvancedDragSelectListener)
                ((OnAdvancedDragSelectListener)mSelectListener).onSelectionStarted(position);
        }

        // -----------------------
        // Functions
        // -----------------------

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)
        {
            if (!mIsActive || rv.getAdapter().getItemCount() == 0)
                return false;

            int action = e.getAction();
            switch (action)
            {
                case MotionEvent.ACTION_POINTER_DOWN:
                case MotionEvent.ACTION_DOWN:
                    reset();
                    break;
            }

            return true;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e)
        {
            if (!mIsActive)
                return;

            int action = e.getAction();
            switch (action)
            {
                case MotionEvent.ACTION_MOVE:
                    updateSelectedRange(rv, e);
                    break;
                case MotionEvent.ACTION_CANCEL:
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                    reset();
                    break;
            }
        }

        private void updateSelectedRange(RecyclerView rv, MotionEvent e)
        {
            updateSelectedRange(rv, e.getX(), e.getY());
        }

        private void updateSelectedRange(RecyclerView rv, float x, float y)
        {
            View child = rv.findChildViewUnder(x, y);
            if (child != null)
            {
                int position = rv.getChildAdapterPosition(child);
                if (position != RecyclerView.NO_POSITION && mEnd != position)
                {
                    mEnd = position;
                    notifySelectRangeChange();
                }
            }
        }

        private void notifySelectRangeChange()
        {
            if (mSelectListener == null)
                return;
            if (mStart == RecyclerView.NO_POSITION || mEnd == RecyclerView.NO_POSITION)
                return;

            int newStart, newEnd;
            newStart = Math.min(mStart, mEnd);
            newEnd = Math.max(mStart, mEnd);
            if (mLastStart == RecyclerView.NO_POSITION || mLastEnd == RecyclerView.NO_POSITION)
            {
                if (newEnd - newStart == 1)
                    mSelectListener.onSelectChange(newStart, newStart, true);
                else
                    mSelectListener.onSelectChange(newStart, newEnd, true);
            }
            else
            {
                if (newStart > mLastStart)
                    mSelectListener.onSelectChange(mLastStart, newStart - 1, false);
                else if (newStart < mLastStart)
                    mSelectListener.onSelectChange(newStart, mLastStart - 1, true);

                if (newEnd > mLastEnd)
                    mSelectListener.onSelectChange(mLastEnd + 1, newEnd, true);
                else if (newEnd < mLastEnd)
                    mSelectListener.onSelectChange(newEnd + 1, mLastEnd, false);
            }

            mLastStart = newStart;
            mLastEnd = newEnd;
        }

        private void reset()
        {
            setIsActive(false);
            if (mSelectListener != null && mSelectListener instanceof OnAdvancedDragSelectListener)
                ((OnAdvancedDragSelectListener)mSelectListener).onSelectionFinished(mEnd);
            mStart = RecyclerView.NO_POSITION;
            mEnd = RecyclerView.NO_POSITION;
            mLastStart = RecyclerView.NO_POSITION;
            mLastEnd = RecyclerView.NO_POSITION;
        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)
        {
            // ignore
        }

        public void setIsActive(boolean isActive)
        {
            this.mIsActive = isActive;
        }

        // -----------------------
        // Interfaces and simple default implementations
        // -----------------------

        public interface OnAdvancedDragSelectListener extends OnDragSelectListener
        {
            /**
             * @param start      the item on which the drag selection was started at
             */
            void onSelectionStarted(int start);

            /**
             * @param end      the item on which the drag selection was finished at
             */
            void onSelectionFinished(int end);
        }

        public interface OnDragSelectListener
        {
            /**
             * @param start      the newly (un)selected range start
             * @param end        the newly (un)selected range end
             * @param isSelected true, it range got selected, false if not
             */
            void onSelectChange(int start, int end, boolean isSelected);
        }
    }

和所需接口的实现

    public class DragSelectionProcessor implements DragSelectTouchListener.OnAdvancedDragSelectListener {

        private ISelectionHandler mSelectionHandler;
        private HashSet<Integer> mOriginalSelection;
        private boolean mFirstWasSelected;
        private boolean mCheckSelectionState = false;

        public DragSelectionProcessor(ISelectionHandler selectionHandler)
        {
            mSelectionHandler = selectionHandler;
        }

        @Override
        public void onSelectionStarted(int start)
        {
            mOriginalSelection = new HashSet<>();
            Set<Integer> selected = mSelectionHandler.getSelection();
            if (selected != null)
                mOriginalSelection.addAll(selected);
            mFirstWasSelected = mOriginalSelection.contains(start);

            mSelectionHandler.updateSelection(start, start, !mFirstWasSelected, true);

        }

        @Override
        public void onSelectionFinished(int end)
        {
            mOriginalSelection = null;
        }

        @Override
        public void onSelectChange(int start, int end, boolean isSelected)
        {
            for (int i = start; i <= end; i++)
                checkedUpdateSelection(i, i, isSelected ? !mFirstWasSelected :  mOriginalSelection.contains(i));
        }

        private void checkedUpdateSelection(int start, int end, boolean newSelectionState)
        {
            if (mCheckSelectionState)
            {
                for (int i = start; i <= end; i++)
                {
                    if (mSelectionHandler.isSelected(i) != newSelectionState)
                        mSelectionHandler.updateSelection(i, i, newSelectionState, false);
                }
            }
            else
                mSelectionHandler.updateSelection(start, end, newSelectionState, false);
        }

        public interface ISelectionHandler
        {
            Set<Integer> getSelection();

            boolean isSelected(int index);

            void updateSelection(int start, int end, boolean isSelected, boolean calledFromOnStart);
        }
    }

1 个答案:

答案 0 :(得分:2)

我不会在每个选择过程中使用notifyItemRangeChanged(pos, pos);notifyDataSetChanged()等。

为什么你不喜欢这个;

1.在您的适配器中使用您的recyclerview引用,如下所示:

 private RecyclerView mRecyclerView;
 public TestAutoDataAdapter(Context context, int size, RecyclerView pRecyclerView){
        this.mRecyclerView = pRecyclerView;
        ..

2.设置所选视图的背景颜色,如下所示:

       public void select(int pos, boolean selected){
          // Get selected view holder from recyclerview
          ViewHolder holder = recyclerview..findViewHolderForAdapterPosition(pos);

            if (selected)
                this.selected.add(pos);
            else
                this.selected.remove(pos);
            //notifyItemRangeChanged(pos, pos);
            holder.tvText.setBackgroundColor(selected ? Color.RED : Color.WHITE);

        }

以这种方式更改整个选择过程并让我知道结果。