Viewholder не сохраняет свое состояние при прокрутке за пределы экрана

У меня есть приложение музыкального проигрывателя, в котором при нажатии на элемент он меняет свое состояние на состояние «воспроизведение», где оно расширяется, чтобы показать панель поиска, а также происходят другие изменения и наоборот.

Проблема заключается в том, что всякий раз, когда я прокручиваю вверх или вниз, держатель представления меняет свое текущее состояние (воспроизведение, приостановленная видимость в этом случае) на какое-то другое случайное представление в списке. Стоит также отметить, что это единообразно во всем списке, поэтому после каждого определенного количества просмотров состояние такое же, как и у того, на которое был нажат (поэтому, если представление в позиции 0 находится в состоянии видимости воспроизведения после каждых 10 просмотров представления находится в том же состоянии, обратите внимание, однако, что песня все еще играет для правого).

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

    public class AllSongsAdapter extends BaseSongAdapter<AllSongsAdapter.AllSongsItemHolder> {

    private ArrayList<Song> songList;

    public AllSongsAdapter(){
        super(null);
    }

    //OnCreateViewHolder was called for every view;
    //FIX: return 0 for same type of views.
    @Override
    public int getItemViewType(int position) {
        return 0;
    }


    //cursor moves to the appropriate position in the list so we just have to update our views
    @Override
    public void onBindViewHolder(AllSongsItemHolder holder, Cursor cursor) {
        if(cursor!=null) {
            int i = cursor.getPosition();
            Log.d("on Bind", "i:" + i);
            Song songItem = songList.get(i);

            holder.songItemName.setText(songItem.title);
            holder.songItemArtistName.setText(songItem.artistName);
            holder.songItemAlbumName.setText(songItem.albumName);
        }
    }

    @Override
    public void swapCursor(Cursor newCursor) {
        super.swapCursor(newCursor);
        songList = SongsLoader.getSongsForCursor(newCursor);
    }

    @Override
    public AllSongsItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.d("CREATE VIEW HOLDER", "holder" );

        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.songs_item_list, parent, false);
        return new AllSongsItemHolder(v);
    }

    private Uri getAlbumArtUri(long albumId) {
        return ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumId);
    }


    public void setPlayVisibility(View child, RecyclerView rv) {
        //View v = rv.getChildAt(rv.getChildAdapterPosition(child));
        AllSongsItemHolder holder = (AllSongsItemHolder) rv.getChildViewHolder(child);

       // if(getItemId(position) == rv.getChildItemId(v)){}

            holder.seekBar.setVisibility(View.VISIBLE);
            holder.songItemTimer.setVisibility(View.VISIBLE);

            holder.songItemImage.setScaleType(ImageView.ScaleType.CENTER);
            holder.songItemImage.setBackgroundColor(Color.parseColor("#303F9F"));


    }
    public void setPauseVisibility(View child,  RecyclerView rv) {
        //View v = rv.getChildAt(rv.getChildAdapterPosition(child));
        AllSongsItemHolder holder = (AllSongsItemHolder) rv.getChildViewHolder(child);

        //if(getItemId(position) == rv.getChildItemId(v)){}

            holder.seekBar.setVisibility(View.GONE);
            holder.songItemTimer.setVisibility(View.GONE);

            holder.songItemImage.setScaleType(ImageView.ScaleType.FIT_XY);
            holder.songItemImage.setBackgroundColor(0);


    }




    static class AllSongsItemHolder extends RecyclerView.ViewHolder {
        private ImageView songItemImage, songItemOptionDropDown;
        private TextView songItemName, songItemAlbumName, songItemArtistName;
        private View separator;

        private SeekBar seekBar;
        private ImageView nowPlayingIcon;
        private TextView songItemTimer;

        public AllSongsItemHolder(View v) {
            super(v);
            songItemImage = v.findViewById(R.id.songItemImage);
            songItemOptionDropDown = v.findViewById(R.id.songItemOptionDropDown);

            songItemAlbumName = v.findViewById(R.id.songItemAlbumName);
            songItemArtistName = v.findViewById(R.id.songItemArtistName);
            songItemName = v.findViewById(R.id.songItemName);
            separator = v.findViewById(R.id.separator);

            seekBar = v.findViewById(R.id.seekbar);
            songItemTimer = v.findViewById(R.id.songItemTimer);
           // nowPlayingIcon = v.findViewById(R.id.nowPlayingIcon);

        }
    }
}

