最近在学习Android,这里就用这个极简天气app练练手吧。
虽然简陋,但是我们将学习以下内容:
- Android单线程模型
- 两个Activity之间传递数据
- 异步类AsyncTask
- 数据结构Map
- Json格式解析
- 多级联动的滚轮
制作思路:
- UI布局
- 城市选择
- 对应城市天气数据获取与处理
- 刷新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就结束了。