Android學習隨筆(8)------RecyclerView(2)

學習流程來自《第一行代碼》(第二版)
在之前的RecyclerView(1)開頭給出了對RecyclerView設置的幾個模塊。在書中只介紹了部分,這邊來擴展一下。

GridLayoutManager

RecyclerView能實現的佈局有很多,佈局都由LayoutManager來控制,可拓展性十分高。

GridLayoutManager layoutManager = new GridLayoutManager(this, 2);

把RecyclerView的LayoutManager設置成GridLayout並傳入上下文,和列數這兩個參數即可。
Exler
我們仍就靠隨機生成TextView的值,但是每個item的排列是網格類型的。

增加分隔線

可以明顯的看出之前我們的RecyclerView是沒有分隔線的。
addItemDecoration(ItemDecoration decoration) :方法就是用來設置分隔線的。
而ItemDecoration是一個抽象類,所以需要實現
1. void getItemOffsets ()
2. void onDraw ()
3. void onDrawOver ()
這三個方法。可以看出分隔線是要自己畫上去的。添加Divider,主要是找到添加Divider的位置, 而Divider是在drawable文件中寫好了的。 利用onDraw和onDrawOver都差不多,所以創建自己的Decoration類繼承RecyclerView.ItemDecoration的時候,只要重寫getItemOffsets(),還有onDraw()和onDrawOver兩者其中之一就可以了.

getItemOffsets()方法,從字面意思就是Item要偏移, 由於我們在Item和Item之間加入了分隔線,線其實本質就是一個長方形,也是用戶自定義的,既然線也有長寬高,就畫橫線來說,上面的Item加入了分隔線,那下面的Item就要往下平移,平移的量就是分隔線的高度。

先來比較兩張圖
Exler
Exler
可以看到第二張圖片中的每個item分隔明顯(ChildView,也就是平時用到的ListView,RecyclerView中的getChildAt(int position)這個返回的,這一部分指的是圖二中紅色的部分白色的部分屬於ChildView的佈局。)

一個View對應一個佈局


Exler

可以明顯的看出我們要畫的就是黑色的這一條線。這條線是加在兩個佈局之間的。

所以我們可以知道我們畫分隔線的位置,是在每一個Item的佈局之間。


確定畫線的具體的座標位置

分隔線的left, top, right, Bottom.
在Adapter中,我們很容易通過parent(這個parent它其實就是我們能看到的部分)獲取每一個childView:
1. left:parent.getPaddingLeft()
2. right: parent. getWidth()-parent.getPaddingRight();
3. top : 就是紅線的上面:我們通過ChildView.getBottom()來得到這個Item的底部的高度,也就是藍線位置,藍線和紅線之間間距:就是這個Item佈局文件的:layout_marginBottom, 然後top的位置就是兩者之和。
4. bttom: 就是top加上分隔線的高度:top+線高

Exler
可以看到,在這張圖片中兩個item之間有了一條系統默認(感覺是佈局空出來的背景色)的分割線。
用自定義的樣式來填充分隔線。在drawable文件夾下添加一個divider.xml文件。

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#00ff00" />
    <size android:height="1dp" />
</shape>

定義一條綠線。
在values文件下的styles.xml文件中style標籤之間添加

<item name="android:listDivider">@drawable/divider</item>

把自定義的樣式引用進來。

MainActivity.java :

List<String> strings = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initStr();

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);

        ItemAdapter adapter = new ItemAdapter(this,strings);

        recyclerView.setLayoutManager(manager);
        recyclerView.setAdapter(adapter);


        recyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.VERTICAL_LIST));
            }

    public void initStr() {
        strings = new ArrayList<String>();
        for (int i = 0; i < 50; i++)
            strings.add("Item" + i);
    }

大部分代碼與(1)類似 :
1. 初始化填入的數據
2. 定義一個LayoutManager,把垂直排列傳進去
3. 定義item的適配器
4. 定義分隔線(告知是垂直的佈局)

ItemAdapter.java

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {

    private List<String> mList;
    private Context mContext;

    public ItemAdapter(Context context, List<String> list) {
        this.mContext = context;
        this.mList = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_layout, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String str = mList.get(position);
        holder.textView.setText(str);
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {

        TextView textView;

        public ViewHolder(View view) {
            super(view);
            textView = (TextView) view.findViewById(R.id.list_item);
        }
    }
}

子項適配器的代碼。

Exler
可以看到item之間有一條淡淡的綠線。
效果不是特別好,可以選擇重新繪製一下divider.xml文件

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <gradient
        android:centerColor="#ff00ff00"
        android:endColor="#ff0000ff"
        android:startColor="#ffff0000"
        android:type="linear" />
    <size android:height="4dp"/>
</shape>

Exler
這個效果就是比較好的了。

 manager.setOrientation(LinearLayoutManager.HORIZONTAL);


 recyclerView.addItemDecoration(new MyDecoration(this, MyDecoration.HORIZONTAL_LIST));

更改一下這兩行代碼
並且可以把item_layout.xml的layout_width屬性改成固定值,如 :200dp
Exler
item的排列爲水平時的豎直分隔線效果圖。

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;

    public static final int[] ATRRS  = new int[]{ android.R.attr.listDivider};     //通過獲取系統屬性中的listDivider來添加,在AndroidManifest.xml中的AppTheme中設置

    public MyDecoration(Context context, int orientation) {
        this.mContext = context;
        final TypedArray ta = context.obtainStyledAttributes(ATRRS);    // TypedArray 用於存放 android 自定義控件 把屬性集 和我們自己定義的屬性集合建立映射關係
        this.mDivider = ta.getDrawable(0);
        ta.recycle();
        setOrientation(orientation);
    }

    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) {    // 水平 畫豎線
            drawVerricalLine(c, parent, state);
        } else {    // 子項佈局垂直排列時花在兩個佈局之間(上佈局的底部)
            drawHorizontalLine(c, parent, state);
        }
    }

    public void drawHorizontalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {    // 畫橫線 Android.graphics

        int left = parent.getPaddingLeft();    // 得到佈局的左邊距
        int right = parent.getWidth() - parent.getPaddingRight();    // 佈局右邊的終點 parent的寬度-(減去)佈局的右邊距(此程序得到的是一條佔滿屏幕寬度的線)
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();    // 獲取child的佈局信息 params 參數
            int top = child.getBottom() + params.bottomMargin;    // item View(Textview)的底部+佈局中的(頁下空白)
            int bottom = top +mDivider.getIntrinsicHeight();    // 高度 + Drawable的固有高度 dp爲單位
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawVerricalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {    //畫豎線
        int top = parent.getPaddingTop();    // 獲取parent的上邊距
        int bottom = parent.getHeight() - parent.getPaddingBottom();    //parent的高度-parent的下邊距
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();    // 獲得child的佈局信息
            int left = child.getRight() + params.rightMargin ;    // 從第一個item(item0)的尾部開始加入分隔線  View的最右邊 + 佈局的頁右空白
            int right = left + mDivider.getIntrinsicHeight() ;    // 兩個佈局之間的空隙 (此處應該與getItemOffsets()方法中的偏移數值相呼應)
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == HORIZONTAL_LIST) {    // 畫豎線 往右偏移
            Log.d("admin",""+mDivider.getIntrinsicWidth()+" "+mDivider.getIntrinsicHeight());    // 爲何輸出Width爲負數
            outRect.set(0, 0,mDivider.getIntrinsicHeight(), 0);    // mDivider.getIntrinsicHeight()
        } else {     ////畫橫線,往下偏移一個分割線的高度
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        }
    }
}

這就是添加分隔線的代碼,具體的代碼解釋含在註釋裏了,大致上是
1. 定義一個drawable,
2. 判斷屏幕方向,調用相應的畫線方法
3. 設定item之間的偏移

item的增加刪除

有時候item不是單一不變的,有時候會有添加和刪除的需求。
先在佈局中添加兩個button

<?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="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        >
    <Button
        android:id="@+id/add_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Add"
        android:layout_margin="15dp"/>
    <Button
        android:id="@+id/delete_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete"
        android:layout_margin="15dp"/>
    </LinearLayout>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </android.support.v7.widget.RecyclerView>
</LinearLayout>

在適配器代碼中添加增加和移除功能 :

    public void addData(int position) {    // 添加item
        String str = "新增item";
        mList.add(position, str);
        notifyItemInserted(position);
        notifyDataSetChanged();
        notifyItemRangeChanged(position, mList.size());
    }

    public void removeData(int position) {    // 刪除item
        mList.remove(position);

        notifyItemRemoved(position);
        notifyItemRangeChanged(position, mList.size());
    }

在MainActivity.java中 調用 適配器實例的增加和移除的方法 :

Button addData = (Button) findViewById(R.id.add_btn);
Button deleteData = (Button) findViewById(R.id.delete_btn);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
LinearLayoutManager manager = new LinearLayoutManager(this);
final ItemAdapter adapter = new ItemAdapter(this,strings);

manager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adapter);

recyclerView.setItemAnimator(new DefaultItemAnimator());    // 設置默認動畫

addData.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        adapter.addData(0);
    }
});

deleteData.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        adapter.removeData(0);
    }
});

這邊沒有顯示我們增加的分隔線。
運行點擊Add按鈕,可以看到有新增item加入 :
Exler

點擊移除 :
Exler
移除的時候還會出現我們在配置RecyclerView時調用的setItemAnimator(new DefaultItemAnimator()); 使用的默認動畫。


參考

http://www.jianshu.com/p/4eff036360da
http://blog.csdn.net/lmj623565791/article/details/45059587
http://blog.csdn.net/qq_21071977/article/details/78001509


遺留問題

1.

在LinearLayoutManager爲垂直的時候,畫豎分隔線

public void drawVerricalLine(Canvas c, RecyclerView parent, RecyclerView.State state) {    //畫豎線
        int top = parent.getPaddingTop();    // 獲取parent的上邊距
        int bottom = parent.getHeight() - parent.getPaddingBottom();    //parent的高度-parent的下邊距
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);

            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)child.getLayoutParams();    // 獲得child的佈局信息
            int left = child.getRight() + params.rightMargin ;    // 從第一個item(item0)的尾部開始加入分隔線  View的最右邊 + 佈局的頁右空白
            int right = left + mDivider.getIntrinsicHeight() ;    // 兩個佈局之間的空隙 (此處爲何用mDivider.getIntrinsicHeight())
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

獲取了一下mDivider.getIntrinsicWidth()方法的數值爲負。
Exler

爲何此數值爲負數

2.

添加item時,position = 0,自定義的List可以列表更新,但是界面上卻沒有顯示出這個新增item(只是整個RecyclerView閃動了一下),刪除的時候也能發現需要多刪一下。就是不能在position=0 的位置顯示新增item。(position = 1之後一切正常)

增加notifyDataSetChanged();可以在position=0處顯示出新增item,但是不出現新增動畫。

刪除功能一切正常。


此博文爲個人學習筆記,僅供個人學習使用,希望對大家有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章