iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
Mobile Development

Android Studio開發過程和介紹系列 第 28

【DAY 28】 將這個月所學集合成一個APP!(功能設計篇)

  • 分享至 

  • xImage
  •  

前言

這一篇會講解天氣API、計算機、1A2B的寫法,這次天氣Api的部分會使用一種叫MVP的架構撰寫,使用這種架構撰寫程式有助於程式的可讀性,也比較好做修整,簡單介紹一下就是將View(介面)、Presenter(邏輯判斷) 給分開,讓個別代表的東西可以分開來去做設定,運作順序是 透過View去告知 Presenter 要執行什麼,再透過Presenter去告知View有什麼東西更新了,View接收到後再去作變更。
文章最後會附上三個功能的完整程式碼。

dependencies

    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    //Retrofit

    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
    implementation 'io.reactivex.rxjava3:rxjava:3.1.6'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    //RxJava

manifests

    <uses-permission android:name="android.permission.INTERNET" />

strings

將spinner的資料加進string,要使用到時在調取

    <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>

WeatherFunction

  • 成果展示

  • WeatherResponse

首先先建立資料傳輸的媒介,建立一個class叫WeatherResponse

import java.util.List;

public class WeatherResponse {
    public Records records;//這次的Api資料,records後面是{},也就是class類別,所以要建立一個records的class

    public class Records{//接著看到records底下的location,他後面是先[]才是{},意思是location裡面的資料是用list包起來,再更裡面的資料是class類別
        public List<Location> location;//這裡代表在List裡面放入Location的物件,而Location是class,所以就是用List去包class,就可以完成上述的順序

    }//下面的資料都是以相同的架構去寫
    public class Location{
        public String locationName;
        public List<WeatherElement> weatherElement;

        @Override
        public String toString() {
            return "Location{" +
                    "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();
    }
  • ApiClient

接著要建立一個class叫ApiClient

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

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();
    }
}
  • GetApi

再來要建立一個interface叫GetApi

import com.example.demo.CommonData.Resopnse.WeatherResponse;

import io.reactivex.rxjava3.core.Observable;
import retrofit2.Response;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface GetApi {
    @GET("F-C0032-001")
    Observable<Response<WeatherResponse>> getWeatherApi(
            @Query("Authorization") String Authorization,
            @Query("locationName") String locationName,
            @Query("elementName") String elementName
    );
}
  • MVP架構

  • Contract

首先建立一個interface,取名為WeatherContract,就是Weather這個功能的接口接著會在裡面放入兩個介面「viewpresenter」,並且會在這兩個介面之下再去撰寫相關的方法

import com.example.demo.CommonData.Resopnse.WeatherResponse;

public interface WeatherContract {
    interface view{
        void getWeatherData(WeatherResponse data);
    }
    interface presenter{
        void setWeatherApi(String locationName, String elementName, String time);
    }
}

可以看到我在view之下建立了獲取天氣資料的方法,並在presenter之下建立了要取天氣Api資料的方法

  • Presenter

接著建立一個class,取名為WeatherPresenter,在這裡要implement WeatherContract的presenter,就是將接口的presenter引用到這裡,並且撰寫相關的方法應該要做的事,下面會一步一步介紹要如何撰寫

1. implements Contract.presenter
public class WeatherPresenter implements WeatherContract.presenter

首先要在presenter先implements Contract定義的presenter
接著如果有在接口的presenter定義方法,那implements後的presenter也會需要定義那些方法的內容。

2. 宣告、初始化、建構元
    private WeatherContract.view view;
    private ApiClient apiClient;
    private GetApi getApi;
    public WeatherPresenter(WeatherContract.view view){
        this.view = view;
        apiClient =new ApiClient();
        getApi = apiClient.myWeatherApi().create(GetApi.class);
    }

宣告最主要是要定義view,跟建構元搭配將這裡的view跟傳入的view綁定,才可以將做完邏輯處理的資料回傳到正確的view,但是這次因為結合了抓取天氣Api資料的功能,所以就將抓取Api資料的工作也丟到presenter來執行,因此還宣告了ApiClient跟GetApi,
再來建構元的部分,就如第一段提到的要設定傳入view,接著上面宣告的view就要綁定到傳入的view,接著就是其他物件的初始化設定。

3. 定義方法
    @Override
    public void setWeatherApi(String locationName, String elementName, String time) {
        String authorization = "CWB-2F70211E-8C2F-4A7F-8841-292FDCE00BEB";
        if (elementName.equals("All")) elementName = "";
        String finalSelectedElement = elementName;
        getApi.getWeatherApi(authorization,locationName,finalSelectedElement)
                .observeOn(Schedulers.io())
                .subscribeOn(AndroidSchedulers.mainThread())
                .subscribe(new DisposableObserver<Response<WeatherResponse>>() {
                    @Override
                    public void onNext(@NonNull Response<WeatherResponse> weatherResponse) {
                        view.getWeatherData(weatherResponse.body());
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        Log.d("test", "onError: " + e +"\n");
                    }

                    @Override
                    public void onComplete() {
                        Log.d("test", "onComplete: ");
                    }
                });
    }

最後就是方法的撰寫,這裡基本上就跟之前介紹的一樣,只有在傳輸資料時做了點變動,這次我將抓取到的資料全部回傳到view去做處理,這邊寫到view.getWeatherData(...),就是在呼叫接口裡面的view的方法,但是因為這裡的view跟呼叫方法那端的view綁定,所以就會將資料回傳給呼叫方法那端的getWeatherData()。

  • WeatherFunction

接著就進到activity端也就是view的設定

public class WeatherFunction extends AppCompatActivity implements WeatherContract.view 

首先要先做的就是implements 接口的 view,並且將view的方法給引入,在輸入完上面那串後,會發現底下出現紅色的線,這其實就是在提醒你要引入view的方法,一樣的在那個位置按下 ALT + ENTER 就會幫你將方法給引入囉~

    private FloatingActionButton floatingActionButton;
    private TextView result;
    private Button search;
    private Spinner location_spinner,element_spinner,time_spinner;
    private String selected_location,selected_element,selected_time;
    private String[] location_data,element_data,time_data,tw_element;
    private WeatherPresenter presenter;

接著就是各種物件的宣告,這裡稍微提一下之前沒出現過的東西
private FloatingActionButton floatingActionButton;,這個是一個叫做浮動式按鈕的物件,這個物件可以無視linearLayout的限制,強行讓它出現在我指定的位置,使用方法就跟一般的Button一模一樣,因此在這次的實作我將這個按鈕設定為返回上一頁的功能
private WeatherPresenter presenter;,這個是呼叫presenter,之後初始化時就回傳入這個activity的view。

    presenter= new WeatherPresenter(this);

    setFindById();//綁定物件id
    setListener();//設定監聽器
    getSpinnerData();//獲取Spinner選單的資料
    setSpinner();//設定Spinner

物件的初始化以及宣告,這個部分設定spinner的部分就跟之前文章講解的一樣,這裡就不重複說明有需要看詳解的可以看之前的文章Day17,這裡就講解setFindById跟setListener的作用,不過看註解其實也知道這個在做的是什麼,但這邊還是稍微展示一下

  • setFindById

    private void setFindById() {
        floatingActionButton = findViewById(R.id.weather_floatActionButton);
        result = findViewById(R.id.weather_result);
        search = findViewById(R.id.weather_search);
        location_spinner = findViewById(R.id.weather_locationName);
        element_spinner = findViewById(R.id.weather_elementName);
        time_spinner = findViewById(R.id.weather_time);
    }//綁定物件id
  • setListener

    private void setListener() {
        floatingActionButton.setOnClickListener(view -> finish());

        search.setOnClickListener(view -> presenter.setWeatherApi(selected_location,selected_element,selected_time));
    }//設定監聽事件

這邊可以看到浮動式按鈕我設定它執行finish的工作,也就是將現在這個頁面結束掉,這樣就會返回到上一頁囉~
接著當我按下搜尋按鈕後,就會呼叫presenter的抓取天氣Api資料的方法,前面也介紹過當抓取完資料後,就會呼叫view的方法將資料回傳到view,所以下面就直接帶到剛剛引入的view的方法吧

  • getWeatherData

    @Override
    public void getWeatherData(WeatherResponse data) {
        if (selected_element.equals("All")) selected_element = "";
        String finalSelectedElement = selected_element;
        result.setText("");
        List time_list = Arrays.asList(time_data);
        List element_list = Arrays.asList(element_data);
        if(data.getElementSize() != 1){
            for (int i = 0; i < data.getElementSize(); i++) {
                result.append(tw_element[i] + data.getDataByTime(i,time_list.indexOf(selected_time))+"\n");
            }
        }
        else {
            result.setText(tw_element[element_list.indexOf(finalSelectedElement)] + data.getDataByTime(0,time_list.indexOf(selected_time))+"\n");
        }
    }

當呼叫presenter的抓取天氣Api資料的方法後,就會使用getWeatherData來將資料傳回到view,所以這個方法就會在呼叫完方法後,又被呼叫並且執行,那我就可以將把資料設定到TextView上的工作也放到這裡,讓view接收到資料後就把資料丟給TextView顯示。

CalculatorFunction

  • 成果展示

計算機的撰寫我就沒使用到MVP架構了,因為也不需要,特地去使用反而會複雜化程式

public class CalculatorFunction extends AppCompatActivity implements View.OnClickListener 

首先先implements View.OnClickListener

    @Override
    public void onClick(View view) {
    }//當有點擊事件發生時就集中到這裡處理

接著需要引入onClick這個方法,這裡會這麼做是為了要將點擊事件都集中到一起做處裡

    private Button add, sub, mult, div, dot, equal,clear;
    private Button num_0, num_1, num_2, num_3, num_4, num_5, num_6, num_7, num_8, num_9;
    private TextView tv_solution,tv_result;
    private FloatingActionButton floatingActionButton;

宣告的部分

    setFindById();//綁定物件id
    setListener();//設定監聽器

onCreate的內容,這裡綁定物件id跟剛剛介紹的一樣,監聽器的部分則有一點變動

  • setListener

    private void setListener() {
        add.setOnClickListener(this);   sub.setOnClickListener(this);
        mult.setOnClickListener(this);  div.setOnClickListener(this);
        dot.setOnClickListener(this);   equal.setOnClickListener(this);
        num_0.setOnClickListener(this);   num_1.setOnClickListener(this);
        num_2.setOnClickListener(this);   num_3.setOnClickListener(this);
        num_4.setOnClickListener(this);   num_5.setOnClickListener(this);
        num_6.setOnClickListener(this);   num_7.setOnClickListener(this);
        num_8.setOnClickListener(this);   num_9.setOnClickListener(this);
        clear.setOnClickListener(this);
        //將點擊事件設定到onClick

        floatingActionButton.setOnClickListener(view -> finish());
        //浮動式按鈕不用集中到onClick,所以這裡就照樣執行finish的指令
    }//設定監聽器

在setOnClickListener裡面輸入this,就可以將點擊事件都集中到onClick設定

  • onClick

@Override
    public void onClick(View view) {
        Button button = (Button) view;
        String btnText = button.getText().toString();
        String dataToCalculate = tv_result.getText().toString();

        if (btnText.equals("=")){
            tv_result.setText(getResult(tv_solution.getText().toString()));
            tv_solution.append(dataToCalculate + btnText);
            return;
        } else if (btnText.equals("CLEAR")) {
            tv_solution.setText("");
            tv_result.setText("");
            return;
        } else if (btnText.equals("+") || btnText.equals("-") || btnText.equals("*") || btnText.equals("/")){
            tv_solution.setText(tv_result.getText() + btnText);
            tv_result.setText("");
        }else{
            dataToCalculate = dataToCalculate + btnText;
            tv_result.setText(dataToCalculate);}
    }//當有點擊事件發生時就集中到這裡處理

首先要先定義點擊事件為哪個按鈕執行的Button button = (Button) view;
接著因為Button上面的文字就是我們所需要的資料,所以這裡又用了一個String將觸發點擊事件的Button的文字記錄下來,
這次計算機我的想法為,按下數字時下方的螢幕會顯示出來,按下計算符號時就將下方螢幕的資料丟到上面的螢幕紀錄,接著再按下數字跟=鍵進行計算,並把結果丟給下方主螢幕顯示

  • getResult

計算的方法使用到了mxparser這個第三方函式庫對輸入的數學式進行計算,這裡附上下載連結
往下拉到這裡,並點選中間JAVA的那個選項

https://ithelp.ithome.com.tw/upload/images/20231008/201615000w1lc0Rjxg.png

下載完後解壓縮會得到這個檔案

https://ithelp.ithome.com.tw/upload/images/20231008/20161500zu72Q19DOp.png

點開後再按bin會來到這個畫面

https://ithelp.ithome.com.tw/upload/images/20231008/2016150071wLcFUBQS.png

這裡要選擇自己android studio使用的jdk版本,如果不確定自己的jdk版本為多少的話,這邊教各位一個方法查看
首先點擊File再點選Project Structure...

https://ithelp.ithome.com.tw/upload/images/20231008/20161500t4MceV8eW5.png

接著再找到SDK Location裡面的JDK Location

https://ithelp.ithome.com.tw/upload/images/20231008/20161500ynFW6pA4hA.png

再來就可以在下面的Gradle JDK找到囉~

https://ithelp.ithome.com.tw/upload/images/20231008/201615005VBYA9qVuR.png

這裡可以看到我的JDK是17版,所以我就選擇JDK17的資料夾點進去

https://ithelp.ithome.com.tw/upload/images/20231008/20161500Tw3nwhrDzs.png

接著會看到這個檔案,將它複製起來
接著回到android studio

https://ithelp.ithome.com.tw/upload/images/20231008/201615009cnN7HszKU.png

將Android 改選為 Project

https://ithelp.ithome.com.tw/upload/images/20231008/201615009cVl6gFKp3.png

接著 Demo > app > libs,將剛剛複製的檔案貼到libs,直接ctrl+v貼上就可以了

最後對著貼入的的檔按按右鍵,再按Add As Library...

https://ithelp.ithome.com.tw/upload/images/20231008/20161500k9VokHlYwh.png

到此就成功將第三方套件給裝好囉~

    private String getResult(String dataToCalculate) {
        try {
            Expression expression = new Expression(dataToCalculate+tv_result.getText().toString());
            return String.valueOf(expression.calculate());
        }catch (Exception e){
            return "Error";
        }
    }

接著看到內容,使用方法就是將要計算的String傳給Expression,接著再.calculate就會自動幫你計算囉~

GameFunction

  • 成果展示

這次小遊戲我選擇 1A2B 來製作,這個部分也可以發揮自己的創意做不同的遊戲,這個部分也是一樣是直接撰寫沒有使用MVP架構

    private EditText et_player_answer;
    private Button btn_enter;
    private TextView result;
    private FloatingActionButton floatingActionButton;
    private ArrayList<Integer> CorrectAnswer, PlayerAnswer;

宣告的部分,這裡用兩個ArrayList來分別儲存「正確答案、玩家答案」

    setFindById();//綁定物件id
    setAnswer();//設定正確答案
    setListener();//設定監聽器

onCreate的部分,在一進到程式後就先產生一個答案,接著就可以開始進行猜題

  • setAnswer

    private void setAnswer() {
        CorrectAnswer= new ArrayList<>();
        ArrayList<Integer> data = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            data.add(i);
        }//先建立一個0到9的陣列
        Collections.shuffle(data);//接著將陣列洗牌
        CorrectAnswer.addAll(data.subList(0,4));//將前四個數字加進正確解答
        Log.d("test", "setAnswer: "+CorrectAnswer);
    }//設定正確答案

我產生正確答案的方法為,先建立一個0到9的陣列,接著使用shuffle的指令,將0~9的資料打亂,最後在把前4個數字丟給正確答案的陣列

  • setListener

    private void setListener() {
        btn_enter.setOnClickListener(view -> CheckAnswer(et_player_answer.getText().toString()));
        floatingActionButton.setOnClickListener(view -> finish());
    }

浮動式按鈕的設定還是一樣,再按下輸入按鈕後,會呼叫CheckAnswer並把輸入的資料傳入

  • CheckAnswer

PlayerAnswer = new ArrayList<>();
        et_player_answer.setText("");//每次輸入完答案都把EditText清空

        String regex = ".*[a-zA-Z].*";//使用正則表達式判斷輸入的答案有沒有英文字母
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(player_answer);
        if (player_answer.length() != 4 || matcher.matches()) {
            Toast.makeText(this,"請輸入四個數字",Toast.LENGTH_SHORT).show(); return;}

首先開頭就先檢測傳入的資料是否符合規定,在檢測英文字母時使用到了正則表達式,接著使用if判斷式判斷輸入的字串長度是否不等於四,以及是否輸入了英文,如果不符合規則就用一個Toast來告知要重新輸入,並且用return;直接將這段程式結束掉,這個部分可以使用的方法也很多,或許也可以使用字符串來判斷是否輸入正確。

  • 補充 : 正則表達式是一種用來匹配和處理文本模式的強大工具,包含一系列字符和特殊符號的字串,用於定義一個文本模式或規則,然後可以使用這個模式來搜索、匹配、替換或提取文本中的特定部分。
    for (int i = 0; i < 4; i++) {
            PlayerAnswer.add(Character.getNumericValue(player_answer.charAt(i)));
        }//將玩家輸入的答案丟給陣列存取

    int quantityA = 0, quantityB = 0;//A跟B的數量
    for (int i = 0; i < 4; i++) {
        if (CorrectAnswer.contains(PlayerAnswer.get(i))){//先判斷輸入的答案有沒有在正確答案內
            if (CorrectAnswer.get(i).equals(PlayerAnswer.get(i))) quantityA++;//如果有就判斷當下的數字是否跟正確答案一樣,是就A+1
            else quantityB++;//反之B+1
        }
    }//判斷幾A幾B
    if (quantityA == 4){
        Toast.makeText(this,"恭喜~你猜中了!",Toast.LENGTH_SHORT).show();
        result.setText("");
        setAnswer();
    }//當A的數量為四個的時候,就展開新的一輪
    result.append("這次輸入 : "+ PlayerAnswer + ",結果為 : " + quantityA + "A" + quantityB + "B\n");
    //沒有4A的時候就正常顯示幾A幾B

當輸入的數字符合規定時就進到下一個階段,首先將玩家輸入的答案丟到一個陣列存取
接著用兩層的if判斷式來判斷A跟B的數量,首先先判斷輸入的答案有沒有在正確答案內,有就進到下一個if判斷式,判斷這個數字的位置跟正確解答是否一樣,是就讓A的數量+1反之就讓B的數量+1
最後再進行判斷現在的A跟B的數量,再將結果放到TextView顯示出來

這次的設計TextView不是單純的顯示出來而已,因為考慮到或許會猜太多次,所以這次我將TextView在布局的時放進了ScrollView裡面,在ScrollView裡面的物件如果超過ScrollView原本的大小,就會升成一個滾輪可以上下滑動,所以當猜的次數太多時,就可以上下滑動以確認自己所猜的數字

Function的完整程式碼

  • Weather

import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.demo.CommonData.Resopnse.WeatherResponse;
import com.example.demo.R;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.util.Arrays;
import java.util.List;

public class WeatherFunction extends AppCompatActivity implements WeatherContract.view {
    private FloatingActionButton floatingActionButton;
    private TextView result;
    private Button search;
    private Spinner location_spinner,element_spinner,time_spinner;
    private String selected_location,selected_element,selected_time;
    private String[] location_data,element_data,time_data,tw_element;
    private WeatherPresenter presenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_weather_function);

        presenter= new WeatherPresenter(this);

        setFindById();//綁定物件id
        setListener();//設定監聽器
        getSpinnerData();//獲取Spinner選單的資料
        setSpinner();//設定Spinner
    }

    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);//分別設定spinner的資料

        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) {}
        });//location的spinner點擊事件

        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) {}
        });//element的spinner點擊事件

        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) {}
        });//time的spinner點擊事件
    }//跟Spinner相關的設定

    private void getSpinnerData() {
        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);
    }//從string獲取資料

    private void setListener() {
        floatingActionButton.setOnClickListener(view -> finish());

        search.setOnClickListener(view -> presenter.setWeatherApi(selected_location,selected_element,selected_time));
    }//設定監聽事件

    private void setFindById() {
        floatingActionButton = findViewById(R.id.weather_floatActionButton);
        result = findViewById(R.id.weather_result);
        search = findViewById(R.id.weather_search);
        location_spinner = findViewById(R.id.weather_locationName);
        element_spinner = findViewById(R.id.weather_elementName);
        time_spinner = findViewById(R.id.weather_time);
    }//綁定物件id

    @Override
    public void getWeatherData(WeatherResponse data) {
        if (selected_element.equals("All")) selected_element = "";
        String finalSelectedElement = selected_element;
        result.setText("");
        List time_list = Arrays.asList(time_data);
        List element_list = Arrays.asList(element_data);
        if(data.getElementSize() != 1){
            for (int i = 0; i < data.getElementSize(); i++) {
                result.append(tw_element[i] + data.getDataByTime(i,time_list.indexOf(selected_time))+"\n");
            }
        }
        else {
            result.setText(tw_element[element_list.indexOf(finalSelectedElement)] + data.getDataByTime(0,time_list.indexOf(selected_time))+"\n");
        }
    }
}
  • Calculator

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.demo.R;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

import org.mariuszgromada.math.mxparser.Expression;

public class CalculatorFunction extends AppCompatActivity implements View.OnClickListener {
    private Button add, sub, mult, div, dot, equal,clear;
    private Button num_0, num_1, num_2, num_3, num_4, num_5, num_6, num_7, num_8, num_9;
    private TextView tv_solution,tv_result;
    private FloatingActionButton floatingActionButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calculator_function);

        setFindById();//綁定物件id
        setListener();//設定監聽器
    }

    private void setListener() {
        add.setOnClickListener(this);   sub.setOnClickListener(this);
        mult.setOnClickListener(this);  div.setOnClickListener(this);
        dot.setOnClickListener(this);   equal.setOnClickListener(this);
        num_0.setOnClickListener(this);   num_1.setOnClickListener(this);
        num_2.setOnClickListener(this);   num_3.setOnClickListener(this);
        num_4.setOnClickListener(this);   num_5.setOnClickListener(this);
        num_6.setOnClickListener(this);   num_7.setOnClickListener(this);
        num_8.setOnClickListener(this);   num_9.setOnClickListener(this);
        clear.setOnClickListener(this);
        //將點擊事件設定到OnClick

        floatingActionButton.setOnClickListener(view -> finish());
        //浮動式按鈕不用集中到OnClick,所以這裡就照樣執行finish的指令
    }//設定監聽器

    private void setFindById() {
        add = findViewById(R.id.btn_add);   sub = findViewById(R.id.btn_sub);
        mult = findViewById(R.id.btn_mult); div = findViewById(R.id.btn_div);
        dot = findViewById(R.id.btn_dot);   equal = findViewById(R.id.btn_equal);
        num_0 = findViewById(R.id.num_0);   num_1 = findViewById(R.id.num_1);
        num_2 = findViewById(R.id.num_2);   num_3 = findViewById(R.id.num_3);
        num_4 = findViewById(R.id.num_4);   num_5 = findViewById(R.id.num_5);
        num_6 = findViewById(R.id.num_6);   num_7 = findViewById(R.id.num_7);
        num_8 = findViewById(R.id.num_8);   num_9 = findViewById(R.id.num_9);
        clear = findViewById(R.id.btn_clear);

        floatingActionButton = findViewById(R.id.calculator_floatActionButton);

        tv_result = findViewById(R.id.calculator_result);
        tv_solution = findViewById(R.id.calculator_solution);
    }//綁定物件id

    @Override
    public void onClick(View view) {
        Button button = (Button) view;
        String btnText = button.getText().toString();
        String dataToCalculate = tv_result.getText().toString();

        if (btnText.equals("=")){
            tv_result.setText(getResult(tv_solution.getText().toString()));
            tv_solution.append(dataToCalculate + btnText);
            return;
        } else if (btnText.equals("CLEAR")) {
            tv_solution.setText("");
            tv_result.setText("");
            return;
        } else if (btnText.equals("+") || btnText.equals("-") || btnText.equals("*") || btnText.equals("/")){
            tv_solution.setText(tv_result.getText() + btnText);
            tv_result.setText("");
        }else{
            dataToCalculate = dataToCalculate + btnText;
            tv_result.setText(dataToCalculate);}
    }//當有點擊事件發生時就集中到這裡處理

        private String getResult(String dataToCalculate) {
            try {
                Expression expression = new Expression(dataToCalculate+tv_result.getText().toString());
                return String.valueOf(expression.calculate());
            }catch (Exception e){
                return "Error";
            }
        }
}
  • Game

import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.demo.R;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

import java.util.ArrayList;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GameFunction extends AppCompatActivity {
    private EditText et_player_answer;
    private Button btn_enter;
    private TextView result;
    private FloatingActionButton floatingActionButton;
    private ArrayList<Integer> CorrectAnswer, PlayerAnswer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game_function);

        setFindById();
        setAnswer();
        setListener();
    }

    private void setListener() {
        btn_enter.setOnClickListener(view -> CheckAnswer(et_player_answer.getText().toString()));
        floatingActionButton.setOnClickListener(view -> finish());
    }

    private void CheckAnswer(String player_answer) {
        PlayerAnswer = new ArrayList<>();
        et_player_answer.setText("");//每次輸入完答案都把EditText清空

        String regex = ".*[a-zA-Z].*";//使用正則表達式判斷輸入的答案有沒有英文字母
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(player_answer);
        if (player_answer.length() < 4 || matcher.matches()) {
            Toast.makeText(this,"請輸入四個數字",Toast.LENGTH_SHORT).show(); return;}

        for (int i = 0; i < 4; i++) {
            PlayerAnswer.add(Character.getNumericValue(player_answer.charAt(i)));
        }//將玩家輸入的答案丟給陣列存取

        int quantityA = 0, quantityB = 0;//A跟B的數量
        for (int i = 0; i < 4; i++) {
            if (CorrectAnswer.contains(PlayerAnswer.get(i))){//先判斷輸入的答案有沒有在正確答案內
                if (CorrectAnswer.get(i).equals(PlayerAnswer.get(i))) quantityA++;//如果有就判斷當下的數字是否跟正確答案一樣,是就A+1
                else quantityB++;//反之B+1
            }
        }//判斷幾A幾B
        if (quantityA == 4){
            Toast.makeText(this,"恭喜~你猜中了!",Toast.LENGTH_SHORT).show();
            result.setText("");
            setAnswer();
        }//當A的數量為四個的時候,就展開新的一輪
        result.append("這次輸入 : "+ PlayerAnswer + ",結果為 : " + quantityA + "A" + quantityB + "B\n");
        //沒有4A的時候就正常顯示幾A幾B
    }

    private void setAnswer() {
        CorrectAnswer= new ArrayList<>();
        ArrayList<Integer> data = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            data.add(i);
        }//先建立一個0到9的陣列
        Collections.shuffle(data);//接著將陣列洗牌
        CorrectAnswer.addAll(data.subList(0,4));//將前四個數字加進正確解答
        Log.d("test", "setAnswer: "+CorrectAnswer);
    }//設定正確答案

    private void setFindById() {
        et_player_answer = findViewById(R.id.et_game);
        btn_enter = findViewById(R.id.btn_game);
        result = findViewById(R.id.game_result);
        floatingActionButton = findViewById(R.id.game_floatActionButton);
    }
}

今天的文章就到此告一個段落,下一篇會將登入畫面跟放置這些功能的介面製作出來。


上一篇
【DAY 27】 將這個月所學集合成一個APP!(佈局篇)
下一篇
【DAY 29】 將這個月所學集合成一個APP!(程式完善篇)
系列文
Android Studio開發過程和介紹30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言