[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
留言
張貼留言