Joao Sardinha
Joao Sardinha

Reputation: 3423

Android: weird reaction when adding views in RecyclerView

I'm using a custom recyclerView that can have a footer and a header, that shoudnt influence the animations tho, here is a video of what's happening: http://www.videosprout.com/video?id=00fae6ac-39ff-47b6-b981-803d2773b67b

Why is every view moving one position back and then back to where it was instead of not doing that?

Here's my adapter:

public class AddEventsAdapter extends HFRecyclerViewAdapter<String, AddEventsAdapter.ViewHolder> {

    public AddEventsAdapter(Context context) {
        super(context);
    }

    @Override
    public void footerOnVisibleItem() {

    }

    @Override
    public void addData(int position, String item) {
        super.addData(position, item);
    }

    @Override
    public ViewHolder onCreateDataItemViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.event_item, parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindDataItemViewHolder(ViewHolder holder, int position) {
        holder.itemTv.setText(getData().get(position));
    }

    class ViewHolder extends RecyclerView.ViewHolder{
        TextView itemTv;
        public ViewHolder(View itemView) {
            super(itemView);
            itemTv = (TextView)itemView.findViewById(R.id.eventName);

        }
    }
}

The implementation:

    final AddEventsAdapter MyAdapter = new AddEventsAdapter(this);
    AddEventsRecycler.setAdapter(MyAdapter);
    AddEventsRecycler.setLayoutManager(new LinearLayoutManager(this));

    //add footer
    final View footerView = LayoutInflater.from(this).inflate(R.layout.events_footer, AddEventsRecycler, false);
    MyAdapter.setFooterView(footerView);

    footerView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            MyAdapter.addData(0, "Event number" + ++g);
        }
    });

    ArrayList<String> data = new ArrayList<>();
    data.add("Vacation");

    MyAdapter.setData(data);

The custom RecyclerAdapter:

public abstract class HFRecyclerViewAdapter<T, VH extends RecyclerView.ViewHolder> extends BaseRecyclerViewAdapter<T> {

    public HFRecyclerViewAdapter(Context context) {
        super(context);
    }

    private static final int TYPE_HEADER = Integer.MAX_VALUE;
    private static final int TYPE_FOOTER = Integer.MAX_VALUE - 1;
    private static final int ITEM_MAX_TYPE = Integer.MAX_VALUE - 2;
    private RecyclerView.ViewHolder headerViewHolder;
    private RecyclerView.ViewHolder footerViewHolder;

    class HFViewHolder extends RecyclerView.ViewHolder {
        HFViewHolder(View v) {
            super(v);
        }
    }

    public void setHeaderView(View header){
        if (headerViewHolder == null || header != headerViewHolder.itemView) {
            headerViewHolder = new HFViewHolder(header);
            notifyDataSetChanged();
        }
    }

    public void setFooterView(View foot){
        if (footerViewHolder == null || foot != footerViewHolder.itemView) {
            footerViewHolder = new HFViewHolder(foot);
            notifyDataSetChanged();
        }
    }

    public void removeHeader(){
        if (headerViewHolder != null){
            headerViewHolder = null;
            notifyDataSetChanged();
        }
    }
    public void removeFooter(){
        if (footerViewHolder != null){
            footerViewHolder = null;
            notifyDataSetChanged();
        }
    }

    public boolean isHeader(int position){
        return hasHeader() && position == 0;
    }

    public boolean isFooter(int position){
        return hasFooter() && position == getDataItemCount() + (hasHeader() ? 1 : 0);
    }

    private int itemPositionInData(int rvPosition){
        return rvPosition - (hasHeader() ? 1 : 0);
    }
    private int itemPositionInRV(int dataPosition){
        return dataPosition + (hasHeader() ? 1 : 0);
    }

    @Override
    public void notifyMyItemInserted(int itemPosition) {
        notifyItemInserted(itemPositionInRV(itemPosition));
    }
    @Override
    public void notifyMyItemRemoved(int itemPosition) {
        notifyItemRemoved(itemPositionInRV(itemPosition));
    }

    @Override
    public void notifyMyItemChanged(int itemPosition) {
        notifyItemChanged(itemPositionInRV(itemPosition));
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER) {
            return headerViewHolder;
        } else if (viewType == TYPE_FOOTER) {
            return footerViewHolder;
        }
        return onCreateDataItemViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (!isHeader(position) && !isFooter(position))
            onBindDataItemViewHolder((VH)holder, itemPositionInData(position));

        if (isFooter(position)){
            footerOnVisibleItem();
        }
    }

    public abstract void footerOnVisibleItem();

    @Override
    public int getItemCount() {
        int itemCount = getDataItemCount();
        if (hasHeader()) {
            itemCount += 1;
        }
        if (hasFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeader(position)) {
            return TYPE_HEADER;
        }
        if (isFooter(position)) {
            return TYPE_FOOTER;
        }
        int dataItemType = getDataItemType(itemPositionInData(position));
        if (dataItemType > ITEM_MAX_TYPE) {
            throw new IllegalStateException("getDataItemType() must be less than " + ITEM_MAX_TYPE + ".");
        }
        return dataItemType;
    }

    public int getDataItemCount() {
        return super.getItemCount();
    }

    /**
     * make sure your dataItemType < Integer.MAX_VALUE-1
     *
     * @param position item view position in rv
     * @return item viewType
     */
    public int getDataItemType(int position){
        return 0;
    }


    public boolean hasHeader(){
        return headerViewHolder != null;
    }
    public boolean hasFooter(){
        return footerViewHolder != null;
    }

    public abstract VH onCreateDataItemViewHolder(ViewGroup parent, int viewType);

    public abstract void onBindDataItemViewHolder(VH holder, int position);



}

EDIT: Same is happening when removing a view, with removeData(getAdapterPosition()

Upvotes: 2

Views: 141

Answers (1)

Eric
Eric

Reputation: 17566

why

this is happening because the element is being added to at the beginning of the array (index 0). when this happens, the RecyclerView will react as shown in the video, because the it pretends the backing data store is a list, and all elements are moved over one index, and the element is inserted in the beginning...which happens to be at the top.

you can see that it won't do this ugly behavior if elements are added at the end of the adapter: MyAdapter.addData(MyAdapter.getItemCount() - 1, "Event ");. but of course, this is not what you want either, because it is the wrong index...and now it would seem like existing elements are actually jumping up one index on the GUI and things...but this is an interesting experiment to verify the theory.

a solution

still add elements at the end of the array using MyAdapter.addData(MyAdapter.getItemCount() - 1, "Event ");, but make the LinearLayoutManager display elements in reverse! this can be done by:

you may have to do something about the headers and footers are positioned in the adapter to make sure they stay as headers and footers in the RecyclerView.Adapter though...once the RecyclerView.LayoutManager is displaying things in reverse.

Upvotes: 1

Related Questions