Android RecyclerView初體驗

来源:http://www.cnblogs.com/jzyhywxz/archive/2017/06/08/6962226.html
-Advertisement-
Play Games

很早之前就聽說過RecyclerView這個組件了,但一直很忙沒時間學習。趁著周末,就花了一天時間來學習RecyclerView。 準備工作 在Android Studio里新建一個Android項目,添加以下工具: 前兩個工具就不說了,基本每個Android項目都會用到,第三個就是使用Recycl ...


很早之前就聽說過RecyclerView這個組件了,但一直很忙沒時間學習。趁著周末,就花了一天時間來學習RecyclerView。

準備工作

在Android Studio里新建一個Android項目,添加以下工具:

    compile 'com.android.support:support-v4:25.2.0'
    compile 'com.android.support:appcompat-v7:25.2.0'
    compile 'com.android.support:recyclerview-v7:25.2.0'
    compile 'com.github.bumptech.glide:glide:3.7.0'

前兩個工具就不說了,基本每個Android項目都會用到,第三個就是使用RecyclerView需要添加的工具了,而最後一個工具是Google推薦使用的一個開源圖像載入工具,它使得載入圖像變得更為流暢,並且使用起來容易上手。

在drawable目錄下放入一些圖像,並將這些圖像名命名為"image[a-zA-Z0-9]*"的形式(因為在程式里會用反射來查找所有以"image"開頭的圖像資源)。

在drawable目錄下放入"add.png"和"remove.png"等用於頂部工具欄的圖像資源。

基本用法

使用RecyclerView,可以依照如下步驟:
1. 在資源文件中定義一個RecyclerView組件;
2. 在Activity中獲取定義的RecyclerView組件,變數名就簡單叫作view;
3. 為view設置LayoutManager(佈局);
4. 為view設置Adapter(適配器);
5. 為view添加ItemDecoration(分隔符,可選);
6. 為view設置ItemAnimator(動畫,可選)。

下麵在資源文件中定義了一個RecyclerView組件:

1     <android.support.v7.widget.RecyclerView
2         android:layout_width="match_parent"
3         android:layout_height="0dp"
4         android:layout_weight="1"
5         android:id="@+id/vert_view" />

然後就可以在Activity中取得這個RecyclerView組件:

1 RecyclerView view=(RecyclerView) findViewById(R.id.vert_view);

至於其他的細節將在下文介紹。

Adapter

用於RecyclerView的適配器需要繼承自RecyclerView.Adapter<T>類,並重寫其中的 T onCreateViewHolder(final ViewGroup parent, int viewType) 、 void onBindViewHolder(T holder, final int position) 和 int getItemCount() 方法。下麵是一個自定義的Adapter:

 1 public class ViewAdapter extends RecyclerView.Adapter<ViewAdapter.ViewHolder> {
 2     private Context context;
 3     private List<Animal> datas;
 4     private int layoutResId;
 5     private int imageViewId;
 6     private int textViewId;
 7     private boolean isCenterCrop;
 8 
 9     public ViewAdapter(Context c, List<Animal> list, int lri, int ivi, int tvi, boolean centerCrop) {
10         context=c;
11         datas=list;
12         layoutResId=lri;
13         imageViewId=ivi;
14         textViewId=tvi;
15         isCenterCrop=centerCrop;
16     }
17 
18     @Override
19     public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
20         View view= LayoutInflater.from(context).inflate(layoutResId, parent, false);
21         return new ViewHolder(view, imageViewId, textViewId);
22     }
23 
24     @Override
25     public void onBindViewHolder(ViewHolder holder, final int position) {
26         final Animal animal=datas.get(position);
27         if(isCenterCrop)
28             Glide.with(context).load(animal.getImgResId()).centerCrop().into(holder.imageView);
29         else
30             Glide.with(context).load(animal.getImgResId()).into(holder.imageView);
31         holder.textView.setText(animal.getDescription());
32         holder.view.setOnClickListener(new View.OnClickListener() {
33             @Override
34             public void onClick(View v) {
35                 Intent intent=new Intent(context, DetailActivity.class);
36                 intent.putExtra("IMG_RES_ID", animal.getImgResId());
37                 intent.putExtra("DESCRIPTION", animal.getDescription());
38                 context.startActivity(intent);
39             }
40         });
41         holder.view.setOnLongClickListener(new View.OnLongClickListener() {
42             @Override
43             public boolean onLongClick(View v) {
44                 Toast.makeText(context, "第"+position+"個元素", Toast.LENGTH_SHORT).show();
45                 return true;
46             }
47         });
48     }
49 
50     @Override
51     public int getItemCount() {
52         return datas.size();
53     }
54 
55     public void addItem(int position) {
56         datas.add(position, ResourceUtils.getRandomAnimal());
57         notifyItemInserted(position);
58     }
59 
60     public void removeItem(int position) {
61         if(position>=0 && position<datas.size()) {
62             datas.remove(position);
63             notifyItemRemoved(position);
64         }
65     }
66 
67     static class ViewHolder extends RecyclerView.ViewHolder {
68         View view;
69         ImageView imageView;
70         TextView textView;
71 
72         public ViewHolder(View itemView, int ivi, int tvi) {
73             super(itemView);
74             view=itemView;
75             imageView=(ImageView) itemView.findViewById(ivi);
76             textView=(TextView) itemView.findViewById(tvi);
77         }
78     }
79 }

