1. 程式人生 > >Android:RecyclerView 的使用,有這一篇就夠了

Android:RecyclerView 的使用,有這一篇就夠了

謹以文章記錄學習歷程,如有錯誤還請指明。

RecyclerView 簡介

首先,可以理解 RecyclerView 是 ListView 的升級版,更加靈活,同時由於封裝了 ListView 的部分實現,導致其使用更簡單,結構更清晰。

從名字 RecyclerView 來看,它直接提供了回收複用的功能,雖然 ListView 我們也可以自己實現 ViewHolder 以及 convertView 進行優化,但是在 RecyclerView 中,它直接封裝了 ViewHolder 的回收複用,也就是說 RecyclerView 將 ViewHolder 標準化,我們不再需要面向 view ,而是直接面向 ViewHolder 編寫實現我們需要的 Adapter,這樣一來,邏輯結構就變得非常清晰。

當然,說到 RecyclerView 的優點,就不得不提它的 插拔式 的體驗,高度解耦:

  • 佈局(顯示方式):可通過LayoutManager(LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager )設定;
  • 分割線:通過 ItemDecoration 實現
  • Item 增刪動畫:通過 ItemAnimator
  • ViewHolder 的建立和繫結:通過實現 Adapter

除此之外,還需要額外提一下關於點選事件的問題,RecyclerView 本身是不提供點選、長按事件的,而隔壁的 ListView 穩穩支援。對此,可能剛接觸 RecyclerView 的同學會瘋狂吐槽,怎麼作為升級版的 RecyclerView 在這一點上還不如舊版呢?

情況真的是這樣麼?

顯然不是

ListView 中對於點選事件的處理,其實是有很大弊端的,它的 setOnItemClickListener() 方法是為子項註冊點選事件,這就導致只能識別到這一整個子項,對於子項中的元件比如按鈕就束手無策了。為此,RecyclerView 直接放棄了這個為子項註冊點選事件的監聽方法,所有點選事件都有具體 View 去註冊,好處顯而易見,我可以按需為元件註冊點選事件,不存在點選不到的元件。

基礎知識補充

如果讀者從未接觸過 ListView 和 RecyclerView,那麼可能對前面提到的 ViewHolder、Adapter等一頭霧水,為此在這裡補充一些基礎知識。

  • LayoutManager:RecyclerView 會根據 Layout Manager 提供的檢視來填充自身,常用的佈局管理器有LinearLayoutManager(線性佈局管理器)、GridLayoutManager(網格佈局管理器)、StaggeredGridLayoutManager (瀑布流佈局管理器)等。
  • ViewHolder:列表中的檢視由 ViewHolder 例項展示。 ViewHolder 用於對控制元件的例項進行快取,負責顯示子項。例如,如果列表顯示了音樂集合,那麼每個 ViewHolder 可能代表一個專輯。
  • Adapter:ViewHolder 物件由 Adapter 管理。Adapter 按需建立 ViewHolder,併為其繫結資料。繫結意味著根據 Adapter 中的位置為子檢視填充對應的資料。

下面我們就介紹一些關於RecyclerView 的用法。

RecyclerView 用法

基礎用法

首先,由於 RecyclerView 屬於 v7-支援庫,因此我們在使用之前需要先新增依賴:

dependencies {
    implementation 'com.android.support:recyclerview-v7:27.1.1'
}

接下來,RecyclerView 的核心使用流程如下:

mRecyclerView = findView(R.id.id_recycler_view);
//設定佈局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
//設定adapter
mRecyclerView.setAdapter(mAdapter)
//設定Item增加、移除動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//新增分割線
mRecyclerView.addItemDecoration(new DividerItemDecoration(
                getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

可能用慣了 ListView ,第一次接觸 RecyclerView 的同學會覺得,ListView 只需要一個 Adapter 就足夠了,怎麼 RecyclerView 的使用這麼繁瑣。

其實恰恰正是由於 RecyclerView 的高度解耦,本身只提供基本的回收和複用,而其他特性都允許我們自己定製,因此我們可以通過它來靈活地實現各種 複雜花哨 的效果。可以說,RecyclerView 以增加部分程式碼的代價來增加其靈活性和擴充套件性。

下面就來介紹一下 如何通過 RecyclerView 輕鬆實現一個普通列表:

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private MyAdapter mMyAdapter;
    private LinearLayoutManager mLayoutManager;
    private List<String> list;

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

        mRecyclerView = findViewById(R.id.recycler_view);
        mMyAdapter = new MyAdapter(list);
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mMyAdapter);
    }

    private void initData() {
        list = new ArrayList<>();
        for (int i = 0; i <= 20; i++) {
            list.add("Item " + i);
        }
    }
}

MyAdapter.java

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

    //資料來源
    private List<String> mList;

    public MyAdapter(List<String> list) {
        mList = list;
    }

    //返回item個數
    @Override
    public int getItemCount() {
        return mList.size() ;
    }

    //建立ViewHolder
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new NormalHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));
    }

    //填充檢視
    @Override
    public void onBindViewHolder(@NonNull final MyAdapter.ViewHolder holder, final int position) {
        holder.mView.setText(mList.get(position));
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mView;

        public ViewHolder(View itemView) {
            super(itemView);
            mView = itemView.findViewById(R.id.text_view);
        }
    }
}

MainActivity 的佈局檔案,activity_main.xml

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

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:divider="#FFFF0000"
        android:dividerHeight="10dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout >

子項佈局,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="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:gravity="center"
        android:background="#c0c7c759"
        android:textSize="20sp"
        android:text="1" />
</FrameLayout>

通過上述程式碼,就輕輕鬆鬆實現了一個列表,效果如下:

這裡寫圖片描述

可以看到,RecyclerView 的實現結構很清晰,且內部直接封裝了 ViewHolder,使得我們不再需要像 ListView 一樣自己實現 ViewHolder+convertView 去優化,我們只需要面向 ViewHolder 變成即可,關於複用優化的部分 RecyclerView 已經幫我們做好了。

分割線 - ItemDecoration

雖然上面我們實現了一個 列表介面,但是!!這實在是醜的沒法看了,我們隨便拉出來一個手機裡的app,裡面的列表顏值都秒殺它,拿微信介面為例:

這裡寫圖片描述

很明顯,不同模組間有明顯的分割空白,而相同模組不同功能也會有一個分割線,整體看起來結構很清晰。

為了給我們的列表新增分割線的效果,我們就需要了解一下 `RecyclerView.addItemDecoration() 方法,該方法接收一個RecyclerView.ItemDecoration 型別引數,其本身是一個抽象類,這就表明我們可以通過繼承這個類,編碼實現我們所需要的 複雜花哨 的分割線效果。

下面,我們先來了解一下 ItemDecoration 這個類,原始碼如下:

public abstract static class ItemDecoration {
        //使用此方法繪製的任何內容都將在繪製子項檢視之前繪製,從而出現在檢視之下。
        public void onDraw(Canvas c, RecyclerView parent, State state) {
            onDraw(c, parent);
        }

        @Deprecated
        public void onDraw(Canvas c, RecyclerView parent) {
        }

        //使用此方法繪製的任何內容都將在繪製子項檢視之後繪製,從而出現在檢視之上。
        public void onDrawOver(Canvas c, RecyclerView parent, State state) {
            onDrawOver(c, parent);
        }

        @Deprecated
        public void onDrawOver(Canvas c, RecyclerView parent) {
        }

        @Deprecated
        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
            outRect.set(0, 0, 0, 0);
        }

        //為每個Item設定一定的偏移量
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }
    }

可以看到,這個類實際上是很簡單的,可供使用的只有三個方法,而其中 onDraw() 方法和 onDrawOver() 方法實際使用中是沒什麼區別的(可能是我太菜,沒遇到要嚴格區分的場景)。於是我們如果想實現自定義效果時,只需要重寫 onDraw()或者onDrawOver() 方法和 getItemOffsets() 方法即可。當 RecyclerView 繪製時,會根據 getItemOffsets() 方法計算每個 Item 的位置,然後通過 onDraw() 方法繪製出來。

下面我們看官方為我們提供的預設實現類 DividerItemDecoration,原始碼如下:

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
    public static final int VERTICAL = LinearLayout.VERTICAL;

    private static final String TAG = "DividerItem";
    private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };

    private Drawable mDivider;

    /**
     * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}.
     */
    private int mOrientation;

    private final Rect mBounds = new Rect();

    /**
     * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a
     * {@link LinearLayoutManager}.
     *
     * @param context Current context, it will be used to access resources.
     * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}.
     */
    public DividerItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        if (mDivider == null) {
            Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "
                    + "DividerItemDecoration. Please set that attribute all call setDrawable()");
        }
        a.recycle();
        setOrientation(orientation);
    }

    /**
     * Sets the orientation for this divider. This should be called if
     * {@link RecyclerView.LayoutManager} changes orientation.
     *
     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
     */
    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL && orientation != VERTICAL) {
            throw new IllegalArgumentException(
                    "Invalid orientation. It should be either HORIZONTAL or VERTICAL");
        }
        mOrientation = orientation;
    }

    /**
     * Sets the {@link Drawable} for this divider.
     *
     * @param drawable Drawable that should be used as a divider.
     */
    public void setDrawable(@NonNull Drawable drawable) {
        if (drawable == null) {
            throw new IllegalArgumentException("Drawable cannot be null.");
        }
        mDivider = drawable;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null || mDivider == null) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

    private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int left;
        final int right;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int top;
        final int bottom;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            top = parent.getPaddingTop();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(parent.getPaddingLeft(), top,
                    parent.getWidth() - parent.getPaddingRight(), bottom);
        } else {
            top = 0;
            bottom = parent.getHeight();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
            final int right = mBounds.right + Math.round(child.getTranslationX());
            final int left = right - mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        if (mDivider == null) {
            outRect.set(0, 0, 0, 0);
            return;
        }
        if (mOrientation == VERTICAL) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

實際上,該實現類會讀取系統主題中的@android:attr/listDivider作為 Item 的分割線(本質是 Drawable ),同時還注意到有這樣一個方法 setDrawable(),該方法允許我們為分割線指定樣式。也就是說,如果我們只是單純為了改變分割線樣式,我們有兩種方式來改變分割線的樣式:

  • 呼叫 setDrawable() 直接傳入一個 drawable 物件
  • 修改系統主題中的 android:listDivider 屬性值

我們來實際操作一下,在原來的 MainActivity.java 中為 RecyclerView 新增預設分割線,如下:

mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

執行一下,結果如下:

這裡寫圖片描述

下面我們再為分割線更換一種樣式:

首先在drawable/路徑下建立一個資原始檔divider.xml

<?xml version="1.0" encoding="utf-8"?>
<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>
  • 通過 setDrawable() 方法:

    //原始碼:
    mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
    
    //改成如下:
    DividerItemDecoration decoration = new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
         decoration.setDrawable(getResources().getDrawable(R.drawable.divider,null));
    mRecyclerView.addItemDecoration(decoration);
  • 或者 修改系統主題中的 android:listDivider 屬性值:

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
          <item name="android:listDivider">@drawable/divider</item>
          ...
      </style>

二者實現的效果是一樣的,如下:

這裡寫圖片描述

是不是有一點點酷炫?是不是對 RecyclerView 躍躍欲試了?別急,RecyclerView 的厲害之處遠不止這些。

佈局 - LayoutManager

前面我們提到,RecyclerView 可以通過設定不同的 LayoutManager 來達到不同的佈局效果,如線性佈局,網格佈局等,這裡我們就來介紹一下,關於 LayoutManager 的使用。

LayoutManager 負責在 RecyclerView 中度量和定位 Item 檢視,並確定何時回收使用者不再可見的 Item 的策略。通過更改 LayoutManager,可以使用 RecyclerView 實現標準的垂直滾動列表、統一的網格、交錯網格、水平滾動集合等。

LayoutManager 仍然只是一個抽象類,官方為我們提供了幾個實現類:

  1. LinearLayoutManager:LayoutManager 的子類,線性佈局管理器,支援橫向、縱向
  2. GridLayoutManager:LinearLayoutManager 的子類,網格佈局管理器
  3. StaggeredGridLayoutManager:LayoutManager 的子類,瀑布流佈局管理器

前面我們一直使用的都是 LinearLayoutManager,下面我們就來試一下後兩個佈局管理器。

首先是 GridLayoutManager,修改程式碼:

//mLayoutManager = new LinearLayoutManager(this);

mLayoutManager = new GridLayoutManager(this,4);
mRecyclerView.setLayoutManager(mLayoutManager);

執行一下。

這裡寫圖片描述

首先,我們實現了 網格佈局,即類似 GridView 的效果。

但是我們也會發現,分割線繪製了一整行,顯然這個效果出了些問題,這是因為在繪製的時候,水平線的計算方式是:父佈局的 左邊緣~右邊緣,自然繪製了一整行,部分原始碼如下:

if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

這還不算強,真正強大的是 RecyclerView 分分鐘可以實現一個 ListView 難以實現的瀑布流,也就是通過 StaggeredGridLayoutManager 來實現瀑布流佈局。

//mLayoutManager = new GridLayoutManager(this,4);

mLayoutManager = new StaggeredGridLayoutManager (4,StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(mLayoutManager);
//如果傳入 StaggeredGridLayoutManager.VERTICAL則該佈局支援縱向滑動,那麼前面的 4 則指的是 4列。
//如果傳入 StaggeredGridLayoutManager.HORIZONTAL,則該佈局支援橫向滑動,那麼前面的4 則指的是 4行

效果如下

這裡寫圖片描述

看起來和 網格佈局沒什麼區別嘛,不要急,我們只需要為 Item 指定一個隨即高度就好了。

//MyAdapter.java
 @Override
    public void onBindViewHolder(@NonNull final MyAdapter.ViewHolder holder, final int position) {
        holder.mView.setText(mList.get(position));

        //加入以下程式碼
        ViewGroup.LayoutParams layoutParams = holder.mView.getLayoutParams();
        layoutParams.height = new Random().nextInt(200)+200;
        holder.mView.setLayoutParams(layoutParams);
    }
//Item.xml 為Item新增外邊距
//實際上分割線也可以通過新增邊距的方式“變相”實現,但是這種偷雞的做法肯定不如去實現一個 ItemDecoration
<TextView
        ...
        android:layout_margin="10dp"
        ...
       />

效果如下:

這裡寫圖片描述

看到此,可以說用 RecyclerView 實現 ListView、GridView、瀑布流的效果簡直毫無壓力。

動畫 - ItemAnimator

動畫也是 RecyclerView 很強大的一點,我們可以通過實現 ItemAnimator 這個抽象類來定製我們想要的動畫。

官方為我們提供了一種預設的實現 DefaultItemAnimator ,藉助這個類,我們就可以加入 Item 的新增和刪除動畫,實現起來很簡單,只需要加入下面這一行程式碼即可:

mRecyclerView.setItemAnimator(new DefaultItemAnimator());

效果如下:

當然我們需要對現有程式碼做出修改

//MyAdapter.java 加入以下方法

public void addData(int position) {
    mList.add(position, "Insert One");
    notifyItemInserted(position);
}

public void removeData(int position) {
    mList.remove(position);
    notifyItemRemoved(position);
}

//新增menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/add"
        android:icon="@drawable/ic_add"
        android:title="Add"/>
    <item android:id="@+id/delete"
        android:icon="@drawable/ic_delete"
        android:title="delete"/>
</menu>

//在MainActivity.java 加入以下方法
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.add:
            mMyAdapter.addData(1);
            break;
        case R.id.delete:
            mMyAdapter.removeData(1);
            break;
    }
    return true;
}

上述只是展示了 StaggeredGridLayoutManager 的動畫,另外的佈局讀者可以自行嘗試。

當然本文只涉及到系統提供的預設動畫實現,至於更多賊雞兒好看的動畫效果,就推薦一個GayHub 上 2.9K Star 的專案:RecyclerViewItemAnimators

點選事件 - 介面回撥

關於點選事件,前面我們分析過為什麼 RecyclerView 不提供點選事件,這裡再重複一下:

  • ListView 中點選事件只能針對子項,無法監聽內部控制元件
  • RecyclerView 按需對任意 View 設定點選監聽,不存在點選不到的控制元件。

這裡提供通過介面回撥的方式來實現點選事件。除此之外,也可以通過手勢判斷,由於筆者現在對手勢沒有了解,讀者可以自行實現。

MyAdapter.java

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

    //...
    //定義介面 OnItemClickListener
    public interface OnItemClickListener {
        void onItemClick(View view, int position);
        void onItemLongClick(View view, int position);
    }
    private OnItemClickListener mOnItemClickListener;

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
}   

    //填充檢視
    @Override
    public void onBindViewHolder(@NonNull final MyAdapter.ViewHolder holder, final int position) {
        holder.mView.setText(mList.get(position));
        if (mOnItemClickLitener != null)
        {
            holder.itemView.setOnClickListener(new OnClickListener()
            {
                @Override
                public void onClick(View v)
                {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.onItemClick(holder.itemView, pos);
                }
            });

            holder.itemView.setOnLongClickListener(new OnLongClickListener()
            {
                @Override
                public boolean onLongClick(View v)
                {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.onItemLongClick(holder.itemView, pos);
                    return false;
                }
            });
    }

    //...
}

MainActivity.java

mMyAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this, ((TextView)view).getText() + " click", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, int position) {

                Toast.makeText(MainActivity.this, position + " Long click", Toast.LENGTH_SHORT).show();
                mMyAdapter.removeData(position);
            }
        });

通過 drawable 設定點選效果

//設定點選效果
<TextView
    android:background="@drawable/item_press"
    .../>


//drawable/item_perss.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:state_pressed="true" android:drawable="@color/colorPressed"/>
    <item android:drawable="@color/colorNormal"/>
</selector>

//colors.xml 新增如下
<resources>
    ...
    <color name="colorNormal">#c0c7c759</color>
    <color name="colorPressed">#00FF00</color>
</resources>

我們在 MyAdapter 中定義監聽介面,在 onBindViewHolder() 中為 holder.itemView 設定點選事件,並回調 MainActivity.java 中設定的監聽事件。

效果如下:

下拉更新 - SwipeRefreshLayout

下拉更新,我們藉助 SwipeRefreshLayout 就可以相當容易的實現了。SwipeRefreshLayout 是 Google 在API 19.1 之後加入的用於下拉重新整理的控制元件,通常情況下,我們只需要實現其內部的 SwipeRefreshLayout.OnRefreshListener介面即可實現下拉重新整理的功能。話不多說,動手實踐。

首先修改 RecyclerView 的佈局,在其外部巢狀一個 SwipeRefreshLayout。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipe_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:divider="#FFFF0000"
        android:dividerHeight="10dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.v4.widget.SwipeRefreshLayout>

MainActivity.java 中實現SwipeRefreshLayout.OnRefreshListener介面:

public class MainActivity extends AppCompatActivity {   
    //.....

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        //...

        mRefreshLayout = findViewById(R.id.swipe_layout);
        mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //重新整理動畫開始後 回撥此方法

                //設定可見
                mRefreshLayout.setRefreshing(true);

                //向頭部插入資料
                List<String> newDatas = new ArrayList<String>();
                for (int i = 0; i < 5; i++) {
                    int index = i + 1;
                    newDatas.add("new item" + index);
                }
                mMyAdapter.addItem(newDatas);
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //模擬載入時間,設定不可見
                        mRefreshLayout.setRefreshing(false);
                    }
                }, 1000);
            }
        });

        //.....

    }

    //....
}

MyAdapter.java 中加入 addItem() 方法,供 下拉重新整理 時呼叫

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    //...

    public void addItem(List<String> list){
        list.addAll(mList);
        mList.clear();
        mList.addAll(list);
        notifyDataSetChanged();
    }

    //....
}

效果如圖:

是不是很簡單~
(第二次重新整理時由於新資料和第一次一樣,導致會有第二次沒刷新出來新資料的錯覺)

上拉載入更多 - footerview

筆者這裡的上拉載入更多,主要實現的是類似分頁載入的功能,一開始只加載部分資料,這樣當資料量過大或者網速過慢時,不至於影響使用者體驗。

具體的實現思路,引入一個 footerview,滑動的時候通過實現 OnScrollListener 介面判斷 footerview 的位置,從而判斷當前是否需要載入更多資料。

為了方便,筆者這裡用最基本的 LinearLayoutManager 實現縱向線性佈局,並取消了上面的隨機高度。

具體看程式碼吧,註釋足夠詳細了。

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private SwipeRefreshLayout mRefreshLayout;
    private RecyclerView mRecyclerView;
    private MyAdapter mMyAdapter;
    private LinearLayoutManager mLayoutManager;
    private List<String> list;

    //最後一個可見Item的位置,關鍵所在
    private int lastVisibleItem;
    private Handler mHandler = new Handler(Looper.getMainLooper());

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

        mRefreshLayout = findViewById(R.id.swipe_layout);
        mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //設定可見
                mRefreshLayout.setRefreshing(true);
                List<String> newDatas = new ArrayList<String>();
                for (int i = 0; i < 5; i++) {
                    int index = i + 1;
                    newDatas.add("new item" + index);
                }
                mMyAdapter.addItem(newDatas);
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //模擬載入時間,設定不可見
                        mRefreshLayout.setRefreshing(false);
                    }
                }, 1000);
            }
        });

        mRecyclerView = findViewById(R.id.recycler_view);
        mMyAdapter = new MyAdapter(list);
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mMyAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(MainActivity.this, ((TextView)view).getText() + " click", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, int position) {

                Toast.makeText(MainActivity.this, position + " Long click", Toast.LENGTH_SHORT).show();
                mMyAdapter.removeData(position);
            }
        });
        mRecyclerView.setAdapter(mMyAdapter);

        //設定滑動監聽
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

                //判斷是否滑動到底
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==mMyAdapter.getItemCount()) {
                    //滑動到底,需要改變狀態為 上拉載入更多
                    mMyAdapter.changeMoreStatus(MyAdapter.LOADING_MORE);
                    //模擬載入資料
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            List<String> newDatas = new ArrayList<String>();
                            for (int i = 0; i < 5; i++) {
                                int index = i + 1;
                                newDatas.add("more item" + index);
                            }
                            mMyAdapter.addMoreItem(newDatas);

                            //此時顯示 正在載入中
                            mMyAdapter.changeMoreStatus(MyAdapter.PULLUP_LOAD_MORE);
                        }
                    },2500);
                }
            }

            //更新最後可見位置
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();

            }
        });

    }
    private void initData() {
        list = new ArrayList<>();
        for (int i = 0; i <= 20; i++) {
            list.add("Item " + i);
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add:
                mMyAdapter.addData(1);
                break;
            case R.id.delete:
                mMyAdapter.removeData(1);
                break;
        }
        return true;
    }


}

MyAdapter.java

package com.bit.whdalive.demo;

import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import org.w3c.dom.Text;

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

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    //上拉狀態0:上拉載入更多
    public static final int PULLUP_LOAD_MORE = 0;
    //上拉狀態1:正在載入中
    public static final int LOADING_MORE = 1;

    private static final int TYPE_FOOTER = 0;//帶Footer的Item
    private static final int TYPE_NORMAL = 1;//不帶Footer的Item

    //資料來源
    private List<String> mList;
    //上拉載入狀態,預設為狀態0-上拉載入更多
    private int load_more_status = 0;

    private Handler mHandler = new Handler(Looper.getMainLooper());

    public MyAdapter(List<String> list) {
        mList = list;
    }
    //定義點選事件的介面
    public interface OnItemClickListener {
        
            
           

相關推薦

AndroidRecyclerView 的使用

謹以文章記錄學習歷程,如有錯誤還請指明。 RecyclerView 簡介 首先,可以理解 RecyclerView 是 ListView 的升級版,更加靈活,同時由於封裝了 ListView 的部分實現,導致其使用更簡單,結構更清晰。 從名字

tensorflow-梯度下降(深度好文)

前言最近機器學習越來越火了,前段時間斯丹福大學副教授吳恩達都親自錄製了關於Deep Learning Specialization的教程,在國內掀起了巨大的學習熱潮。本著不被時代拋棄的念頭,自己也開始研究有關機器學習的知識。都說機器學習的學習難度非常大,但不親自嘗試一下又怎麼

產品設計教程如何理解 px,dp,dpi, pt

先聊聊熟悉的幾個單位 圍繞著各種螢幕做設計和開發的人會碰到下面幾個單位:in, pt, px, dpi,dip/dp, sp 下面先簡單回顧下前四個單位: "in" inches的縮寫,英寸。就是螢幕的物理長度單位。一英寸等於2.54cm。比如Android手機

Cookie介紹及在Android中的使用總結超詳細

Cookie介紹 cookie的起源 早期Web開發面臨的最大問題之一是如何管理狀態。簡言之,伺服器端沒有辦法知道兩個請求是否來自於同一個瀏覽器。那時的辦法是在請求的頁面中插入一個token,並且在下一次請求中將這個token返回(至伺服器)。這就需要在form中插入一個包含toke

Android 必須知道2018年流行的框架庫及開發語言

導語2017 已經悄悄的走了,2018 也已經匆匆的來了,我們在總結過去的同時,也要展望一下未來,來規劃一下今年要學哪些新技術。這幾年優秀Android的開源庫不斷推出,新技術層出不窮,需要我們不斷去了解和掌握,在提高自身開發水平的同時,我們需要付出更多學習精力和時間。俗話說

Android 必須知道2018年流行的框架庫及開發語言

本文更新時間:2018年07月12日15:50:40導語    2017 已經悄悄的走了,2018 也已經匆匆的來了,我們在總結過去的同時,也要展望一下未來,來規劃一下今年要學哪些新技術。這幾年優秀Android的開源庫不斷推出,新技術層出不窮,需要我們不斷去了解和掌握,在提

【Java面試題系列】Java基礎知識面試題(持續更新)

文中面試題從茫茫網海中精心篩選,如有錯誤,歡迎指正! 1.前言 ​ 參加過社招的同學都瞭解,進入一家公司面試開發崗位時,填寫完個人資訊後,一般都會讓先做一份筆試題,然後公司會根據筆試題的回答結果,確定要不要繼續此次面試,如果答的不好,有些公司可能會直接說“技術經理或者總監在忙,你先回去等通知吧”,有些公司

【Java面試題系列】Java基礎知識面試題

路徑 拼接 i++ misc min 中新 dem 總結 內容 文中面試題從茫茫網海中精心篩選,如有錯誤,歡迎指正! 1.前言 參加過社招的同學都了解,進入一家公司面試開發崗位時,填寫完個人信息後,一般都會讓先做一份筆試題,然後公司會根據筆試題的回答結果,確定要不要繼續此

效能測試深入理解執行緒數併發量TPS

併發數,執行緒數,吞吐量,每秒事務數(TPS)都是效能測試領域非常關鍵的資料和指標。 那麼他們之間究竟是怎樣的一個對應關係和內在聯絡? 測試時,我們經常容易將執行緒數等同於表述為併發數,這一表述正確嗎? 本文就將對效能領域的這些關鍵概念做一次探討。 文章可能會比較長,希望您保持耐心看完。   1.

Azure IOT 設備固件更新技巧

trigger 物聯網平臺 搭建 href ice 有效 面板 調用 創建 嫌長不看版 今天為大家準備的硬菜是:在 Azure IoT 中心創建 Node.js 控制臺應用,進行端到端模擬固件更新,為基於 Intel Edison 的設備安裝新版固件的流程。通過創建模擬設備

想做好PPT折線圖

12月 image 菊花 -c 強調 spa any border 線圖 配圖主題無關今天鄭少跟大家聊聊折線圖的使用方法,或者你有疑問,折線圖很簡單,插入修改數據不就好了嗎?如果你要是這樣想的,恭喜你,有可能你會做出下面這樣的效果。如果你要是稍微懂一點折線圖的使用方法,你就

Linux 問題故障定位

1. 背景 有時候會遇到一些疑難雜症,並且監控外掛並不能一眼立馬發現問題的根源。這時候就需要登入伺服器進一步深入分析問題的根源。那麼分析問題需要有一定的技術經驗積累,並且有些問題涉及到的領域非常廣,才能定位到問題。所以,分析問題和踩坑是非常鍛鍊一個人的成長和提升自我能力。如果我們有一套好的分析工具,那將是事

C語言從入門到精通

影響 內容 當前 位置 replace 雙精度 下標 寄存器變量 一個 No.1 計算機與程序設計語言的關系 計算機系統由硬件系統和軟件系統構成,硬件相當於人類的肉體,而軟件相當於人類的靈魂,如果脫離了靈魂,人類就是一具行屍走肉 No.2 C語言的特點 代碼簡潔,靈活性高

【MYSQL學習筆記02】MySQL的高階應用之Explain(完美詳細版

版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/wx1528159409 最近學習MySQL的高階應用Explain,寫一篇學習心得與總結,目錄腦圖如下: 一、Explain基本概念 1. Explain定義 · 我們知道M

抖音內容運營全解剖 !

抖音的火爆已經不用多說,作為短視訊的頭部APP,抖音已經從微信手中奪走不少使用者時間,成為新的“時間黑洞”。 比如:“中毒了,我每天晚上要刷2個小時”,“下一站,逃離微信,上抖音”… 一個企業運營抖音的目的是什麼? 答案顯而易見,無非就是做品牌營銷、擴大品牌影響力。 在短視訊領域積累

百萬併發下的Nginx優化

本文作者主要分享在 Nginx 效能方面的實踐經驗,希望能給大家帶來一些系統化思考,幫助大家更有效地去做 Nginx。 優化方法論 我重點分享如下兩個問題: 保持併發連線數,怎麼樣做到記憶體有效使用。 在高併發的同時保持高吞吐量的重要要點。 實現層面主要是三方面優化,主要聚焦

理解Sharding jdbc原理

相比於Spring基於AbstractRoutingDataSource實現的分庫分表功能,Sharding jdbc在單庫單表擴充套件到多庫多表時,相容性方面表現的更好一點。例如,spring實現的分庫分表sql寫法如下: select id, name, price,

中後臺產品的表格設計(原型規範下載)

中後臺產品的表格設計,看這一篇就夠了(原型規範下載) 2018年4月16日luodonggan 中後臺產品的表格設計,看這一篇就夠了(原型規範下載) 經過了將近一年的後臺產品經歷,踩了很多坑,試了很多錯,也學習到了很多東西,目前也形成了自己的一套規範。本文將其中的部分收穫彙總成文,

Linux 常用指令 —— 摘自《Linux Probe》

touch:用於建立空白檔案或設定檔案的時間,ps:黑客可以用touch指令來修改檔案的最後修改時間,以隱藏自己的修改行為。 mkdir:用於建立空白的目錄,如mkdir path,可以結合引數-p來遞迴建立檔案目錄,如mkdir -p a/b/c/d/e cp:用於複製檔案或目錄,如cp 1.txt p

樹狀陣列(Binary Indexed Tree)

定義 根據維基百科的定義: A Fenwick tree or binary indexed tree is a data structure that can efficiently update elements and calculate pr