如何為RecyclerView添加分隔線
我在簡(jiǎn)書(shū)上發(fā)布了我個(gè)人的第一篇技術(shù)文檔:RecyclerView系列之: RecyclerView系列之(1)為RecyclerView添加Header和Footer,也很有幸,能夠得到那么多人的支持,這讓我迫不及待的趕緊寫(xiě)第二篇文章。今天我將談?wù)劊簽镽ecyclerView添加分隔線。
一. 理解ListView和RecyclerView中的ChildView
在講為Item加入分割線本質(zhì)的前,先來(lái)介紹,認(rèn)識(shí)一下ChildView,也就是平時(shí)我們用到的ListView,RecyclerView中的getChildAt(int position)這個(gè)返回的ChildView是哪一部分?到底是哪一部分呢?一開(kāi)始的時(shí)候,我理解錯(cuò)了,但是經(jīng)過(guò)下面兩張圖這么一比較,你就明白了:
Item布局layout_margin == 0
Item布局Layout_margin == 16dp
下面看代碼的區(qū)別:
第一張圖的代碼, 也就是每一個(gè)list_item的布局文件(下同)如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp"> <TextView android:id="@+id/list_item" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="20sp" android:textColor="#262526" android:background="#08da1d"/> </LinearLayout>
第二張圖的代碼:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp" android:layout_margin="16dp"> <TextView android:id="@+id/list_item" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="20sp" android:textColor="#262526" android:background="#08da1d"/> </LinearLayout>
仔細(xì)看一下,它們的不同之處, 就是第二個(gè)圖的代碼中多了:
android:layout_margin = "16dp"
就多這一句而已。
所以到這里我們應(yīng)該知道了ChildView是哪一部分了,就是圖二中綠色這一部分,邊距這一部分并不屬于ChildView, 而是屬于ChildView的布局。
這樣我們了解ChildView之后,下面再來(lái)理解加入分隔線的原理就簡(jiǎn)單多了。
二. 理解加入分隔線的原理
在ListView中,Google為我們提供了SetDivider(Drawable divider)這樣的方法來(lái)設(shè)置分隔線,那么在RecyclerView中,Google又為我們提供了什么樣的方法去添加分隔線呢?通過(guò)查看官方文檔,它,提供了:addItemDecoration(RecyclerView.ItemDecoration decor)這個(gè)方法了設(shè)置分隔線,那問(wèn)題又來(lái)了,RecyclerView.ItemDecoration是什么東西呢?繼續(xù)查:然后發(fā)現(xiàn)如下:它原來(lái)是一個(gè)類,里面封裝了三個(gè)方法:
(1)void getItemOffsets ()
(2)void onDraw ()
(3)void onDrawOver ()
通過(guò)上面的三個(gè)方法,可以看出,這是要自己直接畫(huà)上去,準(zhǔn)確的說(shuō)這幾個(gè)方法是:添加Divider,主要是找到添加Divider的位置, 而Divider是在drawable文件中寫(xiě)好了的。 利用onDraw和onDrawOver都差不多,我們?cè)趧?chuàng)建自己的Decoration類繼承RecyclerView.ItemDecoration的時(shí)候,我們只要重寫(xiě)getItemOffsets(),還有onDraw()和onDrawOver兩者其中之一就可以了.
那getItemOffsets()方法有什么用呢?從字面意思就是Item要偏移, 由于我們?cè)贗tem和Item之間加入了分隔線,線其實(shí)本質(zhì)就是一個(gè)長(zhǎng)方形,也是用戶自定義的,既然線也有長(zhǎng)寬高,就畫(huà)橫線來(lái)說(shuō),上面的Item加入了分隔線,那下面的Item就要往下平移,平移的量就是分隔線的高度。不理解每關(guān)系,后面看代碼就容易理解了。
現(xiàn)在我們知道了如何添加了,就是通過(guò)畫(huà),那到底是畫(huà)在哪里呢?畫(huà)的位置又怎么確定呢?下面看圖:
分隔線的位置圖
我現(xiàn)在拿畫(huà)橫線來(lái)說(shuō),從上面這個(gè)圖中,我們很容易就可以看到,我們畫(huà)分隔線的位置,是在每一個(gè)Item的布局之間,注意:是布局之間。
好了,我們確定了畫(huà)在哪里,那我們?cè)趺创_定畫(huà)線的具體的坐標(biāo)位置呢?也就是我們要確定:分隔線的left, top, right, Bottom. 在Adapter中,我們很容易通過(guò)parent(這個(gè)parent它其實(shí)就是我們能看到的部分)獲取每一個(gè)childView:
(1)left:parent.getPaddingLeft()
(2)right: parent. getWidth()-parent.getPaddingRight();
(3)top : 就是紅線的上面:我們通過(guò)ChildView.getBottom()來(lái)得到這個(gè)Item的底部的高度,也就是藍(lán)線位置,藍(lán)線和紅線之間間距:就是這個(gè)Item布局文件的:layout_marginBottom, 然后top的位置就是兩者之和。
(4)bttom: 就是top加上分隔線的高度:top+線高
通過(guò)上面的解析,你也許知道了加入分隔線的原理,不理解也沒(méi)有關(guān)系,說(shuō)也不是說(shuō)得很清楚,下面直接上代碼,通過(guò)代碼來(lái)理解。
三. Talk is cheap, show you the code.
(1)首先,先來(lái)看主布局文件:activity_main.xml:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.study.wnw.recyclerviewdivider.MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> </android.support.design.widget.CoordinatorLayout>
我在這里面僅僅加入了一個(gè)RecyclerView
(2)RecyclerView里面每個(gè)Item的布局文件:item_view.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp" android:layout_margin="16sp"> <TextView android:id="@+id/list_item" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textSize="20sp" android:textColor="#f7f4f7" android:background="#08da1d"/> </LinearLayout>
這也沒(méi)有什么可講的,就是在里面添加一個(gè)TextView用來(lái)顯示文本
(3)我們RecyclerView的適配器MyAdapater.java:
package com.study.wnw.recyclerviewdivider; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * Created by wnw on 16-5-22. */ public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { //定義一個(gè)集合,接收從Activity中傳遞過(guò)來(lái)的數(shù)據(jù)和上下文 private List<String> mList; private Context mContext; MyAdapter(Context context, List<String> list){ this.mContext = context; this.mList = list; } //得到child的數(shù)量 @Override public int getItemCount() { return mList.size(); } //創(chuàng)建ChildView @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View layout = LayoutInflater.from(mContext).inflate(R.layout.item_view, parent, false); return new MyHolder(layout); } //將數(shù)據(jù)綁定到每一個(gè)childView中 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof MyHolder){ final String itemText = mList.get(position); ((MyHolder)holder).tv.setText(itemText); } } // 通過(guò)holder的方式來(lái)初始化每一個(gè)ChildView的內(nèi)容 class MyHolder extends RecyclerView.ViewHolder{ TextView tv; public MyHolder(View itemView) { super(itemView); tv = (TextView)itemView.findViewById(R.id.list_item); } } }
好了,這里也沒(méi)有什么好講的,也不是我們這篇文章的重點(diǎn),下面重點(diǎn)來(lái)了。
(4)我們自定義的MyDecoration.java:(繼承RecyclerView.ItemDecoration)
package com.study.wnw.recyclerviewdivider; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; /** * Created by wnw on 16-5-22. */ public class MyDecoration extends RecyclerView.ItemDecoration{ private Context mContext; private Drawable mDivider; private int mOrientation; public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; //我們通過(guò)獲取系統(tǒng)屬性中的listDivider來(lái)添加,在系統(tǒng)中的AppTheme中設(shè)置 public static final int[] ATRRS = new int[]{ android.R.attr.listDivider }; public MyDecoration(Context context, int orientation) { this.mContext = context; final TypedArray ta = context.obtainStyledAttributes(ATRRS); this.mDivider = ta.getDrawable(0); ta.recycle(); setOrientation(orientation); } //設(shè)置屏幕的方向 public void setOrientation(int orientation){ if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST){ throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (mOrientation == HORIZONTAL_LIST){ drawVerticalLine(c, parent, state); }else { drawHorizontalLine(c, parent, state); } } //畫(huà)橫線, 這里的parent其實(shí)是顯示在屏幕顯示的這部分 public void drawHorizontalLine(Canvas c, RecyclerView parent, RecyclerView.State state){ int left = parent.getPaddingLeft(); int right = parent.getWidth() - parent.getPaddingRight(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++){ final View child = parent.getChildAt(i); //獲得child的布局信息 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); //Log.d("wnw", left + " " + top + " "+right+" "+bottom+" "+i); } } //畫(huà)豎線 public void drawVerticalLine(Canvas c, RecyclerView parent, RecyclerView.State state){ int top = parent.getPaddingTop(); int bottom = parent.getHeight() - parent.getPaddingBottom(); final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++){ final View child = parent.getChildAt(i); //獲得child的布局信息 final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams(); final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } //由于Divider也有長(zhǎng)寬高,每一個(gè)Item需要向下或者向右偏移 @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if(mOrientation == HORIZONTAL_LIST){ //畫(huà)橫線,就是往下偏移一個(gè)分割線的高度 outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); }else { //畫(huà)豎線,就是往右偏移一個(gè)分割線的寬度 outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }
從上面的代碼中,我們還通過(guò)系統(tǒng)屬性來(lái)適應(yīng)屏幕的橫屏和豎屏,然后確定畫(huà)橫的,還是豎的Divider,其實(shí)在里面我們做了三件事,第一件是:獲取到系統(tǒng)中的listDivider, 我們就是通過(guò)它在主題中去設(shè)置的,下面第(6)小點(diǎn)看一下代碼就知道了。第二件事:就是找到我們需要添加Divider的位置,從onDraw方法中去找到,并將Divider添加進(jìn)去。第三個(gè)是:得到Item的偏移量。
(5)看看我們的MainActivity.java
package com.study.wnw.recyclerviewdivider; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { //定義RecyclerView private RecyclerView mRecyclerView = null; //定義一個(gè)List集合,用于存放RecyclerView中的每一個(gè)數(shù)據(jù) private List<String> mData = null; //定義一個(gè)Adapter private MyAdapter mAdapter; //定義一個(gè)LinearLayoutManager private LinearLayoutManager mLayoutManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //RecyclerView三步曲+LayoutManager initView(); initData(); mAdapter = new MyAdapter(this,mData); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setAdapter(mAdapter); //這句就是添加我們自定義的分隔線 mRecyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.VERTICAL_LIST)); } //初始化View private void initView(){ mLayoutManager = new LinearLayoutManager(this); mRecyclerView = (RecyclerView)findViewById(R.id.recyclerview); } //初始化加載到RecyclerView中的數(shù)據(jù), 我這里只是給每一個(gè)Item添加了String類型的數(shù)據(jù) private void initData(){ mData = new ArrayList<String>(); for (int i = 0; i < 20; i++){ mData.add("Item" + i); } } }
(6)分隔線Divider的drawable文件:divider..xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#7b7a7a"/> <size android:height="1dp"/> </shape>
我們?cè)谶@里面,畫(huà)了一個(gè):rectangle, 給它填充顏色,還有高度,這樣就搞定了,高度小,顯示出來(lái)也是一條線:其實(shí)線的本質(zhì)就是長(zhǎng)方形。這里可以根據(jù)個(gè)人需要,畫(huà)不同類型的divider
(7)在styles.xml的AppTheme中,設(shè)置listDivider為我們的divider.xml文件:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="android:listDivider">@drawable/divider</item> </style>
這樣,我們將系統(tǒng)的listDivider設(shè)置成我們自定義的divider. 還記得我們?cè)贛yDecoration中獲取系統(tǒng)的listDivider這個(gè)屬性嗎,這樣通過(guò)這個(gè)屬性,我們就可以將我們的divider.xml文件和MyDecoration.java進(jìn)行關(guān)聯(lián)了。
到這里所有的工作就完成了,下面展示一下運(yùn)行結(jié)果:
豎屏效果圖
橫屏效果圖
經(jīng)過(guò)幾個(gè)小時(shí)的寫(xiě)作,終于搞定了,雖然僅僅是一個(gè)添加分隔線的功能,但是還是想盡可能的通過(guò)自己的語(yǔ)言去理解,去認(rèn)知它的原理,這樣做起來(lái)就簡(jiǎn)單多了。一開(kāi)始的時(shí)候,我夜不知道怎么去用,也參考了別人寫(xiě)的文章,特別是鴻洋大神的:Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件, 寫(xiě)得特別的棒,從中也學(xué)到了一些知識(shí)。
好了,這篇文章暫時(shí)寫(xiě)到這里了,簡(jiǎn)單的介紹了一些RecyclerView分隔線的原理和添加方法,希望大家能夠多多交流,過(guò)幾天我會(huì)繼續(xù)寫(xiě)下一篇文章,RecyclerView系列之(3):為RecyclerView添加下拉刷新和上拉加載的功能。最后還是要感謝大家,感謝這個(gè)平臺(tái),能夠讓我們一起交流,一切學(xué)習(xí)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android popupWindow彈出窗體實(shí)現(xiàn)方法分析
這篇文章主要介紹了Android popupWindow彈出窗體實(shí)現(xiàn)方法,結(jié)合具體實(shí)例形式分析了Android彈出窗體的布局及popupwindow屬性設(shè)置、事件監(jiān)聽(tīng)相關(guān)操作技巧,需要的朋友可以參考下2017-07-07Android編程實(shí)現(xiàn)從字符串中查找電話號(hào)碼的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)從字符串中查找電話號(hào)碼的方法,涉及Android針對(duì)字符串的匹配與查找相關(guān)技巧,需要的朋友可以參考下2016-03-03Android 桌面Widget開(kāi)發(fā)要點(diǎn)解析(時(shí)間日期Widget)
總的來(lái)說(shuō),widget主要功能就是顯示一些信息。我們今天編寫(xiě)一個(gè)很簡(jiǎn)單的作為widget,顯示時(shí)間、日期、星期幾等信息。需要顯示時(shí)間信息,那就需要實(shí)時(shí)更新,一秒或者一分鐘更新一次2013-07-07Android編程實(shí)現(xiàn)網(wǎng)絡(luò)圖片查看器和網(wǎng)頁(yè)源碼查看器實(shí)例
這篇文章主要介紹了Android編程實(shí)現(xiàn)網(wǎng)絡(luò)圖片查看器和網(wǎng)頁(yè)源碼查看器,結(jié)合實(shí)例形式分析了Android針對(duì)網(wǎng)絡(luò)圖片及網(wǎng)頁(yè)的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-01-01Android RecyclerView添加上拉加載更多效果
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView添加上拉加載更多效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02總結(jié)Android中多線程更新應(yīng)用的頁(yè)面信息的方式
這篇文章主要介紹了總結(jié)Android中多線程更新應(yīng)用的頁(yè)面信息的方式,文中共總結(jié)了runOnUiThread、Handler、AsyncTask異步以及View直接在UI線程中更新的方法,需要的朋友可以參考下2016-02-02Android實(shí)現(xiàn)簡(jiǎn)潔的APP更新dialog數(shù)字進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)潔的APP更新dialog數(shù)字進(jìn)度條,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04