Tuesday, August 5, 2014

ListView進階版 - RecyclerView使用實例(以Android Studio為例)

文章攢寫時間︰2014/08/05 13:43
文章修改時間︰2014/08/06 10:25
文章修改次數︰2

文章參考來源

1.RecyclerView example(國外網站)
2.RecyclerView in Android: The basics(國外網站)
3.RecyclerViewExtensions on Github
4.Android Simple RecyclerView Widget Example(國外網站)
5.ANDROID L: RECYCLERVIEW TUTORIAL(國外網站)
6.RecyclerView | Android Developer

本篇適合

對Android ListView元件了解的開發者

需要手機

Android 4.0 (Ice cream sandwich)以上

本篇概要

1.學會在Android Studio 0.8.x版添加Google擴充套件
2.學會使用RecyclerView
3.學會使用強大的載圖工具Picasso

一、前言


Google在I/O 2014發表了Android L,
同時也在該系統中推岀一個嶄新的顯示元件RecyclerView,
官方也宣稱該元件比原來的ListVIew更好用。


以下是該元件與原來的ListView不同的地方︰

  • 比ListView更進階也具彈性
  • ViewHolder變成強制性必須實作的類別(稍候看實例會理解)
  • 回收的速度比以往更有效率
  • 以前你只建立ListView和Adapter,現在你還多需要建立一個LayoutManager
  • LayoutManager能幫你避免過多次呼叫findViewById的所造成的資源浪費

二、本文開始

新元件LayoutManager和ItemAnimator介紹

在技術開始前,先說說LayoutManager這個新元件。

LayoutManager屬於RecyclerView的內部元件之一,其目的用來決定RecyclerView當一個view不再顯示給使用者,要怎麼重新使用這些view資源。

無論是要重覆使用(reuse)或資源回收(recycle)一個view,LayoutManager都會從數據集(Dataset)去讀取岀需要的資料,並且取代原view來顯示給使用者。然用這種方式去更換view能避免創建一些不需要的view、也能增進使用findViewById找資源的效能。

目前RecyclerView提供了LinearLayoutManager這個實作類別,該類別繼承了LayoutManager,這個元件能顯示直向版面(vertical)或橫向版面(Horizontal)的列表滑動清單。如果你需要一個客製化的排版方式(譬如想做得像以前的GridLayout方格式版面),你就得自己繼承RecyclerView.LayoutManager這個類別來教RecyclerView怎麼排版。

題外話,RecyclerView還提供動畫特效的功能,可以讓每一筆view顯示時,有一些被新增或移除的特效。如果想要改變這些特效,那麼就得去繼承RecyclerView.ItemAnimator這個類別,並在RecyclerView被實體化時,呼叫RecyclerView.setItemAnimator()這個函式。

技術環境準備

在開始coding以前,您需要準備好以下環境
  • Android Studio(0.8.x版以上)
  • 安裝SDK Tools、Platform-tools、Build-tools
  • 安裝Android Support Library和Android Support Repository
以上所需套件下載完畢後,請確認您電腦裡Android SDK目錄底下有以下資源

開始Coding

(1)建立一個新的App
  • Application name: TestRecyclerView
  • Company Doamin: com.android.recyclerview
  • 點選 Next
  • Minimum SDK: API 15
  • 點選 Next
  • 選擇 Blank Activity
  • 點選 Next
  • 點選 Finish
