Android-優雅地爲RecyclerView添加HeaderView和FooterView

怎麼給RecycerView添加Header/FooterView?答案在網上一搜一大把,實現原理大致相同。我第一次實現這種功能,參考了ListView的方式,使用代理模式設計一個代理類,代理RecyclerView.Adapter的全部行爲。而且添加Header和Footer的功能都在代理類裏面實現。java

新建Adapter代理類ProxyAdapter,繼承RecyclerView.Adapter,將Adapter經過構造方法傳遞給ProxyAdapter。android

public class ProxyAdapter extends RecyclerView.Adapter {
    final RecyclerView.Adapter mAdapter;
    // ......
    
    public ProxyAdapter(RecyclerView.Adapter adapter) {
        if (adapter == null) {
            throw new IllegalArgumentException();
        }
        mAdapter = adapter;
    }
}

 

建立兩個數組容器分別存儲HeaderView和FooterView,並增長add/remove方法用來添加和刪除Header/FooterView。git

List<View> mHeaderViews = new ArrayList<>();
List<View> mFooterViews = new ArrayList<>();

public void addHeaderView(View view) {
  if (mHeaderViews.add(view)) {
    mAdapter.notifyDataSetChanged();
  }
}

public void removeHeaderView(View view) {
  if (mHeaderViews.remove(view)) {
    mAdapter.notifyDataSetChanged();
  }
}

public void addFooterView(View view) {
  if (mFooterViews.add(view)) {
    mAdapter.notifyDataSetChanged();
  }
}

public void removeFooterView(View view) {
  if (mFooterViews.remove(view)) {
    mAdapter.notifyDataSetChanged();
  }
}

 

重寫ProxyAdapter的getItemCount()和getItemViewType(int position)方法, 爲HeaderView和FooterView分配獨立的ViewType。若是ItemView的當前位置position小於headerView的數量時,ItemView爲HEADER類型。若是position大於等於headerView數量和adapter的item數量時,ItemView爲FOOTER類型。經過ViewTypeSpec工具,將算出來的ItemView類型和Header/FooterView的索引位置(在容器中的位置)打包成一個整型結果,做爲ItemViewType返回。github

@Override
public int getItemCount() {
  return mHeaderViews.size() + mFooterViews.size() + mAdapter.getItemCount();
}

@Override
public int getItemViewType(int position) {
  final int numHeaderView = mHeaderViews.size();
//  final int numFooterView = mFooterViewInfos.size();

  if (position < numHeaderView)
    return ViewTypeSpec.makeItemViewTypeSpec(position, ViewTypeSpec.HEADER);

  final int adjPosition = position - numHeaderView;
  final int itemCount = mAdapter.getItemCount();
  if (adjPosition >= itemCount)
    return ViewTypeSpec.makeItemViewTypeSpec(adjPosition - itemCount, ViewTypeSpec.FOOTER);

  int itemViewType = mAdapter.getItemViewType(adjPosition);
  if (itemViewType < 0 || itemViewType > (1 << ViewTypeSpec.TYPE_SHIFT) - 1) {
    throw new IllegalArgumentException("Invalid item view type: RecyclerView.Adapter.getItemViewType return " + itemViewType);
  }
  return itemViewType;
}

 

接下來重寫onCreateViewHolder方法。須要實現的邏輯是經過ViewTypeSpec工具分離viewType,獲得ItemView的類型(type)和索引位置(value)。根據類型type建立匹配的ViewHolder,根據索引位置從數組容器中獲取Header/FooterView數組

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  RecyclerView.ViewHolder viewHolder;
  final int type = ViewTypeSpec.getType(viewType);
  final int value = ViewTypeSpec.getValue(viewType);

  if (type == ViewTypeSpec.HEADER) {
    viewHolder = new FixedViewHolder(mHeaderViews.get(value));
  } else if (type == ViewTypeSpec.FOOTER) {
    viewHolder = new FixedViewHolder(mFooterViews.get(value));
  } else {
    viewHolder = mAdapter.onCreateViewHolder(parent, viewType);
  }
  return viewHolder;
}

 

大體實現流程就是這樣,下面是ProxyAdapter的使用。app

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  // ......
  mRecyclerView
= (RecyclerView) findViewById(R.id.recycler_view);   ContactAdapter adapter = new ContactAdapter();   // ...... ProxyAdapter proxyAdapter = new ProxyAdapter(adapter); mRecyclerView.setAdapter(proxyAdapter)
}

最後須要注意的是,若是列表數據須要刷新,調用的是ContactAdapter的notifyDataSetChanged()而不是ProxyAdapter的notifyDataSetChanged()。不然不會有刷新效果。ide

 

所有代碼:工具

package cncoderx.myapplication.recyclerviewhelper;

import android.support.annotation.IntDef;
import android.support.annotation.IntRange;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

/**
 * @author cncoderx
 */
public class ProxyAdapter extends RecyclerView.Adapter {
    List<View> mHeaderViews = new ArrayList<>();
    List<View> mFooterViews = new ArrayList<>();

    final RecyclerView.Adapter mAdapter;

