上一篇我們已經成功抓取天氣API的資料,這篇就接續下半部分介面布局的設置和存放資料以及程式碼撰寫。
首先,我們一樣先把天氣APP的介面布局設置完成
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Spinner
android:id="@+id/time"
android:layout_width="220dp"
android:layout_height="35dp"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0.486"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
app:layout_constraintVertical_bias="0.287" />
<TextView
android:id="@+id/main_result_rt"
android:layout_width="350dp"
android:layout_height="369dp"
android:background="@drawable/border"
android:text="TextView"
android:gravity="center"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0.428"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/guideline3"
app:layout_constraintVertical_bias="0.069" />
<Button
android:id="@+id/main_search_btn"
android:layout_width="243dp"
android:layout_height="160dp"
android:backgroundTint="@color/KAMENOZOK"
android:text="搜尋"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0.458"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
app:layout_constraintVertical_bias="0.9" />
<Spinner
android:id="@+id/elementName"
android:layout_width="150dp"
android:layout_height="35dp"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0.91"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
app:layout_constraintVertical_bias="0.115" />
<Spinner
android:id="@+id/locationName"
android:layout_width="150dp"
android:layout_height="35dp"
android:background="@drawable/border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0.116"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toBottomOf="@+id/main_result_rt"
app:layout_constraintVertical_bias="0.115" />
</androidx.constraintlayout.widget.ConstraintLayout>
介面設置完成,會長這樣
我解釋一下,在抓取天氣 API 的資料中,設計了三個重要的 Spinner 元件,分別用來呈現「縣市地區 (locationName)」、「天氣因子 (elementName)」以及「時間範疇 (time)」,這些元素以縝密的順序進行排列,從地域資訊的「縣市地區」,到氣象條件的「天氣因子」,再到時間跨度的「今日、明日或後日」,以便使用者能輕鬆篩選並了解所需的天氣資訊。
存放Spinner假資料的地方,在(res/values)下的,紅框strings.xml這個頁面
利用string-array存放Spinner假資料,我創建四個 Spinner 的假資料陣列,分別是 location_data、element_data、time_data 和 tw_element,這些陣列資料將在應用中提供給 Spinner 作為選項,方便讓String[]抓取資料。
strings.xml
<string-array name="陣列的名字">
<item>要存放的資料</item>
<item name="item_1">也可以給item名字,但不是必要</item>
</string-array>
<string-array name="location_data">
<item>臺北市</item>
<item>新北市</item>
<item>基隆市</item>
<item>桃園市</item>
<item>新竹縣</item>
<item>新竹市</item>
<item>苗栗縣</item>
<item>臺中市</item>
<item>彰化縣</item>
<item>南投縣</item>
<item>雲林縣</item>
<item>嘉義縣</item>
<item>嘉義市</item>
<item>臺南市</item>
<item>高雄市</item>
<item>屏東縣</item>
<item>宜蘭縣</item>
<item>花蓮縣</item>
<item>臺東縣</item>
<item>澎湖縣</item>
<item>金門縣</item>
<item>連江縣</item>
</string-array>
<string-array name="element_data">
<item>Wx</item>
<item>PoP</item>
<item>MinT</item>
<item>CI</item>
<item>MaxT</item>
<item>All</item>
</string-array>
<string-array name="time_data">
<item>今天</item>
<item>明天</item>
<item>後天</item>
</string-array>
<string-array name="tw_element">
<item>當日天氣氣象:</item>
<item>當日降雨機率:</item>
<item>當日最底溫度:</item>
<item>當日舒適度:</item>
<item>當日最高溫度:</item>
</string-array>
了解布局後,現在開始連接程式碼,我會簡單說明一下每組程式碼,天氣API的用途
weatherResponse.Java
public class weatherResponse {
public Records records;
public class Records{
public List<Location> location;
}
public class Location{
public String locationName;
public List<WeatherElement> weatherElement;
}
public class WeatherElement{
public String elementName;
public List<Time> time;
}
public class Time{
public Parameter parameter;
}
public class Parameter{
public String parameterName;
public String parameterUnit;
}
public String getDataByTime(Integer index,Integer day) {
return records.location.get(0).weatherElement.get(index).time.get(day).
parameter.parameterName;
}
public String getUnitByTime(Integer index){
return records.location.get(0)
.weatherElement.get(0).time.get(index).parameter.parameterUnit;
}
public Integer getElementSize(){
return records.location.get(0).weatherElement.size();
}
}
這組程式碼,表示從天氣 API 抓取的資料結構
首先宣告 records 是他存取天氣 API 的所有資料
往下,每個class都有包含他需要資料,例如:
Location 包含地點名稱(locationName)和一個 WeatherElement 列表,WeatherElement 代表該地點的不同天氣因子(例如溫度、濕度)
WeatherElement 包含天氣因子的名稱(elementName)和一個 Time 列表,Time 代表不同時間點的天氣資料、時間段 (time)
getDataByTime:根據天氣因子的索引和時間索引,返回對應的天氣資料。
getUnitByTime:根據時間索引,返回天氣單位。
getElementSize:返回該地點的天氣因子數量。
weatherResponse這組程式碼,會根據使用者選擇提取特定天氣資料。
GetApi.Java
public interface GetApi {
@GET("F-C0032-001")
Observable<weatherResponse> getWeatherApi(
@Query("Authorization") String Authorization,
@Query("locationName") String locationName,
@Query("elementName") String elementName
);
}
GetApi 介面,用來向 /F-C0032-001 這個 API 端點發送 GET 請求,並透過查詢參數傳遞授權金鑰(Authorization)、地點名稱(locationName)、和天氣因子(elementName)來查詢指定縣市的天氣資料。
ApiClient.Java
public class ApiClient {
public Retrofit myWeatherApi() {
return new Retrofit.Builder()
.baseUrl("https://opendata.cwa.gov.tw/api/v1/rest/datastore/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory.create())
.build();
}
}
MainActivity.Java
public class MainActivity extends AppCompatActivity {
private TextView result;
private Button search;
private Spinner time_spinner, element_spinner, location_spinner;
private String selected_time, selected_element, selected_location;
private ApiClient apiClient;
private GetApi getApi;
private String[] location_data, element_data, time_data, tw_element;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
result = findViewById(R.id.main_result_rt);
search = findViewById(R.id.main_search_btn);
time_spinner = findViewById(R.id.time);
element_spinner = findViewById(R.id.elementName);
location_spinner = findViewById(R.id.locationName);
apiClient = new ApiClient();
getApi = apiClient.myWeatherApi().create(GetApi.class);
location_data = getResources().getStringArray(R.array.location_data);
element_data = getResources().getStringArray(R.array.element_data);
time_data = getResources().getStringArray(R.array.time_data);
tw_element = getResources().getStringArray(R.array.tw_element);
setSpinner();
search.setOnClickListener(view -> getWeather(selected_location, selected_element, selected_time));
}
private void setSpinner() {
ArrayAdapter location_adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, location_data);
ArrayAdapter element_adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, element_data);
ArrayAdapter time_adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, time_data);
location_spinner.setAdapter(location_adapter);
element_spinner.setAdapter(element_adapter);
time_spinner.setAdapter(time_adapter);
location_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selected_location = location_spinner.getSelectedItem().toString();
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
element_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selected_element = element_spinner.getSelectedItem().toString();
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
time_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
selected_time = time_spinner.getSelectedItem().toString();
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
private void getWeather(String selectedLocation, String selectedElement, String selectedTime) {
String authorization = "CWA-E7B0471C-F45E-4B28-9354-5953C80D9416";
if (selectedElement.equals("All")) selectedElement = "";
String finalSelectedElement = selectedElement;
getApi.getWeatherApi(authorization, selectedLocation, selectedElement)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<weatherResponse>(){
@Override
public void onNext(@NonNull weatherResponse weatherResponse) {
result.setText("");
List<String> time_list = Arrays.asList(time_data);
List<String> element_list = Arrays.asList(element_data);
try {
if (weatherResponse.getElementSize() != 1) {
for (int i = 0; i < weatherResponse.getElementSize(); i++) {
result.append(tw_element[i] + weatherResponse.getDataByTime(i, time_list.indexOf(selectedTime)) + "\n");
}
} else {
result.setText(tw_element[element_list.indexOf(finalSelectedElement)] + weatherResponse.getDataByTime(0, time_list.indexOf(selectedTime)) + "\n");
}
} catch (Exception e) {
Log.e("test", "onNext: " + e);
}
}
@Override
public void onError(@NonNull Throwable e) {
Log.d("test", "onError: ");
}
@Override
public void onComplete() {
Log.d("test", "onComplete: ");
}
});
}
}
顯示成果: