Material Design之RecyclerView基本講解與瀑布流的實現

RecyclerView簡介

  RecyclerView是一種新的視圖組,目標是爲任何基於適配器的視圖提供類似的渲染方式。它被做爲ListView和GridView控件的繼承者,具備更優的靈活性與可替代性。在最新的support-v7版本中提供支持。本文將講解RecyclerView的簡單實現,添加刪除條目,點擊事件添加與瀑布流的實現。
java

相關原理與簡單實現

添加依賴

  在AndroidStudio的build.gradle中添加依賴: android

dependencies {
    ...
    compile 'com.android.support:recyclerview-v7:25.3.0'
}複製代碼

在佈局中使用

  添加完依賴後就能夠在佈局中使用RecyclerView了: git

<android.support.v7.widget.RecyclerView android:id="@+id/main_recyclerview" android:layout_width="match_parent" android:layout_height="match_parent" />複製代碼

RecyclerView.Adapter

  RecyclerView封裝了一種新型的適配器,與如今使用的適配器大同小異。它強制用戶使用RecyclerView提供的ViewHolder,使用時主要須要重寫onCreateViewHolder與onBindViewHolder方法。前者用來展示視圖及其持有者,且只有真正須要一個新view時纔會被回調,不須要檢查是否已經被回收。後者用來綁定數據到View上。 github

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;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private Context context;
    private List<String> list;
    private LayoutInflater inflater;

    public MyAdapter(Context context, List<String> list) {
        this.context = context;
        this.list = list;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.item_recyclerview, parent, false);
        MyViewHolder holder = new MyViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.textView.setText(list.get(position));
    }

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

    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        public MyViewHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.item_textview);
        }
    }
}複製代碼

LayoutManager

  RecyclerView經過佈局管理器LayoutManager控制每個item如何進行排列擺放,什麼時候展現和隱藏。回收或重用一個View時LayoutManager會向適配器請求新的數據來替換舊的數據,這種機制避免了建立過多的View和頻繁的調用findViewById方法,目前其自帶的主要有如下三種: bash

  • LinearLayoutManager:ListView樣式
  • GridLayoutManager:GridView樣式
  • StaggeredGridLayoutManager:瀑布流樣式

    ItemDecoration

      RecyclerView並不能像ListView同樣直接在xml佈局中修改item分割線樣式。須要在Activity動態設置,固然更推薦在單個條目佈局中設置margin或者padding來實現分割線效果,這裏提供一個分割線。
    ```java
    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.view.View;

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;dom

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;
        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;
        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);
        }
}複製代碼

}ide

#### ItemAnimator
  ItemAnimator會根據適配器上收到的通知來動畫顯示視圖組的修改,好比item的添加與刪除。DefaultItemAnimator已經能很好的展示動畫效果了。  
#### RecyclerView初始化
  若想初始化一個RecyclerView使其進入工做狀態,你須要在Activity中作如下的操做:  
```java
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view_main);
MyAdapter adapter = new MyAdapter(this, list);
//設置RecyclerView保持固定的大小
recyclerView.setHasFixedSize(true);
//設置適配器
recyclerView.setAdapter(adapter);
//設置RecyclerView ListView樣式佈局管理
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
//設置RecyclerView的Item分割線
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
//設置RecyclerView的動畫
recyclerView.setItemAnimator(new DefaultItemAnimator());

//設置RecyclerView GridView樣式
//recyclerView.setLayoutManager(new GridLayoutManager(MainActivity.this, 3));
//設置RecyclerView 水平GridView樣式
//recyclerView.setLayoutManager(new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.HORIZONTAL));
//設置RecyclerView 瀑布流樣式
//recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));複製代碼

點擊事件與添加刪除Item

  美中不足的是RecyclerView並無提供像ListView同樣的Item點擊與Item長點擊事件,不提供我們就本身造,經過接口回調來實現。 佈局

Adapter中

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.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private Context context;
    private LayoutInflater inflater;
    protected ArrayList<String> datas;
    private onItemClickedListener onItemClickedListener;

    public MyAdapter(Context context, ArrayList<String> datas) {
        this.context = context;
        this.datas = datas;
        inflater = LayoutInflater.from(context);
    }

    public void setOnItemClickedListener(MyAdapter.onItemClickedListener onItemClickedListener) {
        this.onItemClickedListener = onItemClickedListener;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.textView.setText(datas.get(position));
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = inflater.inflate(R.layout.item_recycler_view, parent, false);
        MyViewHolder myViewHolder = new MyViewHolder(view);
        return myViewHolder;
    }

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

   /** * 添加條目 */
    public void addItem(int position) {
        datas.add(position, "xulei");
    // notifyDataSetChanged();
        notifyItemInserted(position);//調用這個纔有動畫效果
    }

   /** * 移除條目 */
    public void removeItem(int position) {
        datas.remove(position);
        notifyItemRemoved(position);
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public MyViewHolder(View itemView) {
            super(itemView);
            //初始化控件
            textView = (TextView) itemView.findViewById(R.id.item_textview);
            //設置當前條目單擊監聽
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (onItemClickedListener != null)
                        onItemClickedListener.onClick(view, getAdapterPosition());//可馬上獲取到當前position
// onItemClickedListener.onClick(view, getLayoutPosition());//需等當前視圖更新完才能獲取到當前position,<16ms。
                }
            });
            //設置當前條目長按監聽
            itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    if (onItemClickedListener != null)
                        onItemClickedListener.onLongClick(view, getAdapterPosition());
                    return false;
                }
            });
        }
    }

   /** * 點擊回調的接口 */
    interface onItemClickedListener {
        void onClick(View view, int position);

        void onLongClick(View view, int position);
    }
}複製代碼

Activity中

  在Activity中實例化Adapter以後添加以下代碼:gradle

adapter.setOnItemClickedListener(new MyAdapter.onItemClickedListener() {
    @Override
    public void onClick(View view, int position) {
        adapter.addItem(position);
        Toast.makeText(MainActivity.this, "點擊click:" + position, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLongClick(View view, int position) {
        adapter.removeItem(position);
        Toast.makeText(MainActivity.this, "長按click:" + position, Toast.LENGTH_SHORT).show();
    }
});複製代碼

瀑布流的實現

  想實現瀑布流的樣式經過使用RecyclerView也很容易就能實現。動畫

首先在Activity中設置LayoutManager時選擇:

recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));複製代碼

修改Adapter

  瀑布流天然是每一個條目的高度不一樣才能出現瀑布的效果(水平佈局則是寬度不一樣),那麼只需在每一個條目綁定數據時動態改變下其高度便可,貼出瀑布流Adapter代碼:

import android.content.Context;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

public class StaggerAdapter extends MyAdapter {
    private List<Integer> heights;
    public StaggerAdapter(Context context, ArrayList<String> datas) {
        super(context, datas);
        heights = new ArrayList<>();
        for (int i = 0; i < datas.size(); i++) {
            heights.add((int) (100 + Math.random() * 300));
        }
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        layoutParams.height = heights.get(position);
// layoutParams.width = heights.get(position);
        holder.itemView.setLayoutParams(layoutParams);
        holder.textView.setText(datas.get(position));
    }
}複製代碼

總結

  經過實踐發現RecyclerView相較於ListView與GridView確實強大不少,更加的靈活與方便,提升了開發效率。但也有不足之處,如並未封裝點擊事件的回調,確實是比較頭疼。期待Google的完善。
附上GitHub源碼:
RecyclerViewDemo
RecyclerViewDevelop

相關文章
相關標籤/搜索