    public ProxyAdapter(RecyclerView.Adapter adapter) {
        if (adapter == null) {
            throw new IllegalArgumentException();
        }
        mAdapter = adapter;
        setHasStableIds(adapter.hasStableIds());
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder viewHolder;
        final int type = ViewTypeSpec.getType(viewType);
        final int value = ViewTypeSpec.getValue(viewType);

        if (type == ViewTypeSpec.HEADER) {
            viewHolder = new FixedViewHolder(mHeaderViews.get(value));
        } else if (type == ViewTypeSpec.FOOTER) {
            viewHolder = new FixedViewHolder(mFooterViews.get(value));
        } else {
            viewHolder = mAdapter.onCreateViewHolder(parent, viewType);
        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof FixedViewHolder) {
            ((FixedViewHolder) holder).onBind();
        } else {
            int adjPosition = position - mHeaderViews.size();
            mAdapter.onBindViewHolder(holder, adjPosition);
        }
    }

    @Override
    public int getItemCount() {
        return mHeaderViews.size() + mFooterViews.size() + mAdapter.getItemCount();
    }

    @Override
    public int getItemViewType(int position) {
        final int numHeaderView = mHeaderViews.size();
//        final int numFooterView = mFooterViewInfos.size();

        if (position < numHeaderView)
            return ViewTypeSpec.makeItemViewTypeSpec(position, ViewTypeSpec.HEADER);

        final int adjPosition = position - numHeaderView;
        final int itemCount = mAdapter.getItemCount();
        if (adjPosition >= itemCount)
            return ViewTypeSpec.makeItemViewTypeSpec(adjPosition - itemCount, ViewTypeSpec.FOOTER);

        int itemViewType = mAdapter.getItemViewType(adjPosition);
        if (itemViewType < 0 || itemViewType > (1 << ViewTypeSpec.TYPE_SHIFT) - 1) {
            throw new IllegalArgumentException("Invalid item view type: RecyclerView.Adapter.getItemViewType return " + itemViewType);
        }
        return itemViewType;
    }

    @Override
    public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
        mAdapter.onDetachedFromRecyclerView(recyclerView);
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        mAdapter.onAttachedToRecyclerView(recyclerView);
    }

    @Override
    public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
        mAdapter.unregisterAdapterDataObserver(observer);
    }

    @Override
    public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
        mAdapter.registerAdapterDataObserver(observer);
    }

    @Override
    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
        if (holder instanceof FixedViewHolder) return;
        mAdapter.onViewDetachedFromWindow(holder);
    }

    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        if (holder instanceof FixedViewHolder) return;
        mAdapter.onViewAttachedToWindow(holder);
    }

    @Override
    public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
        if (holder instanceof FixedViewHolder) return false;
        return mAdapter.onFailedToRecycleView(holder);
    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        if (holder instanceof FixedViewHolder) return;
        mAdapter.onViewRecycled(holder);
    }

    @Override
    public long getItemId(int position) {
        int adjPosition = position - mHeaderViews.size();
        if (adjPosition >= 0 && adjPosition < mAdapter.getItemCount())
            return mAdapter.getItemId(adjPosition);

        return RecyclerView.NO_ID;
    }

    private boolean isFixedViewType(int viewType) {
        final int type = ViewTypeSpec.getType(viewType);
        return type == ViewTypeSpec.HEADER || type == ViewTypeSpec.FOOTER;
    }

    public void addHeaderView(View view) {
        if (mHeaderViews.add(view)) {
            mAdapter.notifyDataSetChanged();
        }
    }

    public void removeHeaderView(View view) {
        if (mHeaderViews.remove(view)) {
            mAdapter.notifyDataSetChanged();
        }
    }

    public void addFooterView(View view) {
        if (mFooterViews.add(view)) {
            mAdapter.notifyDataSetChanged();
        }
    }

    public void removeFooterView(View view) {
        if (mFooterViews.remove(view)) {
            mAdapter.notifyDataSetChanged();
        }
    }

    static class ViewTypeSpec {
        static final int TYPE_SHIFT = 30;
        static final int TYPE_MASK  = 0x3 << TYPE_SHIFT;

        public static final int UNSPECIFIED = 0 << TYPE_SHIFT;
        public static final int HEADER = 1 << TYPE_SHIFT;
        public static final int FOOTER = 2 << TYPE_SHIFT;

        @IntDef({UNSPECIFIED, HEADER, FOOTER})
        @Retention(RetentionPolicy.SOURCE)
        public @interface ViewTypeSpecMode {}

        public static int makeItemViewTypeSpec(@IntRange(from = 0, to = (1 << TYPE_SHIFT) - 1) int value,
                                               @ViewTypeSpecMode int type) {
            return (value & ~TYPE_MASK) | (type & TYPE_MASK);
        }

        @ViewTypeSpecMode
        public static int getType(int viewType) {
            //noinspection ResourceType
            return (viewType & TYPE_MASK);
        }

        public static int getValue(int viewType) {
            return (viewType & ~TYPE_MASK);
        }
    }

    public static class FixedViewHolder extends RecyclerView.ViewHolder {

        public FixedViewHolder(View itemView) {
            super(itemView);
            setIsRecyclable(false);
        }

        public void onBind() {

        }
    }
}
View Code

 

須要瞭解更多RecyclerView的功能,請訪問RecyclerViewHelperspa

相關文章
相關標籤/搜索