第一个安卓应用开发记录

一、项目框架的搭建

1、在git上新建一个coolweather的仓库
2、新建一个Coolweather项目,添加到git版本管理
3、在java中新建几个包,db:用于存放数据库模型相关的代码,gson:用于存放GSon相关的代码,service:用于存放服务相关的代码,util包用于存放工具相关代码
4、添加项目依赖的第三方,在app/build.gradle
依赖:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation 'org.litepal.android:core:2.0.0'
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.github.bumptech.glide:glide:4.7.1'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

仓库添加

allprojects {
    repositories {
        mavenCentral()
        google()
        jcenter()
    }
}

说明:
implementation 'org.litepal.android:core:2.0.0'是数据库操作的框架
implementation 'com.squareup.okhttp3:okhttp:3.10.0'网络请求框架
implementation 'com.google.code.gson:gson:2.8.5' JSON解析框架
implementation 'com.github.bumptech.glide:glide:4.7.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'为加载图片的框架

二、数据库表的设计

1、数据获取
省份获取接口:
http://guolin.tech/api/china
获取的数据如下:
[{"id":1,"name":"北京"},{"id":2,"name":"上海"}]
省下面对应的市获取接口
http://guolin.tech/api/china/22,其中22为省份的id,数据结构如下:
[{"id":191,"name":"长沙"},{"id":192,"name":"湘潭"},{"id":193,"name":"株洲"},{"id":194,"name":"衡阳"},{"id":195,"name":"郴州"},{"id":196,"name":"常德"},{"id":197,"name":"益阳"},{"id":198,"name":"娄底"},{"id":199,"name":"邵阳"},{"id":200,"name":"岳阳"},{"id":201,"name":"张家界"},{"id":202,"name":"怀化"},{"id":203,"name":"永州"},{"id":204,"name":"吉首"}]
获取区对应的信息
http://guolin.tech/api/china/22/191,其中191为市的id
[{"id":1422,"name":"长沙","weather_id":"CN101250101"},{"id":1423,"name":"宁乡","weather_id":"CN101250102"},{"id":1424,"name":"浏阳","weather_id":"CN101250103"},{"id":1425,"name":"马坡岭","weather_id":"CN101250104"},{"id":1426,"name":"望城","weather_id":"CN101250105"}]
最后通过weather_id 获取天气信息,使用和风天气的api
https://console.heweather.com/register?role=personal,注册免费

天气信息的获取
https://free-api.heweather.com/s6/weather/now?location=CN101250101&key=22c4dc1c0a3245c7a97e7c2543b9781a
数据如下:
{"HeWeather6":[{"basic":{"cid":"CN101250101","location":"长沙","parent_city":"长沙","admin_area":"湖南","cnty":"中国","lat":"28.19408989","lon":"112.98227692","tz":"+8.00"},"update":{"loc":"2018-07-06 16:49","utc":"2018-07-06 08:49"},"status":"ok","now":{"cloud":"75","cond_code":"104","cond_txt":"阴","fl":"31","hum":"85","pcpn":"0.0","pres":"1000","tmp":"29","vis":"13","wind_deg":"293","wind_dir":"西北风","wind_sc":"3","wind_spd":"19"}}]}

2、数据库表实现
新建Province类 继承LitePalSupport

public class Province extends LitePalSupport {
    private int id;
    private String provinceName;
    private int provinceCode;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProvinceName() {
        return provinceName;
    }

    public void setProvinceName(String provinceName) {
        this.provinceName = provinceName;
    }

    public int getProvinceCode() {
        return provinceCode;
    }

    public void setProvinceCode(int provinceCode) {
        this.provinceCode = provinceCode;
    }
}

City类

public class City extends LitePalSupport {
    private  int id;
    private String cityName;
    private int cityCode;
    private  int provinceId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public int getCityCode() {
        return cityCode;
    }

    public void setCityCode(int cityCode) {
        this.cityCode = cityCode;
    }

    public int getProvinceId() {
        return provinceId;
    }

    public void setProvinceId(int provinceId) {
        this.provinceId = provinceId;
    }
}

Country类

public class Country extends LitePalSupport {
    private int id;
    private String countryName;
    private String weatherId;
    private int cityId;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCountryName() {
        return countryName;
    }

    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }

    public String getWeatherId() {
        return weatherId;
    }

    public void setWeatherId(String weatherId) {
        this.weatherId = weatherId;
    }

    public int getCityId() {
        return cityId;
    }

    public void setCityId(int cityId) {
        this.cityId = cityId;
    }
}

在app/src/main 目录在新建assets 文件,在改目录下新建一个litepal.xml文件,编辑内容如下:

<?xml version="1.0" encoding="utf-8"?>

<litepal>
    <!--
        Define the database name of your application.
        By default each database name should be end with .db.
        If you didn't name your database end with .db,
        LitePal would plus the suffix automatically for you.
        For example:
        <dbname value="demo" />
    -->
    <dbname value="cool_weather" />

    <!--
        Define the version of your database. Each time you want
        to upgrade your database, the version tag would helps.
        Modify the models you defined in the mapping tag, and just
        make the version value plus one, the upgrade of database
        will be processed automatically without concern.
            For example:
        <version value="1" />
    -->
    <version value="1" />


    <list>
        <mapping class="com.example.yangjie.coolweather.db.Province" />
        <mapping class="com.example.yangjie.coolweather.db.City" />
        <mapping class="com.example.yangjie.coolweather.db.Country" />
    </list>

    <!--
        Define where the .db file should be. "internal" means the .db file
        will be stored in the database folder of internal storage which no
        one can access. "external" means the .db file will be stored in the
        path to the directory on the primary external storage device where
        the application can place persistent files it owns which everyone
        can access. "internal" will act as default.
        For example:
        <storage value="external" />
    -->

</litepal>

最后需要在配置一下LitePalApplication,修改如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yangjie.coolweather">

    <application
        android:name="org.litepal.LitePalApplication"
        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>
    </application>

</manifest>

3、自定义Fragment

首先定义一个线性布局如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#fff"
    >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        >

        <TextView
            android:id="@+id/title_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="#fff"
            android:textSize="20sp"
            android:text="标题"
            />

        <Button
            android:id="@+id/btn_back"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:background="@drawable/btn_back"

            />
    </RelativeLayout>

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</LinearLayout>

这里自定义了导航条,所以需要修改style.xml文件,修改如下:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

新建一个ChooseAreaFragment继承Fragment,代码如下:

public class ChooseAreaFragment extends Fragment {
    public static final  int LEVEL_PROVINCE = 0;
    public static final  int LEVEL_CITY = 1;
    public static final  int LEVEL_COUNTRY = 2;
    private static final  String TYPE_PROVINCE = "province";
    private static final  String TYPE_City = "city";
    private static final  String TYPE_Country = "country";


    private ProgressDialog progressDialog;
    private TextView titleText;
    private Button backButton;
    private ListView listView;
    private ArrayAdapter<String> adapter;
    private List<String> dataList = new ArrayList<>();

    private List<Province> provinceList;
    private List<City> cityList;
    private List<Country> countryList;

    private Province selectedProvince;
    private City selectedCity;
    private int currentLevel;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.choose_area,container,false);
        titleText = (TextView)view.findViewById(R.id.title_text);
        backButton = (Button)view.findViewById(R.id.btn_back);
        listView = (ListView)view.findViewById(R.id.listView);

        adapter = new ArrayAdapter<>(getContext(),android.R.layout.simple_list_item_1,dataList);
        listView.setAdapter(adapter);
        return view;

    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                if (currentLevel == LEVEL_PROVINCE){
                    selectedProvince = provinceList.get(i);
                    queryCities();
                }else if (currentLevel == LEVEL_CITY){
                    selectedCity = cityList.get(i);
                    queryCounties();
                }
            }
        });

        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (currentLevel == LEVEL_COUNTRY){
                    queryCities();
                }else if (currentLevel == LEVEL_CITY){
                    queryProinces();
                }
            }


        });

        queryProinces();
    }

    private void queryCounties() {

        titleText.setText(selectedCity.getCityName());
        backButton.setVisibility(View.VISIBLE);
        countryList = LitePal.where("cityid = ?",String.valueOf(selectedCity.getId())).find(Country.class);
        if (countryList.size()>0){
            dataList.clear();
            for (Country country:countryList){
                dataList.add(country.getCountryName());
            }
            adapter.notifyDataSetChanged();;
            listView.setSelection(0);
            currentLevel = LEVEL_COUNTRY;
        }else {

            int provinceCode = selectedProvince.getProvinceCode();
            int cityCode = selectedCity.getCityCode();
            String address = "http://guolin.tech/api/china/" + provinceCode + "/" + cityCode;
            queryFromServer(address,TYPE_Country);
        }
    }

    private void queryCities() {
        titleText.setText(selectedProvince.getProvinceName());
        backButton.setVisibility(View.VISIBLE);
        cityList = LitePal.where("provinceid = ?",String.valueOf(selectedProvince.getId())).find(City.class);
        if (cityList.size()>0){
            dataList.clear();
            for (City city:cityList){
                dataList.add(city.getCityName());
            }
            adapter.notifyDataSetChanged();;
            listView.setSelection(0);
            currentLevel = LEVEL_CITY;
        }else {

            int provinceCode = selectedProvince.getProvinceCode();
            String address = "http://guolin.tech/api/china/" + provinceCode;
            queryFromServer(address,TYPE_City);
        }
    }
    private void queryProinces() {
        titleText.setText("中国");
        backButton.setVisibility(View.GONE);
        provinceList = LitePal.findAll(Province.class);
        if (provinceList.size()>0){
            dataList.clear();
            for (Province province:provinceList){
                dataList.add(province.getProvinceName());
            }
            adapter.notifyDataSetChanged();
            listView.setSelection(0);
            currentLevel = LEVEL_PROVINCE;
        }else {
            String address = "http://guolin.tech/api/china";
            queryFromServer(address,TYPE_PROVINCE);
        }
    }

    private void queryFromServer(String address,final String type){
        showProgressDialog();

        HttpUtil.sendHttpRequest(address, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
               getActivity().runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       closeProgressDialog();
                       Toast.makeText(getContext(),"加载失败",Toast.LENGTH_SHORT).show();;
                   }
               });
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //在这里解析数据
                String responseText = response.body().string();

                Log.d(TAG, "onResponse: " + responseText);
                boolean result = false;
                if (type.equals(TYPE_PROVINCE)){
                    result = Utility.handleProvinceResponse(responseText);
                }else if(type.equals(TYPE_City)){
                    result = Utility.handleCityResponse(responseText,selectedProvince.getId());
                }else if(type.equals(TYPE_Country)){
                    result = Utility.handleCountryResponse(responseText,selectedCity.getId());
                }

                if (result){
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            closeProgressDialog();
                            if (type.equals(TYPE_PROVINCE)){
                                queryProinces();
                            }else if(type.equals(TYPE_City)){
                                queryCities();
                            }else if(type.equals(TYPE_Country)){
                                queryCounties();
                            }
                        }
                    });
                }
            }
        });
    }


    private void showProgressDialog(){
        if (progressDialog == null){
            progressDialog = new ProgressDialog(getActivity());
            progressDialog.setMessage("正在加载");
            progressDialog.setCanceledOnTouchOutside(false);
        }
        progressDialog.show();
    }

    private void closeProgressDialog(){
        if (progressDialog!=null){
            progressDialog.dismiss();
        }
    }
}

