<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                看了一下博客目錄,已經有好幾篇博客是關于`RecyclerView`的,不過對于這么一款強大的控件,我還是要再寫一篇博客來學習一下,這篇博客的主題是《為RecyclerView添加header》,當然在看完這篇博客后,相信添加Footer你也應該能夠學會。話說在這么多新控件中為何`RecyclerView`備受開發者的喜愛?這還是因為在Android發展到今天基本上還沒有像`RecyclerView`這么靈活的一個玩意,鑒于他的靈活以及強大,很多人(包括我)已經開始拋棄`ListView`和`GridView`轉為`RecyclerView`了,再使用過`RecyclerView`和被善變的需求折磨后,我相信會有越來越多的人轉到`RecyclerView`的使用上。 ### 問題 好了,廢話不多說了,這篇博客我們要解決的問題有: > 1. 如何為RecyclerView添加Header > 1. 如何讓Header適配各種LayoutManager > 1. 在有Header的情況下,我們的分割線該怎么畫 > 1. 作為一個懶惰的程序員,如何將這些做到最簡便 ### 如果為RecyclerView添加Header 大家在使用`ListView`的時候可以很輕松的添加headers, 但是不知道大家發現沒有,`RecyclerView`和各種`LayoutManager`都沒有哪個方法是為添加header而設立的,這個時候我們就開始思考如何為`RecyclerView`添加header了。 這里我們的解決方案和網上你能搜到的大多數方案一樣,是通過控制`Adapter`的`itemType`來設置的,思路就是**根據不同的itemType去加載不同的布局**。 ~~~ /** * Created by qibin on 2015/11/5. */ public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public static final int TYPE_HEADER = 0; public static final int TYPE_NORMAL = 1; private ArrayList<String> mDatas = new ArrayList<>(); private View mHeaderView; private OnItemClickListener mListener; public void setOnItemClickListener(OnItemClickListener li) { mListener = li; } public void setHeaderView(View headerView) { mHeaderView = headerView; notifyItemInserted(0); } public View getHeaderView() { return mHeaderView; } public void addDatas(ArrayList<String> datas) { mDatas.addAll(datas); notifyDataSetChanged(); } @Override public int getItemViewType(int position) { if(mHeaderView == null) return TYPE_NORMAL; if(position == 0) return TYPE_HEADER; return TYPE_NORMAL; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(mHeaderView != null && viewType == TYPE_HEADER) return new Holder(mHeaderView); View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false); return new Holder(layout); } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { if(getItemViewType(position) == TYPE_HEADER) return; final int pos = getRealPosition(viewHolder); final String data = mDatas.get(pos); if(viewHolder instanceof Holder) { ((Holder) viewHolder).text.setText(data); if(mListener == null) return; viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mListener.onItemClick(pos, data); } }); } } public int getRealPosition(RecyclerView.ViewHolder holder) { int position = holder.getLayoutPosition(); return mHeaderView == null ? position : position - 1; } @Override public int getItemCount() { return mHeaderView == null ? mDatas.size() : mDatas.size() + 1; } class Holder extends RecyclerView.ViewHolder { TextView text; public Holder(View itemView) { super(itemView); if(itemView == mHeaderView) return; text = (TextView) itemView.findViewById(R.id.text); } } interface OnItemClickListener { void onItemClick(int position, String data); } } ~~~ 這里我們重寫了`getItemViewType`方法,并根據位置來返回不同的type,這個type是我們預先商定好的常量,接在`onCreateViewHolder`方法中來判斷itemType,如果是header,則返回我們設置的headerView,否則正常加載item布局,相信大家對于上面的代碼不會有任何疑問,接下來我們就在Activity中用一下試試看, ~~~ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView = (RecyclerView) findViewById(R.id.list); mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setItemAnimator(new DefaultItemAnimator()); mAdapter = new MyAdapter(); mRecyclerView.setAdapter(mAdapter); mAdapter.addDatas(generateData()); setHeader(mRecyclerView); mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() { @Override public void onItemClick(int position, String data) { Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show(); } }); } private void setHeader(RecyclerView view) { View header = LayoutInflater.from(this).inflate(R.layout.header, view, false); mAdapter.setHeaderView(header); } ~~~ 這里`LayoutManager`我們使用了`LinearLayoutManager`,并且給`Adapter`設置了一個header,運行一下 看看效果: ![](https://box.kancloud.cn/2016-02-18_56c55b3e14f30.jpg "") 恩,還不錯,item的點擊事件也很完美,那接下來,我們將`LayoutManager`換成`GridLayoutManager`看看咋樣。 ### 為GridLayoutManager添加header ~~~ // mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mLayoutManager = new GridLayoutManager(this, 2); ~~~ ![](https://box.kancloud.cn/2016-02-18_56c55b3e27880.jpg "") 哎喲,我的小心臟啊,快受不了了,這是什么玩意,我們的header竟然作為一個cell出現在了界面上,這完全不是我們想要的效果啊! 冷靜下來想想,肯定會有解決方法的吧。這時候我們就該引入一個不太常用的方法了: ~~~ gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return getItemViewType(position) == TYPE_HEADER ? gridManager.getSpanCount() : 1; } }); ~~~ 我們解釋一下這段代碼,首先我們設置了一個`SpanSizeLookup`,這個類是一個抽象類,而且僅有一個抽象方法`getSpanSize`,這個方法的返回值決定了我們每個position上的item占據的單元格個數,而我們這段代碼綜合上面為`GridLayoutManager`設置的每行的個數來解釋的話, 就是**當前位置是header的位置,那么該item占據2個單元格,正常情況下占據1個單元格**。那這段代碼放哪呢? 為了以后的封裝,我們還是在`Adapter`中找方法放吧。 我們在`Adapter`中再重寫一個方法`onAttachedToRecyclerView`, ~~~ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if(manager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return getItemViewType(position) == TYPE_HEADER ? gridManager.getSpanCount() : 1; } }); } } ~~~ 這個時候我們再來看一下效果, ![](https://box.kancloud.cn/2016-02-18_56c55b3e3c780.jpg "") 恩,這次達到我們的要求了,不過對于`StaggeredGridLayoutManager`我們還沒做處理,而且我們還發現`StaggeredGridLayoutManager`中并沒有像`GridLayoutManager`中這樣的方法,我們還需要單獨為`StaggeredGridLayoutManager`單獨處理一下。 ### 為StaggeredGridLayoutManager添加header 我們繼續重寫`Adapter`中另外一個方法。 ~~~ @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if(lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(holder.getLayoutPosition() == 0); } } ~~~ 這里的處理方式是用通過`LayoutParams`,而且這里更簡單,`StaggeredGridLayoutManager.LayoutParams`為我們提供了一個`setFullSpan`方法來設置占領全部空間,好開心,看一下`StaggeredGridLayoutManager`的效果, ![](https://box.kancloud.cn/2016-02-18_56c55b3e3c780.jpg "") 啊, 怎么和上面的效果一樣? 很簡單嘛,我們的item都是等高的。 ### 處理分隔符 這是我們開開心心的繼續寫代碼,并且為我們的item添加了分隔符,分隔符我還是用的翔哥寫的那個,畢竟翔哥寫的太好了,而且我們沒有必要重復造輪子,不過這時候問題出現了,相信你也肯定能猜到應該會出現問題了,因為不管我們怎么處理,header對于`RecyclerView`來說還是一個普普通通的item,這時候我們添加分割線,肯定也會對header產生影響,那下面,我們再來對翔哥的分割線改造一下吧。 ~~~ public class GridItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; private Drawable mDivider; private boolean hasHeader; public GridItemDecoration(Context context) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } public GridItemDecoration(Context context, boolean header) { this(context); hasHeader = header; } ... @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(view); int spanCount = getSpanCount(parent); int childCount = parent.getAdapter().getItemCount(); int pos = position; if(hasHeader) { if(position == 0) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); return; } else { pos = position - 1; } } if (isLastColum(parent, pos, spanCount, childCount)) { outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); } } } ~~~ 改造的地方是獲取偏移量的方法我們換了一個,因為原來的那個已經過時了,而且,這里我們還加了一個boolean類型的`hasHeader`變量來表示是不是有header,如果hasHeader并且position為0,那么我們僅僅繪制底部的分割線,其他的地方不繪制,在有header的情況下,我們還需要將position減1,因為我們認為的第1個item其實是第2個。這個時候我們再來看看有分割線的效果。 ![](https://box.kancloud.cn/2016-02-18_56c55b3e5215d.jpg "") 看來我們的想法是對的,header部分除了底部有一個分割線外,并沒有其他的分割線,這也完全符合我們的需求。 ### 封裝 這下好了,基本上完美的處理好了,可是難道我們對于不同的`Adapter`都需要寫那么多代碼嗎? 對于一個懶程序員來說,這肯定是一個可怕的事情,所以,我們還需要對我們的`Adapter`進行封裝,目的就是可以輕輕松松的寫代碼, ~~~ /** * Created by qibin on 2015/11/5. */ public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public static final int TYPE_HEADER = 0; public static final int TYPE_NORMAL = 1; private ArrayList<T> mDatas = new ArrayList<>(); private View mHeaderView; private OnItemClickListener mListener; public void setOnItemClickListener(OnItemClickListener li) { mListener = li; } public void setHeaderView(View headerView) { mHeaderView = headerView; notifyItemInserted(0); } public View getHeaderView() { return mHeaderView; } public void addDatas(ArrayList<T> datas) { mDatas.addAll(datas); notifyDataSetChanged(); } @Override public int getItemViewType(int position) { if(mHeaderView == null) return TYPE_NORMAL; if(position == 0) return TYPE_HEADER; return TYPE_NORMAL; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, final int viewType) { if(mHeaderView != null && viewType == TYPE_HEADER) return new Holder(mHeaderView); return onCreate(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { if(getItemViewType(position) == TYPE_HEADER) return; final int pos = getRealPosition(viewHolder); final T data = mDatas.get(pos); onBind(viewHolder, pos, data); if(mListener != null) { viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mListener.onItemClick(pos, data); } }); } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); if(manager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) manager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return getItemViewType(position) == TYPE_HEADER ? gridManager.getSpanCount() : 1; } }); } } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if(lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(holder.getLayoutPosition() == 0); } } public int getRealPosition(RecyclerView.ViewHolder holder) { int position = holder.getLayoutPosition(); return mHeaderView == null ? position : position - 1; } @Override public int getItemCount() { return mHeaderView == null ? mDatas.size() : mDatas.size() + 1; } public abstract RecyclerView.ViewHolder onCreate(ViewGroup parent, final int viewType); public abstract void onBind(RecyclerView.ViewHolder viewHolder, int RealPosition, T data); public class Holder extends RecyclerView.ViewHolder { public Holder(View itemView) { super(itemView); } } public interface OnItemClickListener<T> { void onItemClick(int position, T data); } } ~~~ 我們將`BaseRecyclerAdapter`抽象起來,并且提供兩個抽象方法`onCreate`和`onBind`用來創建holder和綁定數據,而對于header做的一系列工作,我們都放到了`BaseRecyclerAdapter`中,而繼承`BaseRecyclerAdapter`后,我們僅僅關心我們的holder怎么創建和數據怎么綁定就ok。例如下面代碼: ~~~ /** * Created by qibin on 2015/11/7. */ public class MyAdapter extends BaseRecyclerAdapter<String> { @Override public RecyclerView.ViewHolder onCreate(ViewGroup parent, int viewType) { View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false); return new MyHolder(layout); } @Override public void onBind(RecyclerView.ViewHolder viewHolder, int RealPosition, String data) { if(viewHolder instanceof MyHolder) { ((MyHolder) viewHolder).text.setText(data); } } class MyHolder extends BaseRecyclerAdapter.Holder { TextView text; public MyHolder(View itemView) { super(itemView); text = (TextView) itemView.findViewById(R.id.text); } } } ~~~ 這樣我們再用起來就簡單多了,對于這樣的封裝,我們還算滿意,再做完添加header后,相信大家對于footer也有想法了,有想法就實現它吧,擴展一下`BaseRecyclerAdapter`就ok啦。 好了,這篇博客就到這里吧,最后是本文代碼的下載。 [代碼下載,戳這里](http://download.csdn.net/detail/qibin0506/9251603)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看