天天看點

《Android 基礎(四)》 RecyclerView

介紹

RecyclerView是ListView的豪華增強版。它主要包含以下幾處新的特性,如ViewHolder,ItemDecorator,LayoutManager,SmothScroller以及增加或删除item時item動畫等。官方推薦我們采用RecyclerView來取代ListView。

相對優勢

  • ViewHolder

    ListView需要自己實作ViewHolder來提高性能,或者不使用ViewHolder,但是使用ViewHolder來綁定對象是一個很好的習慣。RecyclerView很好的幫我們解決了這個問題,RecyclerView.ViewHolder在使用RecyclerView過程中必須實作,因為它是一個抽象類無法直接建立,需要自己完成對應子類的建立然後使用

  • LayoutManager

    ListView隻能在垂直方向上滾動,不支援其他的滾動方式,當然開發者有很多自定義的方式完成這些功能,這裡就不做争辯,從設計的角度上看,ListView設計之初應該就沒有想過讓它完成這些複雜的功能,隻是為了單純的清單顯示。但是RecyclerView相較于ListView,在滾動上面的功能擴充了許多。它可以支援多種類型清單的展示要求,主要如下:

    GridLayoutManager ,支援網格展示,可以水準或者豎直滾動,如展示圖檔的畫廊。

    LinearLayoutManager ,可以支援水準和豎直方向上滾動的清單。

    StaggeredGridLayoutManager ,可以支援交叉網格風格的清單,類似于瀑布流或者Pinterest。

  • ItemAnimation

    ItemAnimation是RecyclerView中子項在增加,删除或者移動的情況下顯示的動畫效果,Google越來越重視使用者體驗,從屬性動畫的推出開始,這就是一個趨勢,開發者在這裡可以自己實作自己想要添加的動畫效果,當然,如果你是個懶漢,請使用new DefaultItemAnimator() 。

  • ItemDecoration

    ItemDecoration,名字起的很文藝,子項的裝飾,RecyclerView在預設情況下并不在item之間展示間隔符。如果你想要添加間隔符,你必須使用RecyclerView.ItemDecoration類來實作。懶漢請使用DividerItemDecoration.java。

Recycler示例

實際效果圖

《Android 基礎(四)》 RecyclerView

上面這個效果圖是使用的StaggeredGridLayoutManager

代碼層面

布局檔案

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/colorMainBackground">

    <include layout="@layout/toolbar"></include>

    <FrameLayout 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"
        tools:context=".model.View.MainActivity">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab_add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_marginBottom="30dp"
            android:layout_marginRight="30dp"
            android:src="@drawable/addone" />

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab_del"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|left"
            android:layout_marginBottom="30dp"
            android:layout_marginLeft="30dp"
            android:src="@drawable/delone" />
    </FrameLayout>
</LinearLayout>
           

recycler_item.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

        <android.support.v7.widget.CardView
            android:layout_margin="10dp"
            android:id="@+id/cv_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:cardCornerRadius="20dp"
            app:cardElevation="5dp">
                <TextView
                    android:id="@+id/tv_name"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center" />

        </android.support.v7.widget.CardView>

</FrameLayout>
           

主Activity

public class MainActivity extends AppCompatActivity {
    @Bind(R.id.rv_content)
    RecyclerView rvContent;
    @Bind(R.id.fab_add)
    FloatingActionButton fabAdd;
    @Bind(R.id.fab_del)
    FloatingActionButton fabDel;
    @Bind(R.id.root_layout)
    LinearLayout rootLayout;

    private LayoutManager mLayoutManager; //LayoutManager
    private RVAdapter recyclerAdapter;    //RecyclerView對應的Adapter
    private ArrayList<String> mContentList; //内容list這裡隻是使用了字元串,當然也可以替換成其他的JavaBean類
    private Random mRandom = new Random(); //用于産生随機字元串
    private int mSum = 50; //初始化子項數目

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);  //ButterKnife注入,減少代碼負擔

        mContentList = new ArrayList<String>();
        for (int i = 0; i < mSum; i++) {
            mContentList.add(getRandomString());
        } //随機生成資料
        recyclerAdapter = new RVAdapter(mContentList);
        mLayoutManager = new StaggeredGridLayoutManager(3, LinearLayoutManager.VERTICAL); //3列縱向
        rvContent.setAdapter(recyclerAdapter);
        rvContent.setLayoutManager(mLayoutManager);
        rvContent.setItemAnimator(new DefaultItemAnimator()); //設定動畫效果,可以看下上面的效果圖,動畫效果還是比較明顯的
    }

    @OnClick({R.id.fab_add, R.id.fab_del})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.fab_add:
                recyclerAdapter.addData(1); //加一個
                makeSnackBar(rootLayout, "添加一個 :)", null, null);//顯示一個Snackbar
                break;
            case R.id.fab_del:
                recyclerAdapter.removeData(1); //去掉一個
                makeSnakeBar(rootLayout, "删除一個 :(", null, null); //顯示一個Snackbar
                break;
            default:
                break;
        }
    }
    //用于産生随機字元串的方法
    public String getRandomString() {
        String src = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        StringBuilder dst = new StringBuilder(4);
        for (int i = 0; i < 4; i++) {
            dst.append(src.charAt(mRandom.nextInt(62)));
        }
        return dst.toString();
    }
    
    //建構一個Snackbar并顯示出來
    private void makeSnackBar(View view, String message, String buttonText, View.OnClickListener onClickListener) {
        Snackbar.make(view, message, Snackbar.LENGTH_SHORT)
                .setAction(buttonText, onClickListener)
                .show();
    }
}

           