(2)修改編譯使用版號及添加所需Library
  • 點擊[File]->[Project Structure]
  • 點擊左側[Modules]->[app]
  • 右側頁面選擇[Properties],並修改以下值Compile Sdk Version: API20 / Build Tools Version: API 20.0.0
  • 右側頁面選擇[Flavors],並修改以下值Target SDK: API 20
  • 右側頁面選擇[Dependencies],並按下頁面下方[+]號,選擇1.Library dependency
  • 添加以下5個擴充套件

    com.android.support:appcompat-v7:+
    com.android.support:support-v4:+
    com.android.support:palette-v7:+
    com.android.support:recyclerview-v7:+
    com.squareup.picasso:picasso:2.3.+

    小註︰看到上面5個套件後面都顯示 .+ ,這個符號意謂著跟Gradle說「請幫我使用該套件的最新版本。
    但因為現在(2014/08/05)support-v4最新版本僅提供Android L Preview執行使用,因此我們需要做接下來的修改讓非Android L的手機也能使用這些套件。

  • 為避免專案編譯時遇到
  • Error:Execution failed for task ':app:processDebugManifest'.
  • > Manifest merger failed : uses-sdk:minSdkVersion 15 cannot be smaller than version L declared in library com.android.support:appcompat-v7:21.0.0-rc1


  • 我們需要修改二個地方︰

    1>>修改Gradle腳本app\build.gradle
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:+'
        compile 'com.android.support:support-v4:+'
        compile 'com.android.support:palette-v7:+'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
    }

     請改為

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:20.0.0'
        compile 'com.android.support:support-v4:20.0.0'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
        compile 'com.android.support:palette-v7:+'
    }

     這是build.gradle最後的樣子
    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 20
        buildToolsVersion '20.0.0'
    
        defaultConfig {
            applicationId "recyclerview.android.com.testrecyclerview"
            minSdkVersion 15
            targetSdkVersion 20
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                runProguard false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:20.0.0'
        compile 'com.android.support:support-v4:20.0.0'
        compile 'com.squareup.picasso:picasso:2.3.+'
        compile 'com.android.support:recyclerview-v7:+'
        compile 'com.android.support:palette-v7:+'
    }
    

    2>>添加以下3行code至app\src\AndroidManifest.xml
xmlns:tools="http://schemas.android.com/tools"
<uses-sdk tools:node="replace"></uses-sdk>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>

這是AndroidManifest.xml裡最後的樣子

按下Run ,試看看專案是否能正常在手機執行,
這麼做可以讓我們確定我們在coding前的初步環境和所需套件是否皆已建置完畢。
如果套件皆引用成功,應該能看到這個完全空白的畫面