註意:

  • 其中的Animal類封裝了一張(動物)圖像的資源ID和一段描述(字元串),Animal對象將作為我們的RecyclerView中的元素;
  • onCreateViewHolder方法負責創建一個ViewHolder,因此我們需要提供一個元素使用的資源文件ID,我們需要使用到的view(這裡包括一個ImageView和一個TextView)都可以通過這個ViewHolder得到;
  • onBindViewHolder方法負責對某些view進行設置,這裡我們為ImageView設置了圖像,為TextView設置了文本,併為它們的父view註冊了點擊和長按監聽器;
  • 這裡的addItem和removeItem方法是自定義方法,用於向/從RecyclerView中添加/移除一個元素(將在後面介紹),這裡可以先忽略;
  • 這裡的ViewHolder是ViewAdapter.ViewHolder,它繼承自RecyclerView.ViewHolder,我們將需要使用到的view交給它管理。

定義好Adapter後,就可以為RecyclerView設置適配器了:

1     List<Animal> datas= ResourceUtils.getAnimals();
2     adapter=new ViewAdapter(this, datas, R.layout.item_vertical, R.id.vert_image, R.id.vert_text, true);
3     view.setAdapter(adapter);

 

垂直佈局

要想讓RecyclerView使用和ListView一樣的佈局,只需要為RecyclerView設置一個線性佈局管理器就行了:

1 view.setLayoutManager(new LinearLayoutManager(this));

 

水平佈局

使用水平佈局只需設置LinearLayoutManager的方向即可:

1     LinearLayoutManager layoutManager=new LinearLayoutManager(this);
2     layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
3     view.setLayoutManager(layoutManager);

 

網格佈局

使用網格佈局需要藉助GridLayoutManager類:

1     GridLayoutManager manager=new GridLayoutManager(this, 3);
2     // manager.setOrientation(GridLayoutManager.HORIZONTAL);
3     view.setLayoutManager(manager);

創建GridLayoutManager時,第二個參數使用列數/行數。GridLayoutManager預設是上下滑動的,如果想要左右滑動,可以取消第二行的註釋。

瀑布流佈局

使用瀑布流佈局只需使用StaggeredGridLayoutManager即可:

1 view.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));

StaggeredGridLayoutManager的第一個參數指定列數/行數,第二個參數指定滑動方向,這裡設置成上下滑動。如果使用StaggeredGridLayoutManager時所有元素的大小一致,則效果和網格佈局一樣。

添加分割線

RecyclerView預設是不顯示分割線的,這點和ListView不同,但這也為更靈活的定製自己的分割線提供了機會。