在activity_main.xml文件中添加Fragment布局如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/choose_area_fragment"
        android:name="com.example.yangjie.coolweather.ChooseAreaFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</FrameLayout>

三、展示天气数据

效果如下:


780FA754-69A3-440F-AFC3-297954E7F328.png

将上图的界面拆分成三个布局:1、导航部分(展示城市和更新时间)
2、现在天气信息 3、未来三天信息展示
1、导航部分,新建一个title.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/relativeLayout">


    <Button
        android:id="@+id/nav_button"
        android:layout_width="32dp"
        android:layout_height="30dp"
        android:background="@drawable/home"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title_city"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="180dp"
        android:layout_marginStart="180dp"
        android:layout_marginTop="8dp"
        android:text="北京"
        android:textColor="#fff"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <TextView
        android:id="@+id/title_update_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:textColor="#fff"
        android:textSize="16sp"
        android:text="2018-7-10"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
    
</android.support.constraint.ConstraintLayout>

2、天气信息部分

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="15dp"
    >

    <TextView
        android:id="@+id/tmp_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:textColor="#fff"
        android:textSize="60sp"
        />

    <TextView
        android:id="@+id/weather_info_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:textSize="20sp"
        android:textColor="#fff"
        />

</LinearLayout>

3、未来天气布局forcast.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="15dp"
    android:background="#8000"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="15dp"
        android:textColor="#fff"
        android:text="预报"
        android:textSize="20sp"
        />

    <LinearLayout
        android:id="@+id/forecast_layout"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
    </LinearLayout>


</LinearLayout>

forecast_item.xml文件

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

    <TextView
        android:id="@+id/info_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:textColor="#fff"
        />

    <TextView
        android:id="@+id/tem_min"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:textColor="#fff"
        />


    <TextView
        android:id="@+id/tmp_max"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:textColor="#fff"
        />

    <TextView
        android:id="@+id/date_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="2"
        android:textColor="#fff"
        />

</LinearLayout>

四、生成APK文件

https://blog.csdn.net/wyg1230/article/details/77529465/

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 128,732评论 19 550
  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 78,685评论 25 511
  • 第十三章主要是编写了一个功能完善的天气预报程序 一、功能需求分析   在开始编码之前,我们先对程序进行需求分析,首...
    radish520like阅读 311评论 0 0
  • 【1分钟AI】 1、全国首创无人机流动诊所正式投用 2、搜狗推出「唇语识别」技术 垂直场景下90%正确率 3、工信...
    Yetta000阅读 118评论 0 0
  • 聚集智慧, 聚集热诚, 聚集决心, 聚集资源, 只为结果的呈现, 早安[太阳][太阳][太阳]
    奔波男生阅读 19评论 0 0