[Android] RecyclerView | RecyclerView 簡介&應用
今天的topic是要來簡介一下 RecyclerView這個widget
Google Developer上的Guide裡的overview是這樣說的
沒錯!! RecyclerView 就是個進階且靈活的ListView
還不知道或不清楚ListView的同學可以Google 或參考我的一篇介紹文
[Android]ListView & ArrayAdapter介紹與使用
那怎麼個進階和靈活呢??
RecyclerView是由一些component一起作業來顯示你的資料,這些component包括LayoutManager、RecyclerView.ViewHolder和RecyclerView.Adapter
LayoutManager可以使用標準的LayoutManager(像是LinearLayoutManager、GridLayoutManager等),或是使用自定義的。
list裡的每個View都是ViewHolder物件,這些物件是你自己定義並繼承RecyclerView.ViewHolder介面的class
而RecyclerView.Adapter可以說是資料與ViewHolder之間的溝通橋樑,透過呼叫adapter的onBindViewHolder()方法,並將ViewHolder & position作為參數傳入,你必須自己繼承RecyclerView.Adapter物件並實作這個方法。
如果覺得上面字太多,他們的關係如下圖:
靈活的原因大概((?就是以RecyclerView作為容器,內部的原件都可以自由更換而不會影響到RecyclerView本身,真的是好棒棒呢!!
好的,對RecyclerView有初步的了解後,我們就來做一個小範例吧~
接下來會以okhttp 來 get政府資料開放平台裡的紫外線即時監測資料,並藉由RecyclerView將資料顯示出來。
首先要使用RecyclerView和一些其他的libraries需先在build.gradle中加入dependencies:
先來看看get到的資料長怎樣吧~
get到的資料包含一個array
array裡每個物件都有 County、PublishAgency、PublishTime、SiteName、UVI、WGS84Lat和WGS84Lon這些properties
根據這些properties我們新增一個class來存取這些資訊
接著,知道有哪些資料後就來設計一下ViewHolder的layout
提供一下我的布局的xml:
大概看起來會是這樣子:
好的,前置作業做得差不多了,接下來進入重點!
接著新增ItemAdapter類別並繼承RecyclerView.Adapter<>
後面泛型的類別必須傳入繼承RecyclerView.ViewHolder的類別
所以在ItemAdapter內再新增一個ViewHolder靜態類別
好的,現在我們有Adapter & ViewHolder,為了偷懶((? LayoutManager 用 LinearLayoutManager 就好
接著就是新增自訂義Adapter & LinearLayoutManager 的實例,然後指定給 RecyclerView 就OK了
最後,因為有用到網路所以記得在AndroidManifest.xml裡面加上:
以上為個人淺見,還請各位大大指教~
參考資料:
https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5
https://square.github.io/okhttp/3.x/okhttp/
http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html
https://github.com/square/okio
https://developer.android.com/guide/topics/ui/layout/recyclerview
https://opendata.epa.gov.tw/ws/Data/UV/?$format=json
Google Developer上的Guide裡的overview是這樣說的
The RecyclerView widget is a more advanced and flexible version of ListView.恩...還真是直白呢XDD
沒錯!! RecyclerView 就是個進階且靈活的ListView
還不知道或不清楚ListView的同學可以Google 或參考我的一篇介紹文
[Android]ListView & ArrayAdapter介紹與使用
那怎麼個進階和靈活呢??
RecyclerView是由一些component一起作業來顯示你的資料,這些component包括LayoutManager、RecyclerView.ViewHolder和RecyclerView.Adapter
LayoutManager可以使用標準的LayoutManager(像是LinearLayoutManager、GridLayoutManager等),或是使用自定義的。
list裡的每個View都是ViewHolder物件,這些物件是你自己定義並繼承RecyclerView.ViewHolder介面的class
而RecyclerView.Adapter可以說是資料與ViewHolder之間的溝通橋樑,透過呼叫adapter的onBindViewHolder()方法,並將ViewHolder & position作為參數傳入,你必須自己繼承RecyclerView.Adapter物件並實作這個方法。
如果覺得上面字太多,他們的關係如下圖:
靈活的原因大概((?就是以RecyclerView作為容器,內部的原件都可以自由更換而不會影響到RecyclerView本身,真的是好棒棒呢!!
好的,對RecyclerView有初步的了解後,我們就來做一個小範例吧~
接下來會以okhttp 來 get政府資料開放平台裡的紫外線即時監測資料,並藉由RecyclerView將資料顯示出來。
首先要使用RecyclerView和一些其他的libraries需先在build.gradle中加入dependencies:
dependencies { implementation 'com.android.support:recyclerview-v7:28.0.0' //RecyclerView implementation 'com.squareup.okhttp3:okhttp:3.14.2' //okhttp implementation 'com.google.code.gson:gson:2.8.5' //Gson implementation 'com.squareup.okio:okio:2.2.2' //okio }
先來看看get到的資料長怎樣吧~
[ { "County":"屏東縣", "PublishAgency":"環境保護署", "PublishTime":"2019-06-22 22:00", "SiteName":"屏東", "UVI":"0", "WGS84Lat":"22,40,23.09", "WGS84Lon":"120,29,16.92" }, { "County":"高雄市", "PublishAgency":"環境保護署", "PublishTime":"2019-06-22 22:00", "SiteName":"橋頭", "UVI":"0", "WGS84Lat":"22,45,27.02", "WGS84Lon":"120,18,20.48" },//... ]
get到的資料包含一個array
array裡每個物件都有 County、PublishAgency、PublishTime、SiteName、UVI、WGS84Lat和WGS84Lon這些properties
根據這些properties我們新增一個class來存取這些資訊
public class UviInfo { private String county, publishAgency, publishTime, siteName, UVI, wgs84lat, wgs84lon; public UviInfo(@NotNull JsonObject obj) { county = obj.get("County").getAsString(); publishAgency = obj.get("PublishAgency").getAsString(); publishTime = obj.get("PublishTime").getAsString(); siteName = obj.get("SiteName").getAsString(); UVI = obj.get("UVI").getAsString(); wgs84lat = obj.get("WGS84Lat").getAsString(); wgs84lon = obj.get("WGS84Lon").getAsString(); } public String getCounty() { return county; } public String getPublishAgency() { return publishAgency; } public String getSiteName() { return siteName; } public String getWgs84lat() { return wgs84lat; } public String getWgs84lon() { return wgs84lon; } public String getPublishTime() { return publishTime; } public String getUVI() { return UVI; }
接著,知道有哪些資料後就來設計一下ViewHolder的layout
提供一下我的布局的xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginTop="2dp" android:layout_marginBottom="2dp" android:orientation="horizontal" android:weightSum="12"> <TextView android:id="@+id/cityName_tv" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="5" android:gravity="center" android:singleLine="false" android:text="TextView" android:textAlignment="center" android:textSize="14sp" android:textStyle="bold" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="2" android:orientation="vertical" android:weightSum="10"> <TextView android:id="@+id/location_tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="5" android:singleLine="true" android:text="TextView" android:textSize="12sp" /> <TextView android:id="@+id/agency_tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="5" android:text="TextView" android:textSize="10sp" /> </LinearLayout> <TextView android:id="@+id/uvi_tv" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="5" android:gravity="center" android:singleLine="false" android:text="TextView" android:textAlignment="center" /> </LinearLayout>
大概看起來會是這樣子:
好的,前置作業做得差不多了,接下來進入重點!
接著新增ItemAdapter類別並繼承RecyclerView.Adapter<>
後面泛型的類別必須傳入繼承RecyclerView.ViewHolder的類別
所以在ItemAdapter內再新增一個ViewHolder靜態類別
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> { private ArrayList<UviInfo> data; //建構子,順便傳入要顯示的資料 public ItemAdapter(ArrayList<UviInfo> data){ this.data = data; } /* * 繼承RecyclerView.Adapter 必須實作2個functions & 1 method * 分別為: onCreateViewHolder、onBindViewHolder、getItemCount * */ //onCreateViewHolder 在 RecyclerView 需要新的 ViewHolder時被呼叫 //比較需要注意LayoutInflater.infalte() 的第三個參數(boolean attachToRoot)必須設為false //不然會拋出java.lang.IllegalStateException @NonNull @Override public ItemAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_layout, viewGroup, false); return new ViewHolder(v); } //onBindViewHolder 在RecyclerView 在特定的位置要顯示資料時被呼叫 //第一個參數為ViewHolder,第二個參數是位置 //通常會在這設定layout裡對應的元件要顯示的內容 @Override public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) { UviInfo info = data.get(i); viewHolder.cityName.setText(info.getCounty()); viewHolder.location.setText("站名:"+info.getSiteName()+" WGS84Lat:"+info.getWgs84lat()+" WGS84Lon:"+ info.getWgs84lon()); viewHolder.agency.setText("發布單位:"+info.getPublishAgency()+" 發布時間:"+info.getPublishTime()); viewHolder.uvi.setText(info.getUVI()); } //getItemCount 回傳list裡面item的總數 @Override public int getItemCount() { return data.size(); } public static class ViewHolder extends RecyclerView.ViewHolder{ private TextView cityName, location, agency, uvi; public ViewHolder(@NonNull View itemView) { super(itemView); cityName = itemView.findViewById(R.id.cityName_tv); location = itemView.findViewById(R.id.location_tv); agency = itemView.findViewById(R.id.agency_tv); uvi = itemView.findViewById(R.id.uvi_tv); } } }
好的,現在我們有Adapter & ViewHolder,為了偷懶((? LayoutManager 用 LinearLayoutManager 就好
接著就是新增自訂義Adapter & LinearLayoutManager 的實例,然後指定給 RecyclerView 就OK了
public class MainActivity extends AppCompatActivity { //變數宣告 private final String cert = "-----BEGIN CERTIFICATE-----\n" + "MIIFQzCCBCugAwIBAgIRALxxs3aEwvIH6GL2I9QlZAUwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE\n" + "BhMCVFcxEjAQBgNVBAoMCeihjOaUv+mZojEhMB8GA1UECwwY5pS/5bqc5oaR6K2J566h55CG5Lit\n" + "5b+DMB4XDTE3MDcyNDA4MTMyNVoXDTIwMDcyNDA4MTMyNVowdDELMAkGA1UEBhMCVFcxEjAQBgNV\n" + "BAoMCeihjOaUv+mZojEYMBYGA1UECwwP55Kw5aKD5L+d6K23572yMRwwGgYDVQQDExNvcGVuZGF0\n" + "YS5lcGEuZ292LnR3MRkwFwYDVQQFExAwMDAwMDAwMDEwMDM0MTg4MIIBIjANBgkqhkiG9w0BAQEF\n" + "AAOCAQ8AMIIBCgKCAQEAoKWi4Sgrv8eMYyAfaP3BF63WvUDkwMsMBBZSIu3Ge2tPsRx1yWO2JbxO\n" + "F2YfyJq45MBD2nTOXFq7pgMMHsU5PFqzjoXDiqx7POshI/Zwu6vQMDJJExuw55LV4nFjXHYYtupw\n" + "r7FsWHNq7sNOGZQzGDw85ooK1oXypC8TRQJ5NlYkwFyMVpvdfZDnCbgQNf3vw6ORikB4aF37LUSR\n" + "+inRXHJ6uOWqyp72QZfyR7Zk0/Tb1qById4aFDeD3aN6nfga6wzDO9RBCa7GksZWY1qjVBeJqN1S\n" + "dzwwAD2EDZf88P590p4oletIE5FJtCCQTbjT9FDONe/ekLvrdhIMPzSOaQIDAQABo4IB/jCCAfow\n" + "HwYDVR0jBBgwFoAU0Rhnw1f+EpqRa19fMeo+woSH+70wHQYDVR0OBBYEFCs/BwiQdYrDKwT3jFHj\n" + "qjPuCoZjMIGYBggrBgEFBQcBAQSBizCBiDBFBggrBgEFBQcwAoY5aHR0cDovL2djYS5uYXQuZ292\n" + "LnR3L3JlcG9zaXRvcnkvQ2VydHMvSXNzdWVkVG9UaGlzQ0EucDdiMD8GCCsGAQUFBzABhjNodHRw\n" + "Oi8vZ2NhLm5hdC5nb3YudHcvY2dpLWJpbi9PQ1NQMi9vY3NwX3NlcnZlci5leGUwDgYDVR0PAQH/\n" + "BAQDAgWgMB4GA1UdIAQXMBUwCQYHYIZ2ZQADAzAIBgZngQwBAgIwHgYDVR0RBBcwFYITb3BlbmRh\n" + "dGEuZXBhLmdvdi50dzAgBgNVHQkEGTAXMBUGB2CGdgFkAgExCgYIYIZ2AWQDAwEwgYgGA1UdHwSB\n" + "gDB+MD2gO6A5hjdodHRwOi8vZ2NhLm5hdC5nb3YudHcvcmVwb3NpdG9yeS9HQ0E0L0NSTDIvQ1JM\n" + "XzAwMDIuY3JsMD2gO6A5hjdodHRwOi8vZ2NhLm5hdC5nb3YudHcvcmVwb3NpdG9yeS9HQ0E0L0NS\n" + "TDIvY29tcGxldGUuY3JsMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG\n" + "9w0BAQsFAAOCAQEAUUouHC1BaeHexR2lQ/lkshctinFwTZGtpPDZv2Z7E647qQWia0y1U2mVUXmm\n" + "tUZkFphPBOmRuzEnJ3Cmxel8YADRzj1lfajW92LFfgSXDlIJTKV73+gdzMDvrFCJaSqJ30QhFDza\n" + "aVSaps9XMWxEbPv0Wn7wLDYpi0wGxJtjB/W9tbnH1x+b6PAOzoL6yakYF/4vDn+npudW/oo8IaDu\n" + "QGHAG5i8E5V1YFRuJRxpUMfNuyXTKgqucoiBLk6dJlJ+AbvmS3046+Hvxwmw4ciHPc9yr9GsgOGW\n" + "5jLSmcYHzv5ydxuyWQSCXJ9obuWwwPWABrMiEuphrte2IHsiG44cwA==\n" + "-----END CERTIFICATE-----"; private RecyclerView recyclerView; private RecyclerView.Adapter mAdapter; private RecyclerView.LayoutManager layoutManager; private ArrayList<UviInfo> data; private OkHttpClient mOkHttpClient = null; private final String TAG = "MainActivity"; private AlertDialog.Builder builder; private GestureDetector gestureDetector; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //取得資料 data = getData(); recyclerView = findViewById(R.id.list); layoutManager = new LinearLayoutManager(this); mAdapter = new ItemAdapter(data); //recyclerView 設定 adapter & layoutManager recyclerView.setAdapter(mAdapter); recyclerView.setLayoutManager(layoutManager); //https 驗證會有問題,這裡因為主要是說明recyclerView,想更深入了解的話請google或參考setCertificates()這個函式 mOkHttpClient = setCertificates(new Buffer().writeUtf8(cert).inputStream()); builder = new AlertDialog.Builder(this); gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onSingleTapUp(MotionEvent e) { if(recyclerView.findChildViewUnder(e.getX(), e.getY())!=null){ int pos = recyclerView.getChildAdapterPosition(recyclerView.findChildViewUnder(e.getX(), e.getY())); UviInfo info = data.get(pos); Log.d(TAG, String.valueOf(pos)); builder.setTitle("詳細資訊") .setMessage("縣市:"+info.getCounty()+ "\n站名:"+info.getSiteName()+ "\nWGS84Lat:"+info.getWgs84lat()+ "\nWGS84Lon:"+ info.getWgs84lon()+ "\n發布單位:"+info.getPublishAgency()+ "\n發布時間:"+info.getPublishTime()) .setPositiveButton("確認", null) .create().show(); return true; } return false; } }); recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { if(gestureDetector.onTouchEvent(motionEvent)) return true; return false; } @Override public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean b) { } }); } private ArrayList<UviInfo> getData(){ FutureTask<ArrayList<UviInfo>> task = new FutureTask<>(new Callable<ArrayList<UviInfo>>() { @Override public ArrayList<UviInfo> call() throws Exception { Request request = new Request.Builder().url("https://opendata.epa.gov.tw/ws/Data/UV/?$format=json") .get() .build(); JsonArray jarr = new JsonParser().parse(mOkHttpClient.newCall(request).execute().body().string()).getAsJsonArray(); ArrayList<UviInfo> data = new ArrayList<>(); for(JsonElement elem : jarr){ data.add(new UviInfo(elem.getAsJsonObject())); } return data; } }); new Thread(task).start(); try { return task.get(); } catch (ExecutionException e) { e.printStackTrace(); return new ArrayList<>(); } catch (InterruptedException e) { e.printStackTrace(); return new ArrayList<>(); } } public OkHttpClient setCertificates(InputStream... certificates) { try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); int index = 0; for (InputStream certificate : certificates) { String certificateAlias = Integer.toString(index++); keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate)); try { if (certificate != null) certificate.close(); } catch (IOException e) { } } SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); X509TrustManager trustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }; trustManagerFactory.init(keyStore); sslContext.init ( null, trustManagerFactory.getTrustManagers(), new SecureRandom() ); return new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory(), trustManager) .build(); } catch (Exception e) { e.printStackTrace(); return new OkHttpClient(); } } }
最後,因為有用到網路所以記得在AndroidManifest.xml裡面加上:
<uses-permission android:name="android.permission.INTERNET"/>
最後附上完成圖:
以上為個人淺見,還請各位大大指教~
參考資料:
https://www.javadoc.io/doc/com.google.code.gson/gson/2.8.5
https://square.github.io/okhttp/3.x/okhttp/
http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html
https://github.com/square/okio
https://developer.android.com/guide/topics/ui/layout/recyclerview
https://opendata.epa.gov.tw/ws/Data/UV/?$format=json
留言
張貼留言