要想定製自己的分割線,需要定義類繼承自RecyclerView.ItemDecoration,並實現如下方法:

  •  void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) :此方法負責繪製分割線;
  •  void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) :此方法和onDraw方法一樣,也是繪製分割線,不過繪製的時機是在繪製元素View完成後,通常只需要重寫onDraw和此方法中的一個即可;
  •  void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) :此方法計算分割線的尺寸,以方便計算元素View的偏移。

這裡以繪製垂直佈局的分割線為例,介紹如何定義自己的分割線,至於其他幾種佈局的分割線可以參考著來寫,文章最後也會提供所有源碼:

 1 public class VerticalItemDecoration extends RecyclerView.ItemDecoration {
 2     private Drawable divider;
 3     private static final int[] ATTRS=new int[] {
 4             android.R.attr.listDivider,
 5     };
 6 
 7     public VerticalItemDecoration(Context c) {
 8         TypedArray array=c.obtainStyledAttributes(ATTRS);
 9         divider=array.getDrawable(0);
10         array.recycle();
11     }
12 
13     @Override
14     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
15         super.onDraw(c, parent, state);
16         DecorationUtils.onVerticalDraw(c, parent, divider);
17     }
18 
19     @Override
20     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
21         super.getItemOffsets(outRect, view, parent, state);
22         int position=parent.getChildLayoutPosition(view);
23         int childCount=parent.getAdapter().getItemCount();
24         if((position+1)<childCount)
25             outRect.set(0, 0, 0, divider.getIntrinsicHeight());
26     }
27 }
 1 public class DecorationUtils {
 2     public static void onVerticalDraw(Canvas c, RecyclerView parent, Drawable divider) {
 3         int left=parent.getPaddingLeft();
 4         int right=parent.getWidth()-parent.getPaddingRight();
 5 
 6         for(int i=0; i<parent.getChildCount()-1; i++) {
 7             View child=parent.getChildAt(i);
 8             RecyclerView.LayoutParams params=(RecyclerView.LayoutParams) child.getLayoutParams();
 9             int top=child.getBottom()+params.bottomMargin;
10             int bottom=top+divider.getIntrinsicHeight();
11             divider.setBounds(left, top, right, bottom);
12             divider.draw(c);
13         }
14     }
15     // ...
16 }

這裡的分隔符樣式使用了Android提供的“listDivider”,你可以重定義listDivider屬性來修改分割線樣式:

1     <!-- Base application theme. -->
2     <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
3         <!-- Customize your theme here. -->
4         <item name="colorPrimary">@color/colorPrimary</item>
5         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
6         <item name="colorAccent">@color/colorAccent</item>
7         <item name="android:listDivider">@drawable/divider</item>
8     </style>

添加動畫

你可能會好奇,RecyclerView為什麼需要添加動畫,不是就一個滑動嗎?難道是添加滑動的動畫?

考慮這種情況,當需要向/從RecyclerView中添加/移除元素時,如果一個元素突然插進來或者突然消失,用戶會是什麼感受(肯定覺得這APP是不是犯二了)。如果在添加/移除元素時,能有一個動畫顯示元素的出現/消失,那用戶體驗就更好了。所以,我們這裡是為元素添加動畫。

RecyclerView提供了一個預設動畫,只需要簡單的一行代碼:

1 view.setItemAnimator(new DefaultItemAnimator());

好了,現在我們已經為元素的出現/消失添加了動畫了。那麼問題又來了,我們怎麼添加/移除元素呢?

還記得我們上面介紹適配器時提供的ViewAdapter嗎?當時讓你暫時忽略了兩個方法——addItem和removeItem,這兩個方法就是用於添加/移除元素的。

我們現在來看下細節:

 1 public void addItem(int position) {
 2     datas.add(position, ResourceUtils.getRandomAnimal());
 3     notifyItemInserted(position);
 4 }
 5 
 6 public void removeItem(int position) {
 7     if(position>=0 && position<datas.size()) {
 8         datas.remove(position);
 9         notifyItemRemoved(position);
10     }
11 }

註意到調用了notifyItemInserted和notifyItemRemoved方法,它們就是用於通知RecyclerView元素髮生改變的。

現在已經有了添加/移除元素的介面,下麵還需要在Activity中添加交互的動作。我們提供了一個Toolbar(用於代替ActionBar的一個組件),並提供了兩個item。用戶可以點擊這兩個item來添加/移除元素:

 1     @Override
 2     protected void onCreate(Bundle savedInstanceState) {
 3         // ...
 4         Toolbar toolbar=(Toolbar) findViewById(R.id.toolbar);
 5         setSupportActionBar(toolbar);
 6         // ...
 7     }
 8 
 9     @Override
10     public boolean onCreateOptionsMenu(Menu menu) {
11         getMenuInflater().inflate(R.menu.toolbar, menu);
12         return true;
13     }
14 
15     @Override
16     public boolean onOptionsItemSelected(MenuItem item) {
17         switch (item.getItemId()) {
18             case R.id.add:
19                 adapter.addItem(0);
20                 view.smoothScrollToPosition(0);
21                 break;
22             case R.id.remove:
23                 adapter.removeItem(0);
24                 break;
25             default:break;
26         }
27         return true;
28     }
29 }

好了,現在你就可以在添加/移除元素時看到動畫效果了。

源代碼

整個項目的源代碼已經上傳到GitHub:
https://github.com/jzyhywxz/RecyclerView


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於客戶端應用程式,免不了和遠程服務打交道。設計一個良好的『服務層』能幫我們規範和分離業務代碼,提高生產效率。服務層最核心的模塊一定是怎樣發送請求,雖然Mono提供了很多C 網路請求類,諸如 ,`HttpWebRequest UnityWebRequest WWW`,這是官方推薦的,也是最佳選擇。 ...
  • 變數的作用域是可以訪問該變數的代碼區域。一般情況下,確定作用域遵循以下規則: 1.只要類在某個作用域內,其欄位(也稱為成員變數)也在該作用域內(這句話可以簡單的理解為,類中定義的欄位可以在類中的任意地方被訪問到)。 2.局部變數存在於表示聲明該變數的塊語句或方法結束的有花括弧之前的作用域內。(註意... ...
  • 本文源自《.NET通信框架的設計、實現與應用》書稿第一章內容 類庫是一些類的集合,只要我們將一些可以復用的類集中放到一個Library中,我們就可以稱其為一個類庫。 類庫中的許多元素(如類、結構、介面、枚舉、委托等)之間可能有一些關聯,但這些關聯通常用於支持一個類概念或介面概念的完整表達。 如果我們 ...
  • 代碼: RootViewController.h RootViewController.m ...
  • 轉載自http://www.runoob.com/w3cnote/android-tutorial-customer-baseadapter.html 作者:coder-pig 本節引言: 如題,本節給大家帶來的是構建一個可復用的自定義BaseAdapter,我們每每涉及到ListView Grid ...
  • 接手一個項目,有一個問題需要修改:輪播圖不能手動滑動,手動滑動輪播圖只會觸發側滑菜單。 猜測:viewpager控制項(輪播圖)的觸摸事件被SlidingMenu控制項(側滑菜單,非第三方項目,乃是上個開發人員自定義的)攔截了。 基於這個猜測,我自定義一個ViewPager,重寫dispatchTouc ...
  • 淺談Kotlin(一):簡介及Android Studio中配置 淺談Kotlin(二):基本類型、基本語法、代碼風格 淺談Kotlin(三):類 淺談Kotlin(四):控制流 本篇介紹Kotlin的控制流語法(if,for,while,when....) 一、if 語句 1、基本用法: 2、判斷 ...
  • 使用微信插件 cordova plugin add cordova-plugin-wechat --variable wechatappid=YOUR_WECHAT_APPID; $scope.sharechat=function(scene,title, desc, url, thumb){// ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...