擴充卡

public class RVAdapter extends RecyclerView.Adapter<RVAdapter.MyViewHolder>{
    ArrayList<String> mContentList;
    Random mRandom = new Random();
    //ViewHolder繼承自RecyclerView.ViewHolder  子View拿到友善後面通路
    public class MyViewHolder  extends RecyclerView.ViewHolder {
        TextView textView;
        CardView cardView;
        public MyViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.tv_name);
            cardView = (CardView) itemView.findViewById(R.id.cv_bg);
        }
    }
    public RVAdapter(ArrayList<String> mContentList) {
        this.mContentList = mContentList;
    }
    //建立ViewHolder,由于RecyclerView.ViewHolder是一個抽象類無法執行個體化,是以必須實作一個子類才能使用,這裡自己嘗試的過程中走了一些彎路,注意inflate最後一個參數設定成false不然可能會出現crash
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_item, parent, false);
        return new MyViewHolder(view);
    }

    //onBindView  用于設定需要顯示的View中的内容和一些屬性值
    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.cardView.setCardBackgroundColor(getRandomColor());
        holder.textView.setText(mContentList.get(position));
        holder.itemView.getLayoutParams().height = getRandomHeight(200,400);//産生随機高度,看上去像瀑布
    }

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

    //産生随機顔色
    private int getRandomColor() {
        return (0xff000000|mRandom.nextInt(0x00ffffff));
    }
    //産生随機高度
    private int getRandomHeight(int min , int max) {
        return (mRandom.nextInt(max - min) + min );
    }
    //添加一個子項
    public void addData(int position) {
        mContentList.add(position, "Insert One");
        notifyItemInserted(position);//不調用這個沒有動畫效果
    }
    //删除一個子項
    public void removeData(int position) {
        mContentList.remove(position);
        notifyItemRemoved(position);//不調用這個沒有動畫效果
    }
}

           

需要注意的幾個地方:

  1. 實作自己的ViewHolder繼承recyclerView,ViewHolder,因為抽象類不能執行個體化
  2. inflate子項的時候,最後一個參數設定成false
  3. 動畫效果需要在Adapter中調用notifyItem***方法才行

StaggeredGridLayoutManager的效果圖上面已經有顯示了

其他效果

LinearLayoutManager效果圖

對應修改代碼

mLayoutManager = new LinearLayoutManager(this);
rvContent.addItemDecoration(new DividerItemDecoration(this, StaggeredGridLayoutManager.VERTICAL));
           
《Android 基礎(四)》 RecyclerView

GridLayoutManager效果圖

對應修改代碼

mLayoutManager = new GridLayoutManager(this, 3);//3列
rvContent.addItemDecoration(new DividerItemDecoration(this, StaggeredGridLayoutManager.VERTICAL));
           
《Android 基礎(四)》 RecyclerView

StaggeredGridLayoutManager.HORIZONTAL橫向的效果圖

對應修改代碼

mLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.HORIZONTAL);

RVAdapter.java中onBindViewHolder
holder.itemView.getLayoutParams().width = getRandomHeight(200,400);
           
《Android 基礎(四)》 RecyclerView

關于ItemDecoration

這裡附上Google Sample中的DividerItemDecoration.java代碼 希望可以幫助到大家

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };
    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    private Drawable mDivider;
    private int mOrientation;

    public DividerItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.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) {
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }

    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin +
                    Math.round(ViewCompat.getTranslationY(child));
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }


    public void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getRight() + params.rightMargin +
                    Math.round(ViewCompat.getTranslationX(child));
            final int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }
}
           

備注

Android源碼中有很多關于這些View的使用方法,大家可以查閱并參考使用。

博文中部分代碼:

http://download.csdn.net/detail/poorkick/9541326

繼續閱讀