(3)添加或修改3個Java class

  1. 添加ItemData這個Object類
  2. package recyclerview.android.hmkcode.com.recyclerview;
    
    /**
     * Created by lp43 on 2014/8/4.
     */
    public class ItemData {
        private String title;
        private String imageUrl;
    
        public ItemData(String title,String imageUrl){
    
            this.title = title;
            this.imageUrl = imageUrl;
    
        }
    
        public String getTitle() {
            return title;
        }
    
        public String getImageUrl() {
            return imageUrl;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public void setImageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
        }
    }
    

  3. 實作RecyclerView的Adapter-->MyAdapter.java
  4. package recyclerview.android.hmkcode.com.recyclerview;
    
    import android.support.v7.widget.RecyclerView;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    import com.squareup.picasso.Picasso;
    
    /**
     * Created by lp43 on 2014/8/4.
     */
    public class MyAdapter extends RecyclerView.Adapter{
        private ItemData[] itemsData;
    
        public MyAdapter(ItemData[] itemsData) {
            this.itemsData = itemsData;
        }
    
    
    
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            // create a new view
            View itemLayoutView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_layout, null);
    
            // create ViewHolder
    
            ViewHolder viewHolder = new ViewHolder(itemLayoutView);
            return viewHolder;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder viewHolder, int position) {
            // - get data from your itemsData at this position
            // - replace the contents of the view with that itemsData
    
            viewHolder.txtViewTitle.setText(itemsData[position].getTitle());
    //        viewHolder.imgViewIcon.setImageResource(itemsData[position].getImageUrl());
    
            Picasso.with(viewHolder.imgViewIcon.getContext()).cancelRequest(viewHolder.imgViewIcon);
            Picasso.with(viewHolder.imgViewIcon.getContext()).load(itemsData[position].getImageUrl()).into(viewHolder.imgViewIcon);
        }
    
        // Return the size of your itemsData (invoked by the layout manager)
        @Override
        public int getItemCount() {
            return itemsData.length;
        }
    
        // inner class to hold a reference to each item of RecyclerView
        public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    
            public TextView txtViewTitle;
            public ImageView imgViewIcon;
    
            public ViewHolder(View itemLayoutView) {
                super(itemLayoutView);
                itemLayoutView.setOnClickListener(this);
                txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
                imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
            }
    
            @Override
            public void onClick(View view) {
                Toast.makeText(view.getContext(), "position = " + getPosition(), Toast.LENGTH_SHORT).show();
            }
        }
    }
    

  5. 在主頁面MyActivity.java實例化RecyclerView
  6. package recyclerview.android.hmkcode.com.recyclerview;
    
    import android.support.v7.app.ActionBarActivity;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.support.v7.widget.DefaultItemAnimator;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    
    
    public class MyActivity extends ActionBarActivity {
        private String[] sources = {
                "http://lorempixel.com/600/250/",
                "http://lorempixel.com/600/250/sports",
                "http://lorempixel.com/600/200/sports/Dummy-Text",
                "http://lorempixel.com/600/200/nature",
                "http://lorempixel.com/600/200/food",
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_my);
    
            // 1. get a reference to recyclerView
            RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    
            // this is data fro recycler view
            ItemData[] itemsData = {
                    new ItemData("Delete",sources[0]),
                    new ItemData("Cloud",sources[1]),
                    new ItemData("Favorite",sources[2]),
                    new ItemData("Like",sources[3]),
                    new ItemData("Rating",sources[4]),
                    new ItemData("Delete",sources[0]),
                    new ItemData("Cloud",sources[1]),
                    new ItemData("Favorite",sources[2]),
                    new ItemData("Like",sources[3]),
                    new ItemData("Rating",sources[4])
            };
    
            // 2. set layoutManger
            recyclerView.setLayoutManager(new LinearLayoutManager(this));
            // 3. create an adapter
            MyAdapter mAdapter = new MyAdapter(itemsData);
            // 4. set adapter
            recyclerView.setAdapter(mAdapter);
            // 5. set item animator to DefaultAnimator
            recyclerView.setItemAnimator(new DefaultItemAnimator());
    
        }
    
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.my, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
            if (id == R.id.action_settings) {
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
    
    
(4)drawable資料夾添加一個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="@drawable/border2_pressed" />
    <item android:drawable="@drawable/border2" />
</selector>

border2_pressed.png

border2.png

(5)添加或修改layout資料夾的二個xml

  1. activity_my.xml
  2. <RelativeLayout 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"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context=".MyActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
    </RelativeLayout>
    
    
  3. item_layout.xml
  4. <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="40dp"
        android:background="@drawable/border2_combine">
    
        <!-- icon -->
        <ImageView
            android:id="@+id/item_icon"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="1dp"
            android:layout_marginBottom="1dp"
            android:contentDescription="icon"
            android:src="@drawable/ic_launcher" />
    
        <!-- title -->
        <TextView
            android:id="@+id/item_title"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/item_icon"
            android:layout_alignBaseline="@+id/item_icon"
            android:textColor="@android:color/darker_gray"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginTop="8dp"
            android:textSize="22dp" />
    
    </RelativeLayout>
    

(6)修改styles.xml裡的主題風格
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

</resources>

(7)編譯並執行
這是程式執行後的樣子

三、結論

撰寫這篇文章的時間是2014年8月5日,
本來這個專案裡很多的套件都要在Android L Preview裡去執行,
但因為添加了
<uses-sdk tools:node="replace"></uses-sdk>
這個屬性到AndroidManifest.xml裡,
所以我們能在Android 4.x就能搶先體驗RecyclerView這個元件帶來的強大功能。

試著滑看看,
RecyclerView是不是像官方說的一樣流暢?

我實測跑了3隻Android 4.x的手機顯示都是正常的哦!

如需本篇源碼請至GitHub下載

相關文章

1. ViewPagerのイベントをハンドルする(ページ移動)

2 comments: