1. 程式人生 > >極簡天氣app

極簡天氣app

最近在學習Android,這裡就用這個極簡天氣app練練手吧。

製作思路:

  1. UI佈局
  2. 城市選擇
  3. 對應城市天氣資料獲取與處理
  4. 重新整理UI

先看看效果吧(前面有點遲鈍):

我點選右上角的城市管理按鈕後進入城市選擇(裡面的城市是隨便寫的哦),確認後返回主介面,這裡只顯示高溫而已。

確實極簡吧,但是其中的處理過程可是一點沒有少的!是要少加布局上的修飾就瞬間高大上了。

先看佈局檔案(activity_main.xml):

@+id/activity_main_textview_city這是用於顯示我們選擇的城市的TextView。

@+id/activity_main_textview_content這是顯示天氣內容的TextView(我偷懶把所有的內容都放進去了,嘿嘿,嘿嘿)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/activity_main_button_chengshiguanli"
        android:layout_width="50dp"
        android:layout_height="25dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_marginTop="10dp"
        android:layout_marginRight="10dp"
        android:text="@string/chengshiguanli"
        android:background="@color/colorWhite"
        android:textSize="5pt"/>

    <TextView
        android:id="@+id/activity_main_textview_city"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:textSize="17pt"
        android:gravity="center"
    />

    <TextView
        android:id="@+id/activity_main_textview_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/activity_main_textview_city"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        />
</RelativeLayout>

下面是選擇城市的佈局檔案(choose_city.xml):

其中我們用到了com.wx.wheelview.widget.WheelView元件,這是一個開源的滾輪元件,使用之前需要先匯入相應的包,這裡是其專案地址和詳細的使用說明:https://github.com/venshine/WheelView

"@+id/city_btn",android:text="boom" 這個按鈕是我們做測試用的。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
   >

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.wx.wheelview.widget.WheelView
            android:id="@+id/wheel_province"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>

        <com.wx.wheelview.widget.WheelView
            android:id="@+id/wheel_city"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>

        <com.wx.wheelview.widget.WheelView
            android:id="@+id/wheel_area"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>

    </LinearLayout>

    <Button
        android:id="@+id/city_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="boom"
        android:layout_gravity="center"/>

    <Button
        android:id="@+id/makesure_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="確認"
        android:layout_gravity="center"/>
</LinearLayout>

下面是我們的MainActivity.java檔案:

負責為按鈕的監聽和重新整理UI。

這裡我們使用startActivityForResult(intent, requestcode)方法進行UI頁面的跳轉。並用onActivityResult(int requestCode, int resultCode, Intent data)方法根據對應的requestcode來接受ChooseCity.java檔案處理後含天氣資料的bundle物件,進而重新整理UI。

package com.example.administrator.myapplication_weather;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Button btn_glcs;
    private TextView text_city;
    private TextView text_content;

    private int requestcode;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        btn_glcs = (Button) findViewById(R.id.activity_main_button_chengshiguanli);
        btn_glcs.setOnClickListener(new btn_glcsListener());
        text_city = (TextView) findViewById(R.id.activity_main_textview_city);
        text_content = (TextView)findViewById(R.id.activity_main_textview_content);
    }


    class btn_glcsListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(MainActivity.this, ChooseCity.class);
            requestcode = 0;
            startActivityForResult(intent, requestcode);
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 0) {
            Bundle bundle =data.getExtras();
            text_city.setText(bundle.getString("city"));
            text_content.setText(bundle.getString("high"));

        }
    }
}

下面是ChooseCity.java檔案用來處理choosecity.xml佈局檔案對應的邏輯,其中對滾輪的配置較多,但是基本流程是一樣的。

在確認按鈕的監聽事件中有這麼一句: new GetContacts().execute();可以在下面看到這個類是繼承AsyncTask類的。這是android提供的非同步類。(因為android不允許將耗時的操作放在UI執行緒裡,所以要使用非同步類(當然新開執行緒也可以),即 

Void doInBackground(Void... voids)來在後臺下載我們的天氣資料。

package com.example.administrator.myapplication_weather;

import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.wx.wheelview.adapter.ArrayWheelAdapter;
import com.wx.wheelview.widget.WheelView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

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


public class ChooseCity extends Activity {

    public static final String action = "jason.broadcast.action";
    private int requestcode;
    private WheelView wheel_province;
    private WheelView wheel_city;
    private WheelView wheel_area;

    private Button btn_city;
    private Button btn_makesure;

    private Bundle bundle = new Bundle();
    private static  String cityurl = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.choose_city);
        initCity();
    }

    private void initCity()
    {
        wheel_province = findViewById(R.id.wheel_province);
        wheel_city = findViewById(R.id.wheel_city);
        wheel_area = findViewById(R.id.wheel_area);
        //定義WheelView的style,比如選中文字大小和其他文字大小(這裡WheelView已經封裝了)
        WheelView.WheelViewStyle style = new WheelView.WheelViewStyle();
        style.selectedTextSize = 18;
        style.textSize = 14;

        //在這裡設定一個WheelView的Adapter作為資料來源的介面卡
        wheel_province.setWheelAdapter(new ArrayWheelAdapter(this));
        //為WheelView設定一個面板風格(這裡在WheelView中已經封裝了一個Holo)
        wheel_province.setSkin(WheelView.Skin.Holo);
        //這裡將資料放入WheelView中
        wheel_province.setWheelData(createProvinceDatas());
        //設定WheelView的Style(上面已經定義)
        wheel_province.setStyle(style);

        wheel_city.setWheelAdapter(new ArrayWheelAdapter(this));
        wheel_city.setSkin(WheelView.Skin.Holo);
        //這裡就很奇妙了,我詳細說一下
        //看下面的幾個建立資料的函式,從province到city再到area,其中的返回型別中分別為List<String>,HashMap<String, List<String>>, HashMap<String, List<String>>
        //其中第一種為String列表,也就是第一個省份的列表可以直接通過String列表得到。
        //HashMap是雜湊表,他裡面的值都是通過key-value進行對應,所以在這個情況中就是一個省(String key)對應著一個市(String value)的列表(同理得到第二個市與區的關係)
        //HashMap.get(key)方法是用來通過key的值來得到value的值
        //WheelView.getSelection()通過看就知道是一個獲取位置的方法(大神在WheelView中封裝好了).
        //綜上所述,其實這條東西,逆向來讀就是,通過得到省的WheelView的位置來得到省的value值,而省的value值就是市的key值,所以說可以得到市的一整個value值。
        wheel_city.setWheelData(createCityDatas().get(createProvinceDatas().get(wheel_province.getSelection())));
        wheel_city.setStyle(style);

        //這裡是把省的WheelView與市的WheelView連線起來(封裝好的)(加入下一級的WheelView)
        wheel_province.join(wheel_city);
        //這裡是把省的WheelView與市的WheelView的資料連線起來
        wheel_province.joinDatas(createCityDatas());

        wheel_area.setWheelAdapter(new ArrayWheelAdapter(this));
        wheel_area.setSkin(WheelView.Skin.Holo);
        //這個嘛,上面解釋過了,但是又臭又長,簡單說一下
        //其實就匹配了兩次,通過得到省和市的位置來定位到他們兩個的value,再通過value得到區的value值
        wheel_area.setWheelData(createAreaDatas().get(createCityDatas().get(createProvinceDatas().get(wheel_province.getSelection())).get(wheel_city.getSelection())));
        wheel_area.setStyle(style);

        wheel_city.join(wheel_area);
        wheel_city.joinDatas(createAreaDatas());



        btn_city = (Button)findViewById(R.id.city_btn);
        btn_city.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String province = wheel_province.getSelectionItem().toString();
                String city = wheel_city.getSelectionItem().toString();
                String area = wheel_area.getSelectionItem().toString();
                Toast.makeText(ChooseCity.this, province + city + area, Toast.LENGTH_SHORT).show();
            }
        });


        btn_makesure = (Button)findViewById(R.id.makesure_btn);
        btn_makesure.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                String area = wheel_province.getSelectionItem().toString();
                cityurl = "http://wthrcdn.etouch.cn/weather_mini?city="+area;
                Log.d("DATA",cityurl);

                new GetContacts().execute();

            }
        });
    }

    //這裡是第一級,所以直接把他放入一個List中就可以了
    private List<String> createProvinceDatas() {
        String[] strings = {"黑龍江", "吉林", "北京"};
        //將字元陣列轉換為List形式
        return Arrays.asList(strings);
    }

    private HashMap<String, List<String>> createCityDatas() {
        //新建一個雜湊表
        HashMap<String, List<String>> map = new HashMap<String, List<String>>();
        String[] strings = {"黑龍江", "吉林","北京"};
        String[] s1 = {"哈爾濱", "齊齊哈爾", "大慶"};
        String[] s2 = {"長春", "吉林"};
        String[] s3 = {"瀋陽", "大連", "鞍山", "撫順"};
        String[][] ss = {s1, s2, s3};
        for (int i = 0; i < strings.length; i++) {
            //在這裡把key與value分別列出,然後通過HashMap.put進行配對然後寫入雜湊表。
            map.put(strings[i], Arrays.asList(ss[i]));
        }
        // 一個雜湊表的輸出檢測(自學雜湊表時測試一下用的,自己也可以試試)
//        Iterator iter = map.entrySet().iterator();
//        while(iter.hasNext()) {
//            Map.Entry entry = (Map.Entry) iter.next();
//            Object key = entry.getKey();
//            Object value = entry.getValue();
//            Log.v("second",key + ":" + value);
//        }
        return map;
    }

    private HashMap<String, List<String>> createAreaDatas() {
        HashMap<String, List<String>> map = new HashMap<String, List<String>>();
        String[] strings = {"哈爾濱", "齊齊哈爾", "大慶", "長春", "吉林", "瀋陽", "大連", "鞍山", "撫順"};
        String[] s1 = {"道里區", "道外區", "南崗區", "香坊區"};
        String[] s2 = {"龍沙區", "建華區", "鐵鋒區"};
        String[] s3 = {"紅崗區", "大同區"};
        String[] s11 = {"南關區", "朝陽區"};
        String[] s12 = {"龍潭區"};
        String[] s21 = {"和平區", "皇姑區", "大東區", "鐵西區"};
        String[] s22 = {"中山區", "金州區"};
        String[] s23 = {"鐵東區", "鐵西區"};
        String[] s24 = {"新撫區", "望花區", "順城區"};
        String[][] ss = {s1, s2, s3, s11, s12, s21, s22, s23, s24};
        for (int i = 0; i < strings.length; i++) {
            map.put(strings[i], Arrays.asList(ss[i]));
        }
//        Iterator iter = map.entrySet().iterator();
//        while(iter.hasNext()) {
//            Map.Entry entry = (Map.Entry) iter.next();
//            Object key = entry.getKey();
//            Object value = entry.getValue();
//            Log.v("first",key + ":" + value);
//        }
        return map;
    }

        public Bundle jsonParse(String cityjsondata) throws JSONException {
            Bundle jsonbundle = new Bundle();
            JSONObject reader = new JSONObject(cityjsondata);
            JSONObject weatherdata  = reader.getJSONObject("data");
            JSONArray forecast = weatherdata.getJSONArray("forecast");
            JSONObject todayweather = (JSONObject) forecast.get(0);

            Log.d("DATA",todayweather.getString("low"));
            Log.d("DATA",todayweather.getString("fengli"));
            Log.d("DATA",todayweather.getString("fengxiang"));
            Log.d("DATA",todayweather.getString("type"));

            jsonbundle.putString("high",todayweather.getString("high"));
            jsonbundle.putString("low",todayweather.getString("low"));
            jsonbundle.putString("fengli",todayweather.getString("fengli"));
            jsonbundle.putString("fengxiang",todayweather.getString("fengxiang"));
            jsonbundle.putString("type",todayweather.getString("type"));
            return jsonbundle;
        }

    private class GetContacts extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.d("DATA","Json Data is downloading");
            Toast.makeText(ChooseCity.this,"Json Data is downloading",Toast.LENGTH_LONG).show();
        }
        @Override
        protected Void doInBackground(Void... voids) {

            Log.d("DATA","Deal with data");
            GetCityJsonData getdata = new GetCityJsonData();
            String jsoncontent = getdata.GetDataContent(cityurl);
            try {
                    bundle = jsonParse(jsoncontent);
            } catch (JSONException e) {
                Log.d("DATA","ERROR in doInBackground");
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {

            super.onPostExecute(result);

            String area = wheel_province.getSelectionItem().toString();

            bundle.putString("city",area);
            bundle.putString("joke","haha");

            Log.d("DATA","POST!");
            Log.d("DATA",bundle.getString("high"));
            Log.d("DATA","POST2");

            Intent intent = new Intent(ChooseCity.this,MainActivity.class);
            intent.putExtras(bundle);
            requestcode = 0;
            setResult(requestcode,intent);
            finish();
        }
    }

}

GetDataContent.java檔案:用來根據URL來獲取對應的Json檔案,並以String的格式返回
:

package com.example.administrator.myapplication_weather;

import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

 public class GetCityJsonData {

    private String url="";

    public GetCityJsonData()
    {

    }

    public String GetDataContent(String url)
    {
        String response = null;
        setUrl(url);
        Log.d("DATA","download the data...");

        try {
            URL Url = new URL(getUrl());
            HttpURLConnection conn = (HttpURLConnection) Url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(8000);
            conn.setReadTimeout(8000);

            InputStream in = new BufferedInputStream(conn.getInputStream());
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            StringBuffer sb = new StringBuffer();
            String str;
            while((str = reader.readLine())!=null)
            {
                sb.append(str);
            }
            response = sb.toString();
            Log.d("GetData",response);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return response;
    }

    private String getUrl()
    {
        return url;
    }

    private void setUrl(String url)
    {
        this.url = url;
    }

}

對應的配置檔案:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.administrator.myapplication_weather">
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ChooseCity"
            android:exported="true"/>
    </application>

</manifest>

到此我的極簡天氣App就結束了。

雖然簡陋,但是我們學習了以下內容:

  • Android單執行緒模型
  • 兩個Activity之間傳遞資料
  • 非同步類AsyncTask
  • 資料結構Map
  • Json格式解析
  • 多級聯動的滾輪