Я заметил, что когда getItemViewType возвращает позицию, эта проблема не возникает, потому что представление переработчика содержит экземпляр для каждого элемента в списке. Но очевидно, что это неадекватное решение, потому что оно замедляет прокрутку при первой загрузке, поскольку оно должно создавать каждое представление. Может быть что-то с этим делать?

Код базового адаптера:

public abstract class BaseSongAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {


    protected Cursor mCursor;
    protected int mRowIDColumn;
    protected boolean mDataValid;



    public BaseSongAdapter(Cursor c) {
        init(c);
    }

    public BaseSongAdapter() {}


    void init(Cursor c) {
        boolean cursorPresent = c != null;
        mCursor = c;
        mDataValid = cursorPresent;
        mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
        setHasStableIds(true);

        if(mDataValid && mCursor!=null) {
            Log.d("VALID", "CURSOR");
        }
    }


    @Override
    public void onBindViewHolder(VH holder, int position) {
        //Log.d("ON BIND","CALLED");
        if (!mDataValid) {
            throw new IllegalStateException("Cannot bind viewholder when cursor is in invalid state.");
        }
        if (!mCursor.moveToPosition(position)) {
            throw new IllegalStateException("Could not move cursor to position " + position + " when trying to bind viewholder");
        }

        onBindViewHolder(holder, mCursor);

    }

    public abstract void onBindViewHolder(VH holder, Cursor cursor);

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    @Override
    public int getItemCount() {

        if (mDataValid) {
            return mCursor.getCount();
        } else {
            return 0;
        }
    }

    @Override
    public long getItemId(int position) {
        if (!mDataValid) {
            throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state.");
        }
        if (!mCursor.moveToPosition(position)) {
            throw new IllegalStateException("Could not move cursor to position " + position + " when trying to get an item id");
        }

        return mCursor.getLong(mRowIDColumn);
    }

    public void swapCursor(Cursor newCursor) {
        if (newCursor == mCursor) {
            return;
        }

        //Log.d("TAG", "swapCursor");

        //Cursor oldCursor = mCursor;
        if (newCursor != null) {
            mCursor = newCursor;
            mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
            mDataValid = true;
            // notify the observers about the new cursor
            notifyDataSetChanged();
        } else {
            mCursor = null;
            mRowIDColumn = -1;
            mDataValid = false;
            // notify the observers about the lack of a data set
            notifyItemRangeRemoved(0, getItemCount());
        }
        //return oldCursor;
    }



}

Код фрагмента песни, в котором обрабатываются клики (в onActivityCreated):

public class AllSongsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

    private final int LOADER_ID_ALLSONGS = 0;

    private Context ctx;
    private AllSongsAdapter mAdapter;
    private MediaPlayerHolder mediaPlayerHolder;

    //on click play,pause variables
    private boolean playingCLicked = false;
    private boolean firstTime = true;
    private String playbackState;
    private View lastChildView;
    private long currentSongId;
    private long newID;
    private long nextID; //id for next song
    private int lastTrackPosition; //for auto next track

    private RecyclerView rv;

    public AllSongsFragment() {
        super();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        ctx = context;
    }

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

        getLoaderManager().initLoader(LOADER_ID_ALLSONGS, null, this);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.all_songs_fragment, container, false);
        return view;
    }

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

        rv = (RecyclerView) getActivity().findViewById(R.id.fragmentList);

        mediaPlayerHolder = new MediaPlayerHolder(getActivity());
        mediaPlayerHolder.setPlaybackInfoListener(new PlaybackListener());


        rv.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), rv, new ClickListener() {
            @Override
            public void click(View view, final int position) {
                Toast.makeText(getActivity(), "onClick " + position, Toast.LENGTH_SHORT).show();

                newID = mAdapter.getItemId(position);
                if (playingCLicked) {
                    if (mediaPlayerHolder.isPlaying()) {
                        if (currentSongId == newID) {
                            mAdapter.setPauseVisibility(view, rv);
                            lastChildView = view;
                        }

                        //PAUSE IT
                        mediaPlayerHolder.pause();

                        Log.d("playingclicked", "true" + "state" + playbackState);
                        //when a different song is clicked while current song is playing
                        if (playbackState.equalsIgnoreCase("paused") && newID != currentSongId) {
                            currentSongId = newID;
                            mAdapter.setPauseVisibility(lastChildView, rv);
                            mAdapter.setPlayVisibility(view, rv);
                            mediaPlayerHolder.reset();
                            mediaPlayerHolder.loadMedia(currentSongId);
                            mediaPlayerHolder.play();

                            lastTrackPosition = position;
                            lastChildView = view;
                            Log.d("else 1 positions", "currentsongID" + currentSongId + "lasttrack" + lastTrackPosition);
                        }

                        playingCLicked = !playingCLicked;
                    } else { //media is not playing
                        Log.d("else 1", "play");
                        if (firstTime) {
                            Log.d("different", "no");
                            currentSongId = newID;
                            lastTrackPosition = position;
                            lastChildView = view;
                            mediaPlayerHolder.reset();
                            mediaPlayerHolder.loadMedia(currentSongId);

                            Log.d("SelectedTrack", "" + mediaPlayerHolder.getMediaPlayer().getSelectedTrack(MEDIA_TRACK_TYPE_AUDIO));
                            firstTime = false;
                        }

                        if (newID != currentSongId) {
                            Log.d("different", "yes");
                            //mediaPlayerHolder.stop();
                            currentSongId = newID;
                            mediaPlayerHolder.reset();
                            mediaPlayerHolder.loadMedia(currentSongId);
                            firstTime = true;
                        }
                        if (currentSongId == newID)
                            mAdapter.setPlayVisibility(view, rv);

                        //PLAY IT
                        mediaPlayerHolder.play();

                        playingCLicked = !playingCLicked;
                    }

                } else //----------playingclicked = false, first called--------------
                {
                    if (!mediaPlayerHolder.isPlaying()) {
                        lastChildView = view;
                        mAdapter.setPlayVisibility(view, rv);


                        if (firstTime) {
                            Log.d("different", "no, first time");
                            currentSongId = newID;
                            lastTrackPosition = position;
                            lastChildView = view;
                            mediaPlayerHolder.reset();
                            mediaPlayerHolder.loadMedia(currentSongId);

                            Log.d("SelectedTrack", "" + mediaPlayerHolder.getMediaPlayer().getSelectedTrack(MEDIA_TRACK_TYPE_AUDIO));
                            firstTime = false;
                        }

                        Log.d("playbackState", playbackState + "currentId " + currentSongId);

                        //called when current song is paused and after playingClicked = true following is called if songId is different
                        if (newID != currentSongId) {
                            Log.d("different", "yes");
                            currentSongId = newID;
                            mediaPlayerHolder.stop();
                            mediaPlayerHolder.reset();
                            mediaPlayerHolder.loadMedia(currentSongId);
                        }

                        //PLAY IT
                        mediaPlayerHolder.play();

                        playingCLicked = !playingCLicked;

                    } else { //media is playing
                        Log.d("else 2", "pause");
                        if (newID == currentSongId) {
                            lastChildView = view;
                            lastTrackPosition = position;
                            mAdapter.setPauseVisibility(view, rv);
                        }

                        //PAUSE IT
                        mediaPlayerHolder.pause();
                        firstTime = false;

                        //when a different song is clicked while current song is playing
                        if (playbackState.equalsIgnoreCase("paused") && newID != currentSongId) {
                            currentSongId = newID;
                            mAdapter.setPauseVisibility(lastChildView, rv);
                            mAdapter.setPlayVisibility(view, rv);
                            mediaPlayerHolder.reset();
                            mediaPlayerHolder.loadMedia(currentSongId);
                            mediaPlayerHolder.play();

                            lastTrackPosition = position;
                            lastChildView = view;

                            Log.d("else 2 positions", "currentsongID" + currentSongId + "lasttrack" + lastTrackPosition);
                        }

                        playingCLicked = !playingCLicked;
                    }
                }
            }

            @Override
            public void onLongClick(View view, int position) {
                Toast.makeText(getActivity(),"onLongClick " + position,Toast.LENGTH_SHORT).show();
            }
        }));

        mAdapter = new AllSongsAdapter();
        rv.setAdapter(mAdapter);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        rv.setLayoutManager(layoutManager);

        //------------Temporary divider-----------------
        RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(getActivity(), layoutManager.getOrientation());
        rv.addItemDecoration(itemDecoration);

    }


    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        Log.d("CREATE LOADER", "SUCCESS");
        Uri musicUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
        final String[] PROJECTION = {"_id", "title", "artist", "album", "duration", "track", "artist_id", "album_id"};

        return new CursorLoader(ctx,
                musicUri,
                PROJECTION,
                null,
                null,
                MediaStore.Audio.Media.DEFAULT_SORT_ORDER );
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mAdapter.swapCursor(data);

        Log.d("LOAD FINISHED", "SUCCESS");
    }


    @Override
    public void onLoaderReset(Loader loader) {
        Log.d("LOADER RESET", "CALLED");
        mAdapter.swapCursor(null);
    }



    class PlaybackListener extends PlaybackStateListener{

        @Override
        void onDurationChanged(int duration) {
            super.onDurationChanged(duration);
        }

        @Override
        void onPositionChanged(int position) {
            super.onPositionChanged(position);
        }

        @Override
        void onStateChanged(int state) {
            playbackState = PlaybackListener.convertStateToString(state);
        }

        @Override
        void onPlaybackCompleted() {
            //super.onPlaybackCompleted();
            //pause visibility after completed
            mAdapter.setPauseVisibility(lastChildView, rv);
        }

        @Override
        void onLogUpdated(String formattedMessage) {
            super.onLogUpdated(formattedMessage);
        }
    }

//---------------RECYCLER VIEW TOUCH EVENTS LISTENER CLASS------------------------
    class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

        private GestureDetector gestureDetector;
        private ClickListener mListener;


        public RecyclerTouchListener(Context ctx, final RecyclerView recyclerView, ClickListener clickListener){
            mListener = clickListener;

            gestureDetector = new GestureDetector(ctx, new GestureDetector.OnGestureListener() {
                @Override
                public boolean onDown(MotionEvent e) {
                    return false;
                }

                @Override
                public void onShowPress(MotionEvent e) {

                }

                @Override
                public boolean onSingleTapUp(MotionEvent e) {
                   // Log.d("GESTURE DETECTED", "ACTION UP" + e);
                    return true;
                }

                @Override
                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                    return false;
                }

                @Override
                public void onLongPress(MotionEvent e) {
                    //Log.d("GESTURE DETECTED", "LONG PRESS");

                    if(rv.getScrollState() == SCROLL_STATE_IDLE && !(rv.getScrollState() == SCROLL_STATE_DRAGGING)) {
                        View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
                        if (child != null && mListener != null) {
                            mListener.onLongClick(child, recyclerView.getChildLayoutPosition(child));
                        }
                    }
                }

                @Override
                public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
                    return false;
                }
            });

        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
           //Log.d("Intercept Event", "INTERCEPTING \n" + e);

            if(rv.getScrollState() == SCROLL_STATE_IDLE && !(rv.getScrollState() == SCROLL_STATE_DRAGGING)) {
                View child = rv.findChildViewUnder(e.getX(), e.getY());
                if (child != null && mListener != null && gestureDetector.onTouchEvent(e)) {
                    mListener.click(child, rv.getChildLayoutPosition(child));
                }
            }

            //true if onTouchEvent is to be called, false if you want gesture detector to handle events
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {

        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        }


    }


}

Любая помощь будет принята с благодарностью!


person Munim Ahmed    schedule 04.02.2018    source источник


Ответы (1)


Вам нужно установить видимость воспроизведения/паузы/любой в onBindViewHolder, иначе она будет любой, какой бы ни была последняя настройка, даже если новый объект модели привязан к этому представлению.

person dazza5000    schedule 04.02.2018
comment
понятно, вы также предлагаете, чтобы я вызывал свой прослушиватель кликов в держателе представления адаптеров или я должен каким-то образом получить ссылку на представление, по которому щелкнули, и установить для него видимость в onBind? как я буду реализовывать последний? - person Munim Ahmed; 05.02.2018
comment
Обычно я устанавливаю конкретный прослушиватель кликов в onBindViewHolder, потому что вам нужно знать конкретный элемент. - person dazza5000; 05.02.2018
comment
Спасибо за помощь я разобрался. Я получил текущую позицию воспроизводимых дорожек в onClickListener, реализованную в держателе представления. Затем оттуда я мог бы установить видимость для воспроизводимой в данный момент дорожки видимой, если позиция держателей равна позиции текущей дорожки, в противном случае сделать ее невидимой. К счастью, мне не пришлось переносить весь код прослушивателя кликов из фрагмента в адаптер. - person Munim Ahmed; 06.02.2018