上一篇:Android 天气APP(十二)空气质量、UI优化调整

天气预报详情,逐小时预报详情

  • 新版-------------------
    • 一、适配器点击监听
    • 二、页面实现
    • 三、天气预报详情弹窗
    • 四、逐小时天气预报详情弹窗
    • 五、Item点击效果
    • 六、文章源码
  • 旧版-------------------
    • 前言
    • 正文
      • 弹窗
      • 切换背景
        • 逻辑思路
      • 图片列表
      • 手动定义

新版-------------------

  在上一篇文章中增加了空气质量数据的显示,这篇文章中,将会添加天气预报详情数据的显示,这样我们可以知道更多的天气预报信息。

一、适配器点击监听

  在之前的文章中,我们完成了天气预报和逐小时天气的数据显示,两个数据都是以列表的形式显示的,那么这里我们查看详情的话就可以通过点击列表的item来触发,首先我们需要让列表能够监听点击事件。

在adapter包下新建一个OnClickItemCallback接口,代码如下:

public interface OnClickItemCallback {/*** item点击* @param position 点击位置*/void onItemClick(int position);
}

这里很简单,就一个方法,下面我们先进入到DailyAdapter中,新增如下代码:

 private OnClickItemCallback onClickItemCallback;public void setOnClickItemCallback(OnClickItemCallback onClickItemCallback) {this.onClickItemCallback = onClickItemCallback;}

  这个代码就很简单,初始化,然后设置监听,下面就是列表item视图的点击出来接口的回调,修改onCreateViewHolder()方法,代码如下所示:

    @NonNull@Overridepublic ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {ItemDailyRvBinding binding = ItemDailyRvBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);ViewHolder holder = new ViewHolder(binding);//添加点击回调binding.getRoot().setOnClickListener(v -> {if (onClickItemCallback != null) {onClickItemCallback.onItemClick(holder.getAdapterPosition());}});return holder;}

  这样我们就添加了天气预报列表的item视图点击监听,这里我只写了天气列表的点击监听,你试试看能不能一葫芦画瓢为逐小时天气适配器也添加一个点击监听,当然了实现不了也没有关系,可以查看本篇文章所对应的源码,然后在MainActivity中进行实现。

二、页面实现

在MainActivity中的initView()方法中添加如下代码:

        dailyAdapter.setOnClickItemCallback(new OnClickItemCallback() {@Overridepublic void onItemClick(int position) {}});

添加位置如下图所示:

  这样我们需要在触发点击的时候做什么?要显示详情数据,怎么显示呢?可以通过一个底部弹窗来做,那么首先需要有一个弹窗视图。

三、天气预报详情弹窗

在layout下新建一个dialog_daily_detail.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:layout_width="match_parent"android:paddingBottom="@dimen/dp_16"android:layout_height="match_parent"><com.google.android.material.appbar.MaterialToolbarandroid:id="@+id/toolbar_daily"android:layout_width="409dp"android:layout_height="wrap_content"android:background="@color/white"android:minHeight="?attr/actionBarSize"android:theme="?attr/actionBarTheme"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"><ImageViewandroid:id="@+id/iv_close"android:layout_gravity="end"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"app:srcCompat="@drawable/ic_round_close_24" /></com.google.android.material.appbar.MaterialToolbar><TextViewandroid:id="@+id/tv_tmp_max_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="16dp"android:layout_weight="1"android:text="最高温"android:textColor="@color/black"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/toolbar_daily" /><!--最高温--><TextViewandroid:id="@+id/tv_tmp_max"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginEnd="16dp"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="@+id/guideline4"app:layout_constraintTop_toTopOf="@+id/tv_tmp_max_title" /><TextViewandroid:id="@+id/tv_tmp_min_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="最低温"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_tmp_max_title" /><!--最低温--><TextViewandroid:id="@+id/tv_tmp_min"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_tmp_min_title" /><TextViewandroid:id="@+id/tv_uv_index_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="紫外线强度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_tmp_min_title" /><!--紫外线强度--><TextViewandroid:id="@+id/tv_uv_index"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_uv_index_title" /><TextViewandroid:id="@+id/tv_cond_txt_d_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="白天天气状况"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_uv_index_title" /><!--白天天气状况--><TextViewandroid:id="@+id/tv_cond_txt_d"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_cond_txt_d_title" /><TextViewandroid:id="@+id/tv_cond_txt_n_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="晚上天气状况"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_cond_txt_d_title" /><!--晚上天气状况--><TextViewandroid:id="@+id/tv_cond_txt_n"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_cond_txt_n_title" /><TextViewandroid:id="@+id/tv_wind_deg_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风向360角度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_cond_txt_n_title" /><!--风向360角度--><TextViewandroid:id="@+id/tv_wind_deg"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_wind_deg_title" /><TextViewandroid:id="@+id/tv_wind_dir_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风向"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_deg_title" /><!--风向--><TextViewandroid:id="@+id/tv_wind_dir"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_wind_dir_title" /><TextViewandroid:id="@+id/tv_wind_sc_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风力"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_wind_dir_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_dir_title" /><!--风力--><TextViewandroid:id="@+id/tv_wind_sc"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_wind_sc_title" /><TextViewandroid:id="@+id/tv_wind_spd_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风速"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_sc_title" /><!--风速--><TextViewandroid:id="@+id/tv_wind_spd"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_wind_spd_title" /><TextViewandroid:id="@+id/tv_cloud_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="云量"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_spd_title" /><!--云量--><TextViewandroid:id="@+id/tv_cloud"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_cloud_title" /><TextViewandroid:id="@+id/tv_hum_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="相对湿度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_cloud_title" /><!--相对湿度--><TextViewandroid:id="@+id/tv_hum"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_hum_title" /><TextViewandroid:id="@+id/tv_pres_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="大气压强"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_hum_title" /><!--大气压强--><TextViewandroid:id="@+id/tv_pres"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_pres_title" /><TextViewandroid:id="@+id/tv_pcpn_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="降水量"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_pres_title" /><!--降水量--><TextViewandroid:id="@+id/tv_pcpn"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_pcpn_title" /><TextViewandroid:id="@+id/tv_vis_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="能见度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max_title"app:layout_constraintTop_toBottomOf="@+id/tv_pcpn_title" /><!--能见度--><TextViewandroid:id="@+id/tv_vis"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp_max"app:layout_constraintStart_toStartOf="@+id/tv_tmp_max"app:layout_constraintTop_toTopOf="@+id/tv_vis_title" /><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/guideline4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"app:layout_constraintGuide_begin="164dp" /></androidx.constraintlayout.widget.ConstraintLayout>

预览如图所示:

  总体来说是比较简单的布局了,下面我们可以在MainActivity中写一个显示天气预报详情弹窗的方法,代码如下所示:

    private void showDailyDetailDialog(DailyResponse.DailyBean dailyBean) {BottomSheetDialog dialog = new BottomSheetDialog(MainActivity.this);DialogDailyDetailBinding detailBinding = DialogDailyDetailBinding.inflate(LayoutInflater.from(MainActivity.this), null, false);//关闭弹窗detailBinding.ivClose.setOnClickListener(v -> dialog.dismiss());//设置数据显示detailBinding.toolbarDaily.setTitle(String.format("%s   %s", dailyBean.getFxDate(), EasyDate.getWeek(dailyBean.getFxDate())));detailBinding.toolbarDaily.setSubtitle("天气预报详情");detailBinding.tvTmpMax.setText(String.format("%s℃", dailyBean.getTempMax()));detailBinding.tvTmpMin.setText(String.format("%s℃", dailyBean.getTempMin()));detailBinding.tvUvIndex.setText(dailyBean.getUvIndex());detailBinding.tvCondTxtD.setText(dailyBean.getTextDay());detailBinding.tvCondTxtN.setText(dailyBean.getTextNight());detailBinding.tvWindDeg.setText(String.format("%s°", dailyBean.getWind360Day()));detailBinding.tvWindDir.setText(dailyBean.getWindDirDay());detailBinding.tvWindSc.setText(String.format("%s级", dailyBean.getWindScaleDay()));detailBinding.tvWindSpd.setText(String.format("%s公里/小时", dailyBean.getWindSpeedDay()));detailBinding.tvCloud.setText(String.format("%s%%", dailyBean.getCloud()));detailBinding.tvHum.setText(String.format("%s%%", dailyBean.getHumidity()));detailBinding.tvPres.setText(String.format("%shPa", dailyBean.getPressure()));detailBinding.tvPcpn.setText(String.format("%smm", dailyBean.getPrecip()));detailBinding.tvVis.setText(String.format("%skm", dailyBean.getVis()));dialog.setContentView(detailBinding.getRoot());dialog.show();}

  这个弹窗其实和之前的城市弹窗的使用方式相似,只不过这个更简单,可以说没有逻辑,就是显示数据就行了,那么我们下一步就是在点击的时候显示这个弹窗了,回到initView()方法,修改之前的item监听事件,代码如下所示:

dailyAdapter.setOnClickItemCallback(position -> showDailyDetailDialog(dailyBeanList.get(position)));

应该是能理解的吧,下面运行一下。

下面我们写逐小时天气预报详情弹窗。

四、逐小时天气预报详情弹窗

在layout下创建一个dialog_hourly_detail.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:layout_width="match_parent"android:paddingBottom="@dimen/dp_16"android:layout_height="match_parent"><com.google.android.material.appbar.MaterialToolbarandroid:id="@+id/toolbar_hourly"android:layout_width="409dp"android:layout_height="wrap_content"android:background="@color/white"android:minHeight="?attr/actionBarSize"android:theme="?attr/actionBarTheme"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"><ImageViewandroid:id="@+id/iv_close"android:layout_gravity="end"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="16dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"app:srcCompat="@drawable/ic_round_close_24" /></com.google.android.material.appbar.MaterialToolbar><TextViewandroid:id="@+id/tv_tmp_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginTop="16dp"android:layout_weight="1"android:text="温度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/toolbar_hourly" /><!--最高温--><TextViewandroid:id="@+id/tv_tmp"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginEnd="16dp"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="@+id/guideline4"app:layout_constraintTop_toTopOf="@+id/tv_tmp_title" /><TextViewandroid:id="@+id/tv_cond_txt_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="天气状况"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_tmp_title" /><!--白天天气状况--><TextViewandroid:id="@+id/tv_cond_txt"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_cond_txt_title" /><TextViewandroid:id="@+id/tv_wind_deg_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风向360角度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_cond_txt_title" /><!--风向360角度--><TextViewandroid:id="@+id/tv_wind_deg"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_wind_deg_title" /><TextViewandroid:id="@+id/tv_wind_dir_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风向"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_deg_title" /><!--风向--><TextViewandroid:id="@+id/tv_wind_dir"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_wind_dir_title" /><TextViewandroid:id="@+id/tv_wind_sc_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风力"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_wind_dir_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_dir_title" /><!--风力--><TextViewandroid:id="@+id/tv_wind_sc"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_wind_sc_title" /><TextViewandroid:id="@+id/tv_wind_spd_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="风速"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_sc_title" /><!--风速--><TextViewandroid:id="@+id/tv_wind_spd"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_wind_spd_title" /><TextViewandroid:id="@+id/tv_cloud_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="云量"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_wind_spd_title" /><!--云量--><TextViewandroid:id="@+id/tv_cloud"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_cloud_title" /><TextViewandroid:id="@+id/tv_hum_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="相对湿度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_cloud_title" /><!--相对湿度--><TextViewandroid:id="@+id/tv_hum"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_hum_title" /><TextViewandroid:id="@+id/tv_pres_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="大气压强"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_hum_title" /><!--大气压强--><TextViewandroid:id="@+id/tv_pres"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_pres_title" /><TextViewandroid:id="@+id/tv_pop_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="降水概率"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_pres_title" /><!--降水概率--><TextViewandroid:id="@+id/tv_pop"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_pop_title" /><TextViewandroid:id="@+id/tv_dew_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:layout_weight="1"android:text="露点温度"android:textColor="@color/black"app:layout_constraintStart_toStartOf="@+id/tv_tmp_title"app:layout_constraintTop_toBottomOf="@+id/tv_pop_title" /><!--露点温度--><TextViewandroid:id="@+id/tv_dew"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:textColor="@color/black"app:layout_constraintEnd_toEndOf="@+id/tv_tmp"app:layout_constraintStart_toStartOf="@+id/tv_tmp"app:layout_constraintTop_toTopOf="@+id/tv_dew_title" /><androidx.constraintlayout.widget.Guidelineandroid:id="@+id/guideline4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"app:layout_constraintGuide_begin="164dp" /></androidx.constraintlayout.widget.ConstraintLayout>

其实和之前的天气弹窗很像,数据会少一点。

下面在MainActivity中增加弹窗显示方法,代码如下所示:

    private void showHourlyDetailDialog(HourlyResponse.HourlyBean hourlyBean) {BottomSheetDialog dialog = new BottomSheetDialog(MainActivity.this);DialogHourlyDetailBinding detailBinding = DialogHourlyDetailBinding.inflate(LayoutInflater.from(MainActivity.this), null, false);//关闭弹窗detailBinding.ivClose.setOnClickListener(v -> dialog.dismiss());//设置数据显示String time = EasyDate.updateTime(hourlyBean.getFxTime());detailBinding.toolbarHourly.setTitle(EasyDate.showTimeInfo(time) + time);detailBinding.toolbarHourly.setSubtitle("逐小时预报详情");detailBinding.tvTmp.setText(String.format("%s℃", hourlyBean.getTemp()));detailBinding.tvCondTxt.setText(hourlyBean.getText());detailBinding.tvWindDeg.setText(String.format("%s°", hourlyBean.getWind360()));detailBinding.tvWindDir.setText(hourlyBean.getWindDir());detailBinding.tvWindSc.setText(String.format("%s级", hourlyBean.getWindScale()));detailBinding.tvWindSpd.setText(String.format("公里/小时%s", hourlyBean.getWindSpeed()));detailBinding.tvHum.setText(String.format("%s%%", hourlyBean.getHumidity()));detailBinding.tvPres.setText(String.format("%shPa", hourlyBean.getPressure()));detailBinding.tvPop.setText(String.format("%s%%", hourlyBean.getPop()));detailBinding.tvDew.setText(String.format("%s℃", hourlyBean.getDew()));detailBinding.tvCloud.setText(String.format("%s%%", hourlyBean.getCloud()));dialog.setContentView(detailBinding.getRoot());dialog.show();}

最后在initView中添加如下代码:

hourlyAdapter.setOnClickItemCallback(position -> showHourlyDetailDialog(hourlyBeanList.get(position)));

  如果你发现你的setOnClickItemCallback爆红,那就是你没有为适配器添加点击事件,再往上面看看或者看源码都行。

下面运行一下。

五、Item点击效果

  我们现在的item点击时候没有效果,有时候你就感觉好像没有点到一样,下面修改item_daily_rv.xml,在根布局中添加一行代码:

android:background="?attr/selectableItemBackground"

添加位置如下图所示:


item_hourly_rv.xml也这样添加,然后我们再运行一下看看效果。

  看看这个点击的效果,你会发现天气预报列表的效果不是那么理想,这是布局造成的,我们的列表设置了内填充,那么为了是这个点击效果能够占满屏幕,就需要将内边距放在item里面。

在activity_main.xml中将左右内填充移除,如下图所示。

在item_daily_rv.xml中添加,如下图所示:

下面再运行可以了。

六、文章源码

欢迎 StarFork

第十三篇文章源码地址: GoodWeather-New-13

旧版-------------------

前言

自定义背景,做这个功能的原因是因为一些人觉得必应的每日一图并不好看,想要手动上传自己手机里的壁纸作为背景,并且应用也要有自带的壁纸供用户选择。正所谓有需就有求,这是亘古不变的道理,第三个就是UI的优化,这次我是打算把切换城市的弹窗挪到二级菜单里面,右上角做一个一级菜单列表,这个列表暂定功能为切换城市和切换背景,这样做也是符合大众APP的审美,比如微信、支付宝、QQ之类的。

正文

不知道你们是不是被这个仿微信弹窗的内容吸引进来的,是的话,你就来对地方了。我不是标题党,我是踏踏实实的程序员,不搞那些花里胡哨的文章标题,实事求是,我觉那种标题吸引别人进来看,结果什么有用的知识都没有,得浪费别人的时间等同于诈骗。

弹窗

首先在项目的layout布局文件中创建一个弹窗的布局
window_add.xml,弹窗的背景图片是一个.9可拉伸的png图片,


一黑一白,想用自取即可,或者可以去GitHub上面源码里去取图。
在上一篇文章中,在mvplibrary中的res文件下新建了colors.xml,并在里面新增几个颜色进去,所以为了更好的管理项目中的颜色,后续的颜色都会写在这里,其他页面通过@color/black来调用即可,

然后再创建一个dimen.xml文件,这个里面主要防止尺寸大小和字体大小

调用方式通过@dimen/dp_10或者@dimen/sp_10
因为下面的布局文件中会涉及到这两个xml里面的内容,所以我这里会说的比较清楚。
colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><color name="arc_bg_color">#C6D7F4</color><color name="arc_progress_color">#FBFEF7</color><color name="white">#ffffff</color><!--白色--><color name="black">#000000</color><!--黑色--><color name="blue_one">#9FC8E9</color><!--浅蓝色--><color name="transparent">#00000000</color><!--透明--><color name="transparent_bg">#22000000</color><!--半透明-->
</resources>

dimen.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><!--尺寸--><dimen name="dp_0">0dp</dimen><dimen name="dp_0_1">0.1dp</dimen><dimen name="dp_0_5">0.5dp</dimen><dimen name="dp_1">1dp</dimen><dimen name="dp_1_5">1.5dp</dimen><dimen name="dp_2">2dp</dimen><dimen name="dp_2_5">2.5dp</dimen><dimen name="dp_3">3dp</dimen><dimen name="dp_3_5">3.5dp</dimen><dimen name="dp_4">4dp</dimen><dimen name="dp_4_5">4.5dp</dimen><dimen name="dp_5">5dp</dimen><dimen name="dp_6">6dp</dimen><dimen name="dp_7">7dp</dimen><dimen name="dp_8">8dp</dimen><dimen name="dp_9">9dp</dimen><dimen name="dp_10">10dp</dimen><dimen name="dp_11">11dp</dimen><dimen name="dp_12">12dp</dimen><dimen name="dp_13">13dp</dimen><dimen name="dp_14">14dp</dimen><dimen name="dp_15">15dp</dimen><dimen name="dp_16">16dp</dimen><dimen name="dp_17">17dp</dimen><dimen name="dp_18">18dp</dimen><dimen name="dp_19">19dp</dimen><dimen name="dp_20">20dp</dimen><dimen name="dp_21">21dp</dimen><dimen name="dp_22">22dp</dimen><dimen name="dp_23">23dp</dimen><dimen name="dp_24">24dp</dimen><dimen name="dp_25">25dp</dimen><dimen name="dp_26">26dp</dimen><dimen name="dp_27">27dp</dimen><dimen name="dp_28">28dp</dimen><dimen name="dp_29">29dp</dimen><dimen name="dp_30">30dp</dimen><dimen name="dp_31">31dp</dimen><dimen name="dp_32">32dp</dimen><dimen name="dp_33">33dp</dimen><dimen name="dp_34">34dp</dimen><dimen name="dp_35">35dp</dimen><dimen name="dp_36">36dp</dimen><dimen name="dp_37">37dp</dimen><dimen name="dp_38">38dp</dimen><dimen name="dp_39">39dp</dimen><dimen name="dp_40">40dp</dimen><dimen name="dp_41">41dp</dimen><dimen name="dp_42">42dp</dimen><dimen name="dp_43">43dp</dimen><dimen name="dp_44">44dp</dimen><dimen name="dp_45">45dp</dimen><dimen name="dp_46">46dp</dimen><dimen name="dp_47">47dp</dimen><dimen name="dp_48">48dp</dimen><dimen name="dp_49">49dp</dimen><dimen name="dp_50">50dp</dimen><dimen name="dp_51">51dp</dimen><dimen name="dp_52">52dp</dimen><dimen name="dp_53">53dp</dimen><dimen name="dp_54">54dp</dimen><dimen name="dp_55">55dp</dimen><dimen name="dp_56">56dp</dimen><dimen name="dp_57">57dp</dimen><dimen name="dp_58">58dp</dimen><dimen name="dp_59">59dp</dimen><dimen name="dp_60">60dp</dimen><dimen name="dp_61">61dp</dimen><dimen name="dp_62">62dp</dimen><dimen name="dp_63">63dp</dimen><dimen name="dp_64">64dp</dimen><dimen name="dp_65">65dp</dimen><dimen name="dp_66">66dp</dimen><dimen name="dp_67">67dp</dimen><dimen name="dp_68">68dp</dimen><dimen name="dp_69">69dp</dimen><dimen name="dp_70">70dp</dimen><dimen name="dp_71">71dp</dimen><dimen name="dp_72">72dp</dimen><dimen name="dp_73">73dp</dimen><dimen name="dp_74">74dp</dimen><dimen name="dp_75">75dp</dimen><dimen name="dp_76">76dp</dimen><dimen name="dp_77">77dp</dimen><dimen name="dp_78">78dp</dimen><dimen name="dp_79">79dp</dimen><dimen name="dp_80">80dp</dimen><dimen name="dp_81">81dp</dimen><dimen name="dp_82">82dp</dimen><dimen name="dp_83">83dp</dimen><dimen name="dp_84">84dp</dimen><dimen name="dp_85">85dp</dimen><dimen name="dp_86">86dp</dimen><dimen name="dp_87">87dp</dimen><dimen name="dp_88">88dp</dimen><dimen name="dp_89">89dp</dimen><dimen name="dp_90">90dp</dimen><dimen name="dp_91">91dp</dimen><dimen name="dp_92">92dp</dimen><dimen name="dp_93">93dp</dimen><dimen name="dp_94">94dp</dimen><dimen name="dp_95">95dp</dimen><dimen name="dp_96">96dp</dimen><dimen name="dp_97">97dp</dimen><dimen name="dp_98">98dp</dimen><dimen name="dp_99">99dp</dimen><dimen name="dp_100">100dp</dimen><dimen name="dp_101">101dp</dimen><dimen name="dp_102">102dp</dimen><dimen name="dp_103">103dp</dimen><dimen name="dp_104">104dp</dimen><dimen name="dp_105">105dp</dimen><dimen name="dp_106">106dp</dimen><dimen name="dp_107">107dp</dimen><dimen name="dp_108">108dp</dimen><dimen name="dp_109">109dp</dimen><dimen name="dp_110">110dp</dimen><dimen name="dp_111">111dp</dimen><dimen name="dp_112">112dp</dimen><dimen name="dp_113">113dp</dimen><dimen name="dp_114">114dp</dimen><dimen name="dp_115">115dp</dimen><dimen name="dp_116">116dp</dimen><dimen name="dp_117">117dp</dimen><dimen name="dp_118">118dp</dimen><dimen name="dp_119">119dp</dimen><dimen name="dp_120">120dp</dimen><dimen name="dp_121">121dp</dimen><dimen name="dp_122">122dp</dimen><dimen name="dp_123">123dp</dimen><dimen name="dp_124">124dp</dimen><dimen name="dp_125">125dp</dimen><dimen name="dp_126">126dp</dimen><dimen name="dp_127">127dp</dimen><dimen name="dp_128">128dp</dimen><dimen name="dp_129">129dp</dimen><dimen name="dp_130">130dp</dimen><dimen name="dp_131">131dp</dimen><dimen name="dp_132">132dp</dimen><dimen name="dp_133">133dp</dimen><dimen name="dp_134">134dp</dimen><dimen name="dp_135">135dp</dimen><dimen name="dp_136">136dp</dimen><dimen name="dp_137">137dp</dimen><dimen name="dp_138">138dp</dimen><dimen name="dp_139">139dp</dimen><dimen name="dp_140">140dp</dimen><dimen name="dp_141">141dp</dimen><dimen name="dp_142">142dp</dimen><dimen name="dp_143">143dp</dimen><dimen name="dp_144">144dp</dimen><dimen name="dp_145">145dp</dimen><dimen name="dp_146">146dp</dimen><dimen name="dp_147">147dp</dimen><dimen name="dp_148">148dp</dimen><dimen name="dp_149">149dp</dimen><dimen name="dp_150">150dp</dimen><dimen name="dp_151">151dp</dimen><dimen name="dp_152">152dp</dimen><dimen name="dp_153">153dp</dimen><dimen name="dp_154">154dp</dimen><dimen name="dp_155">155dp</dimen><dimen name="dp_156">156dp</dimen><dimen name="dp_157">157dp</dimen><dimen name="dp_158">158dp</dimen><dimen name="dp_159">159dp</dimen><dimen name="dp_160">160dp</dimen><dimen name="dp_161">161dp</dimen><dimen name="dp_162">162dp</dimen><dimen name="dp_163">163dp</dimen><dimen name="dp_164">164dp</dimen><dimen name="dp_165">165dp</dimen><dimen name="dp_166">166dp</dimen><dimen name="dp_167">167dp</dimen><dimen name="dp_168">168dp</dimen><dimen name="dp_169">169dp</dimen><dimen name="dp_170">170dp</dimen><dimen name="dp_171">171dp</dimen><dimen name="dp_172">172dp</dimen><dimen name="dp_173">173dp</dimen><dimen name="dp_174">174dp</dimen><dimen name="dp_175">175dp</dimen><dimen name="dp_176">176dp</dimen><dimen name="dp_177">177dp</dimen><dimen name="dp_178">178dp</dimen><dimen name="dp_179">179dp</dimen><dimen name="dp_180">180dp</dimen><dimen name="dp_181">181dp</dimen><dimen name="dp_182">182dp</dimen><dimen name="dp_183">183dp</dimen><dimen name="dp_184">184dp</dimen><dimen name="dp_185">185dp</dimen><dimen name="dp_186">186dp</dimen><dimen name="dp_187">187dp</dimen><dimen name="dp_188">188dp</dimen><dimen name="dp_189">189dp</dimen><dimen name="dp_190">190dp</dimen><dimen name="dp_191">191dp</dimen><dimen name="dp_192">192dp</dimen><dimen name="dp_193">193dp</dimen><dimen name="dp_194">194dp</dimen><dimen name="dp_195">195dp</dimen><dimen name="dp_196">196dp</dimen><dimen name="dp_197">197dp</dimen><dimen name="dp_198">198dp</dimen><dimen name="dp_199">199dp</dimen><dimen name="dp_200">200dp</dimen><dimen name="dp_201">201dp</dimen><dimen name="dp_202">202dp</dimen><dimen name="dp_203">203dp</dimen><dimen name="dp_204">204dp</dimen><dimen name="dp_205">205dp</dimen><dimen name="dp_206">206dp</dimen><dimen name="dp_207">207dp</dimen><dimen name="dp_208">208dp</dimen><dimen name="dp_209">209dp</dimen><dimen name="dp_210">210dp</dimen><dimen name="dp_211">211dp</dimen><dimen name="dp_212">212dp</dimen><dimen name="dp_213">213dp</dimen><dimen name="dp_214">214dp</dimen><dimen name="dp_215">215dp</dimen><dimen name="dp_216">216dp</dimen><dimen name="dp_217">217dp</dimen><dimen name="dp_218">218dp</dimen><dimen name="dp_219">219dp</dimen><dimen name="dp_220">220dp</dimen><dimen name="dp_221">221dp</dimen><dimen name="dp_222">222dp</dimen><dimen name="dp_223">223dp</dimen><dimen name="dp_224">224dp</dimen><dimen name="dp_225">225dp</dimen><dimen name="dp_226">226dp</dimen><dimen name="dp_227">227dp</dimen><dimen name="dp_228">228dp</dimen><dimen name="dp_229">229dp</dimen><dimen name="dp_230">230dp</dimen><dimen name="dp_231">231dp</dimen><dimen name="dp_232">232dp</dimen><dimen name="dp_233">233dp</dimen><dimen name="dp_234">234dp</dimen><dimen name="dp_235">235dp</dimen><dimen name="dp_236">236dp</dimen><dimen name="dp_237">237dp</dimen><dimen name="dp_238">238dp</dimen><dimen name="dp_239">239dp</dimen><dimen name="dp_240">240dp</dimen><dimen name="dp_241">241dp</dimen><dimen name="dp_242">242dp</dimen><dimen name="dp_243">243dp</dimen><dimen name="dp_244">244dp</dimen><dimen name="dp_245">245dp</dimen><dimen name="dp_246">246dp</dimen><dimen name="dp_247">247dp</dimen><dimen name="dp_248">248dp</dimen><dimen name="dp_249">249dp</dimen><dimen name="dp_250">250dp</dimen><dimen name="dp_251">251dp</dimen><dimen name="dp_252">252dp</dimen><dimen name="dp_253">253dp</dimen><dimen name="dp_254">254dp</dimen><dimen name="dp_255">255dp</dimen><dimen name="dp_256">256dp</dimen><dimen name="dp_257">257dp</dimen><dimen name="dp_258">258dp</dimen><dimen name="dp_259">259dp</dimen><dimen name="dp_260">260dp</dimen><dimen name="dp_261">261dp</dimen><dimen name="dp_262">262dp</dimen><dimen name="dp_263">263dp</dimen><dimen name="dp_264">264dp</dimen><dimen name="dp_265">265dp</dimen><dimen name="dp_266">266dp</dimen><dimen name="dp_267">267dp</dimen><dimen name="dp_268">268dp</dimen><dimen name="dp_269">269dp</dimen><dimen name="dp_270">270dp</dimen><dimen name="dp_271">271dp</dimen><dimen name="dp_272">272dp</dimen><dimen name="dp_273">273dp</dimen><dimen name="dp_274">274dp</dimen><dimen name="dp_275">275dp</dimen><dimen name="dp_276">276dp</dimen><dimen name="dp_277">277dp</dimen><dimen name="dp_278">278dp</dimen><dimen name="dp_279">279dp</dimen><dimen name="dp_280">280dp</dimen><dimen name="dp_281">281dp</dimen><dimen name="dp_282">282dp</dimen><dimen name="dp_283">283dp</dimen><dimen name="dp_284">284dp</dimen><dimen name="dp_285">285dp</dimen><dimen name="dp_286">286dp</dimen><dimen name="dp_287">287dp</dimen><dimen name="dp_288">288dp</dimen><dimen name="dp_289">289dp</dimen><dimen name="dp_290">290dp</dimen><dimen name="dp_291">291dp</dimen><dimen name="dp_292">292dp</dimen><dimen name="dp_293">293dp</dimen><dimen name="dp_294">294dp</dimen><dimen name="dp_295">295dp</dimen><dimen name="dp_296">296dp</dimen><dimen name="dp_297">297dp</dimen><dimen name="dp_298">298dp</dimen><dimen name="dp_299">299dp</dimen><dimen name="dp_300">300dp</dimen><dimen name="dp_301">301dp</dimen><dimen name="dp_302">302dp</dimen><dimen name="dp_303">303dp</dimen><dimen name="dp_304">304dp</dimen><dimen name="dp_305">305dp</dimen><dimen name="dp_306">306dp</dimen><dimen name="dp_307">307dp</dimen><dimen name="dp_308">308dp</dimen><dimen name="dp_309">309dp</dimen><dimen name="dp_310">310dp</dimen><dimen name="dp_311">311dp</dimen><dimen name="dp_312">312dp</dimen><dimen name="dp_313">313dp</dimen><dimen name="dp_314">314dp</dimen><dimen name="dp_315">315dp</dimen><dimen name="dp_316">316dp</dimen><dimen name="dp_317">317dp</dimen><dimen name="dp_318">318dp</dimen><dimen name="dp_319">319dp</dimen><dimen name="dp_320">320dp</dimen><dimen name="dp_321">321dp</dimen><dimen name="dp_322">322dp</dimen><dimen name="dp_323">323dp</dimen><dimen name="dp_324">324dp</dimen><dimen name="dp_325">325dp</dimen><dimen name="dp_326">326dp</dimen><dimen name="dp_327">327dp</dimen><dimen name="dp_328">328dp</dimen><dimen name="dp_329">329dp</dimen><dimen name="dp_330">330dp</dimen><dimen name="dp_331">331dp</dimen><dimen name="dp_332">332dp</dimen><dimen name="dp_333">333dp</dimen><dimen name="dp_334">334dp</dimen><dimen name="dp_335">335dp</dimen><dimen name="dp_336">336dp</dimen><dimen name="dp_337">337dp</dimen><dimen name="dp_338">338dp</dimen><dimen name="dp_339">339dp</dimen><dimen name="dp_340">340dp</dimen><dimen name="dp_341">341dp</dimen><dimen name="dp_342">342dp</dimen><dimen name="dp_343">343dp</dimen><dimen name="dp_344">344dp</dimen><dimen name="dp_345">345dp</dimen><dimen name="dp_346">346dp</dimen><dimen name="dp_347">347dp</dimen><dimen name="dp_348">348dp</dimen><dimen name="dp_349">349dp</dimen><dimen name="dp_350">350dp</dimen><dimen name="dp_351">351dp</dimen><dimen name="dp_352">352dp</dimen><dimen name="dp_353">353dp</dimen><dimen name="dp_354">354dp</dimen><dimen name="dp_355">355dp</dimen><dimen name="dp_356">356dp</dimen><dimen name="dp_357">357dp</dimen><dimen name="dp_358">358dp</dimen><dimen name="dp_359">359dp</dimen><dimen name="dp_360">360dp</dimen><dimen name="dp_365">365dp</dimen><dimen name="dp_370">370dp</dimen><dimen name="dp_400">400dp</dimen><dimen name="dp_410">410dp</dimen><dimen name="dp_422">422dp</dimen><dimen name="dp_472">472dp</dimen><dimen name="dp_500">500dp</dimen><dimen name="dp_600">600dp</dimen><dimen name="dp_640">640dp</dimen><dimen name="dp_720">720dp</dimen><!--字体--><dimen name="sp_6">6sp</dimen><dimen name="sp_7">7sp</dimen><dimen name="sp_8">8sp</dimen><dimen name="sp_9">9sp</dimen><dimen name="sp_10">10sp</dimen><dimen name="sp_11">11sp</dimen><dimen name="sp_12">12sp</dimen><dimen name="sp_13">13sp</dimen><dimen name="sp_14">14sp</dimen><dimen name="sp_15">15sp</dimen><dimen name="sp_16">16sp</dimen><dimen name="sp_17">17sp</dimen><dimen name="sp_18">18sp</dimen><dimen name="sp_19">19sp</dimen><dimen name="sp_20">20sp</dimen><dimen name="sp_21">21sp</dimen><dimen name="sp_22">22sp</dimen><dimen name="sp_23">23sp</dimen><dimen name="sp_24">24sp</dimen><dimen name="sp_25">25sp</dimen><dimen name="sp_26">26sp</dimen><dimen name="sp_27">27sp</dimen><dimen name="sp_28">28sp</dimen><dimen name="sp_29">29sp</dimen><dimen name="sp_30">30sp</dimen><dimen name="sp_31">31sp</dimen><dimen name="sp_32">32sp</dimen><dimen name="sp_33">33sp</dimen><dimen name="sp_34">34sp</dimen><dimen name="sp_35">35sp</dimen><dimen name="sp_36">36sp</dimen><dimen name="sp_37">37sp</dimen><dimen name="sp_38">38sp</dimen><dimen name="sp_40">40sp</dimen><dimen name="sp_42">42sp</dimen><dimen name="sp_48">48sp</dimen>
</resources>

弹窗页面的布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/transparent"android:orientation="vertical"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="@dimen/dp_8"android:background="@mipmap/icon_add_bg_9"android:orientation="vertical"android:paddingBottom="@dimen/dp_20"android:paddingTop="@dimen/dp_20"><TextViewandroid:id="@+id/tv_change_city"android:gravity="center"android:layout_width="@dimen/dp_140"android:layout_height="@dimen/dp_48"android:text="切换城市"android:foreground="@drawable/bg_white"android:textColor="@color/black"android:textSize="@dimen/sp_16"/><TextViewandroid:id="@+id/tv_change_bg"android:gravity="center"android:layout_width="@dimen/dp_140"android:layout_height="@dimen/dp_48"android:text="切换背景"android:foreground="@drawable/bg_white"android:textColor="@color/black"android:textSize="@dimen/sp_16"/><TextViewandroid:id="@+id/tv_more"android:gravity="center"android:layout_width="@dimen/dp_140"android:layout_height="@dimen/dp_48"android:text="更多功能"android:foreground="@drawable/bg_white"android:textColor="@color/black"android:textSize="@dimen/sp_16"/></LinearLayout></LinearLayout>

然后是对activity_main.xml文件的修改

这里修改了原来的id和src里面的图片,增加了点击的效果
icon_add.png

selector_bg_img.xml,点击之后背景变色,增加用户体验

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:state_pressed="true"><shape><solid android:color="#22000000"/></shape></item><item><shape><solid android:color="#00000000"/></shape></item>
</selector>

MainActivity.java

这里我是把原来的id注释掉,不过我没有删掉,因为我要让你们知道是怎么样一个过程,你们是可以直接替换的,不替换会报错了,当然你不改Id就不会报错,但是id的命名和现在是意思对不上,会对其他人造成困扰,严谨一点就修改ID。

更改点击之后的弹窗。

要显示弹窗一些基本的配置必不可少,这里用到了一个动画工具类AnimationUtil,代码如下:

package com.llw.mvplibrary.utils;import android.animation.Animator;
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;/*** 动画工具类* UpdateListener: 动画过程中通过添加此监听来回调数据* EndListener: 动画结束的时候通过此监听器来做一些处理*/
public class AnimationUtil {private ValueAnimator valueAnimator;private UpdateListener updateListener;private EndListener endListener;private long duration;private float start;private float end;private Interpolator interpolator = new LinearInterpolator();public AnimationUtil() {duration = 1000; //默认动画时常1sstart = 0.0f;end = 1.0f;interpolator = new LinearInterpolator();// 匀速的插值器}public void setDuration(int timeLength) {duration = timeLength;}public void setValueAnimator(float start, float end, long duration) {this.start = start;this.end = end;this.duration = duration;}public void setInterpolator(Interpolator interpolator) {this.interpolator = interpolator;}public void startAnimator() {if (valueAnimator != null){valueAnimator = null;}valueAnimator = ValueAnimator.ofFloat(start, end);valueAnimator.setDuration(duration);valueAnimator.setInterpolator(interpolator);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {if (updateListener == null) {return;}float cur = (float) valueAnimator.getAnimatedValue();updateListener.progress(cur);}});valueAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animator) {}@Overridepublic void onAnimationEnd(Animator animator) {if(endListener == null){return;}endListener.endUpdate(animator);}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic void onAnimationRepeat(Animator animator) {}});valueAnimator.start();}public void addUpdateListener(UpdateListener updateListener) {this.updateListener = updateListener;}public void addEndListner(EndListener endListener){this.endListener = endListener;}public interface EndListener {void endUpdate(Animator animator);}public interface UpdateListener {void progress(float progress);}
}


然后写三个方法,一个显示弹窗,及控制里面的点击事件、计算动画时间、第三个修改背景的透明度类似蒙版的效果。

这三个方法的代码我都会贴上来。不过首先,先增加弹窗出现和关闭的动画效果。

这张图告诉你在什么地方添加这个样式

     <!--右上角加号点击弹窗动画效果--><style name="pop_add"><item name="android:windowEnterAnimation">@anim/pop_add_show</item><item name="android:windowExitAnimation">@anim/pop_add_hide</item></style>

pop_add_show.xml 显示动画

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><alphaandroid:duration="500"android:fromAlpha="0.0"android:toAlpha="1.0"/><scaleandroid:duration="500"android:fromXScale="0"android:fromYScale="0"android:interpolator="@android:anim/decelerate_interpolator"android:pivotX="85%"android:pivotY="0%"android:toXScale="1.0"android:toYScale="1.0"/>
</set>

pop_add_hide.xml 隐藏动画

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><alphaandroid:duration="500"android:fromAlpha="1.0"android:toAlpha="0.0"/><scaleandroid:duration="500"android:fromXScale="1.0"android:fromYScale="1.0"android:interpolator="@android:anim/accelerate_interpolator"android:pivotX="85%"android:pivotY="0%"android:toXScale="0"android:toYScale="0"/>
</set>

然后贴一下三个方法的代码:

showAddWindow方法

 /*** 更多功能弹窗,因为区别于我原先写的弹窗*/private void showAddWindow() {// 设置布局文件mPopupWindow.setContentView(LayoutInflater.from(this).inflate(R.layout.window_add, null));// 为了避免部分机型不显示,我们需要重新设置一下宽高mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);mPopupWindow.setBackgroundDrawable(new ColorDrawable(0x0000));// 设置pop透明效果mPopupWindow.setAnimationStyle(R.style.pop_add);// 设置pop出入动画mPopupWindow.setFocusable(true);// 设置pop获取焦点,如果为false点击返回按钮会退出当前Activity,如果pop中有Editor的话,focusable必须要为truemPopupWindow.setTouchable(true);// 设置pop可点击,为false点击事件无效,默认为truemPopupWindow.setOutsideTouchable(true);// 设置点击pop外侧消失,默认为false;在focusable为true时点击外侧始终消失mPopupWindow.showAsDropDown(ivAdd, -100, 0);// 相对于 + 号正下面,同时可以设置偏移量// 设置pop关闭监听,用于改变背景透明度mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {//关闭弹窗@Overridepublic void onDismiss() {toggleBright();}});//绑定布局中的控件TextView changeCity = mPopupWindow.getContentView().findViewById(R.id.tv_change_city);TextView changeBg = mPopupWindow.getContentView().findViewById(R.id.tv_change_bg);TextView more = mPopupWindow.getContentView().findViewById(R.id.tv_more);changeCity.setOnClickListener(view -> {//切换城市showCityWindow();mPopupWindow.dismiss();});changeBg.setOnClickListener(view -> {//切换背景ToastUtils.showShortToast(context,"你点击了切换背景");mPopupWindow.dismiss();});more.setOnClickListener(view -> {//更多功能ToastUtils.showShortToast(context,"如果你有什么好的建议,可以博客留言哦!");mPopupWindow.dismiss();});}

计算动画时间

 /*** 计算动画时间*/private void toggleBright() {// 三个参数分别为:起始值 结束值 时长,那么整个动画回调过来的值就是从0.5f--1f的animUtil.setValueAnimator(START_ALPHA, END_ALPHA, DURATION);animUtil.addUpdateListener(new AnimationUtil.UpdateListener() {@Overridepublic void progress(float progress) {// 此处系统会根据上述三个值,计算每次回调的值是多少,我们根据这个值来改变透明度bgAlpha = bright ? progress : (START_ALPHA + END_ALPHA - progress);backgroundAlpha(bgAlpha);}});animUtil.addEndListner(new AnimationUtil.EndListener() {@Overridepublic void endUpdate(Animator animator) {// 在一次动画结束的时候,翻转状态bright = !bright;}});animUtil.startAnimator();}

此方法用于改变背景的透明度

 /*** 此方法用于改变背景的透明度,从而达到“变暗”的效果*/private void backgroundAlpha(float bgAlpha) {WindowManager.LayoutParams lp = getWindow().getAttributes();// 0.0-1.0lp.alpha = bgAlpha;getWindow().setAttributes(lp);// everything behind this window will be dimmed.// 此方法用来设置浮动层,防止部分手机变暗无效getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);}

这几个方法借鉴了网络上的代码。

效果图如下:

如果你在写的过程中遇到任何问题,可以直接评论或者给我发邮件。
接下来就是切换背景了

切换背景

切换背景的业务代码,我当然不可能也在MainActivity中写,因为现在里面的代码已经够多了,所以就要新建一个页面。在项目的包下新建一个ui包,用于存放除MainActivity之外的所有Activity。这样会比较规范,至于为什么不把MainActivity也放进去,因为目前我还不想放进去。
鼠标右键点击ui → New → Activity → Empty Activity


Next即可

创建好之后修改布局。
修改布局之前,这个要更改一个开关按钮的样式。

首先是增加样式代码:

 <!--开关按钮--><declare-styleable name="SwitchButton"><attr name="sb_shadow_radius" format="reference|dimension"/><attr name="sb_shadow_offset" format="reference|dimension"/><attr name="sb_shadow_color" format="reference|color"/><attr name="sb_uncheck_color" format="reference|color"/><attr name="sb_checked_color" format="reference|color"/><attr name="sb_border_width" format="reference|dimension"/><attr name="sb_checkline_color" format="reference|color"/><attr name="sb_checkline_width" format="reference|dimension"/><attr name="sb_uncheckcircle_color" format="reference|color"/><attr name="sb_uncheckcircle_width" format="reference|dimension"/><attr name="sb_uncheckcircle_radius" format="reference|dimension"/><attr name="sb_checked" format="reference|boolean"/><attr name="sb_shadow_effect" format="reference|boolean"/><attr name="sb_effect_duration" format="reference|integer"/><attr name="sb_button_color" format="reference|color"/><attr name="sb_show_indicator" format="reference|boolean"/><attr name="sb_background" format="reference|color"/><attr name="sb_enable_effect" format="reference|boolean"/></declare-styleable>

然后自定义View

SwitchButton.java代码如下:

package com.llw.mvplibrary.view;import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Checkable;import com.llw.mvplibrary.R;/*** SwitchButton.*/
public class SwitchButton extends View implements Checkable {private static final int DEFAULT_WIDTH = dp2pxInt(58);private static final int DEFAULT_HEIGHT = dp2pxInt(36);/*** 动画状态:* 1.静止* 2.进入拖动* 3.处于拖动* 4.拖动-复位* 5.拖动-切换* 6.点击切换* **/private final int ANIMATE_STATE_NONE = 0;private final int ANIMATE_STATE_PENDING_DRAG = 1;private final int ANIMATE_STATE_DRAGING = 2;private final int ANIMATE_STATE_PENDING_RESET = 3;private final int ANIMATE_STATE_PENDING_SETTLE = 4;private final int ANIMATE_STATE_SWITCH = 5;public SwitchButton(Context context) {super(context);init(context, null);}public SwitchButton(Context context, AttributeSet attrs) {super(context, attrs);init(context, attrs);}public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs);}@TargetApi(Build.VERSION_CODES.LOLLIPOP)public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context, attrs);}@Overridepublic final void setPadding(int left, int top, int right, int bottom) {super.setPadding(0, 0, 0, 0);}/*** 初始化参数*/private void init(Context context, AttributeSet attrs) {TypedArray typedArray = null;if(attrs != null){typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton);}shadowEffect = optBoolean(typedArray,R.styleable.SwitchButton_sb_shadow_effect,true);uncheckCircleColor = optColor(typedArray,R.styleable.SwitchButton_sb_uncheckcircle_color,0XffAAAAAA);//0XffAAAAAA;uncheckCircleWidth = optPixelSize(typedArray,R.styleable.SwitchButton_sb_uncheckcircle_width,dp2pxInt(1.5f));//dp2pxInt(1.5f);uncheckCircleOffsetX = dp2px(10);uncheckCircleRadius = optPixelSize(typedArray,R.styleable.SwitchButton_sb_uncheckcircle_radius,dp2px(4));//dp2px(4);checkedLineOffsetX = dp2px(4);checkedLineOffsetY = dp2px(4);shadowRadius = optPixelSize(typedArray,R.styleable.SwitchButton_sb_shadow_radius,dp2pxInt(2.5f));//dp2pxInt(2.5f);shadowOffset = optPixelSize(typedArray,R.styleable.SwitchButton_sb_shadow_offset,dp2pxInt(1.5f));//dp2pxInt(1.5f);shadowColor = optColor(typedArray,R.styleable.SwitchButton_sb_shadow_color,0X33000000);//0X33000000;uncheckColor = optColor(typedArray,R.styleable.SwitchButton_sb_uncheck_color,0XffDDDDDD);//0XffDDDDDD;checkedColor = optColor(typedArray,R.styleable.SwitchButton_sb_checked_color,0Xff51d367);//0Xff51d367;borderWidth = optPixelSize(typedArray,R.styleable.SwitchButton_sb_border_width,dp2pxInt(1));//dp2pxInt(1);checkLineColor = optColor(typedArray,R.styleable.SwitchButton_sb_checkline_color,Color.WHITE);//Color.WHITE;checkLineWidth = optPixelSize(typedArray,R.styleable.SwitchButton_sb_checkline_width,dp2pxInt(1f));//dp2pxInt(1.0f);checkLineLength = dp2px(6);int buttonColor = optColor(typedArray,R.styleable.SwitchButton_sb_button_color,Color.WHITE);//Color.WHITE;int effectDuration = optInt(typedArray,R.styleable.SwitchButton_sb_effect_duration,300);//300;isChecked = optBoolean(typedArray,R.styleable.SwitchButton_sb_checked,false);showIndicator = optBoolean(typedArray,R.styleable.SwitchButton_sb_show_indicator,true);background = optColor(typedArray,R.styleable.SwitchButton_sb_background,Color.WHITE);//Color.WHITE;enableEffect = optBoolean(typedArray,R.styleable.SwitchButton_sb_enable_effect,true);if(typedArray != null){typedArray.recycle();}paint = new Paint(Paint.ANTI_ALIAS_FLAG);buttonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);buttonPaint.setColor(buttonColor);if(shadowEffect){buttonPaint.setShadowLayer(shadowRadius,0, shadowOffset,shadowColor);}viewState = new ViewState();beforeState = new ViewState();afterState = new ViewState();valueAnimator = ValueAnimator.ofFloat(0f, 1f);valueAnimator.setDuration(effectDuration);valueAnimator.setRepeatCount(0);valueAnimator.addUpdateListener(animatorUpdateListener);valueAnimator.addListener(animatorListener);super.setClickable(true);this.setPadding(0, 0, 0, 0);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {setLayerType(LAYER_TYPE_SOFTWARE, null);}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {final int widthMode = MeasureSpec.getMode(widthMeasureSpec);final int heightMode = MeasureSpec.getMode(heightMeasureSpec);if(widthMode == MeasureSpec.UNSPECIFIED|| widthMode == MeasureSpec.AT_MOST){widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY);}if(heightMode == MeasureSpec.UNSPECIFIED|| heightMode == MeasureSpec.AT_MOST){heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY);}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);float viewPadding = Math.max(shadowRadius + shadowOffset, borderWidth);height = h - viewPadding - viewPadding;width = w - viewPadding - viewPadding;viewRadius = height * .5f;buttonRadius = viewRadius - borderWidth;left = viewPadding;top = viewPadding;right = w - viewPadding;bottom = h - viewPadding;centerX = (left + right) * .5f;centerY = (top + bottom) * .5f;buttonMinX = left + viewRadius;buttonMaxX = right - viewRadius;if(isChecked()){setCheckedViewState(viewState);}else{setUncheckViewState(viewState);}isUiInited = true;postInvalidate();}/*** @param viewState*/private void setUncheckViewState(ViewState viewState){viewState.radius = 0;viewState.checkStateColor = uncheckColor;viewState.checkedLineColor = Color.TRANSPARENT;viewState.buttonX = buttonMinX;}/*** @param viewState*/private void setCheckedViewState(ViewState viewState){viewState.radius = viewRadius;viewState.checkStateColor = checkedColor;viewState.checkedLineColor = checkLineColor;viewState.buttonX = buttonMaxX;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);paint.setStrokeWidth(borderWidth);paint.setStyle(Paint.Style.FILL);//绘制白色背景paint.setColor(background);drawRoundRect(canvas,left, top, right, bottom,viewRadius, paint);//绘制关闭状态的边框paint.setStyle(Paint.Style.STROKE);paint.setColor(uncheckColor);drawRoundRect(canvas,left, top, right, bottom,viewRadius, paint);//绘制小圆圈if(showIndicator){drawUncheckIndicator(canvas);}//绘制开启背景色float des = viewState.radius * .5f;//[0-backgroundRadius*0.5f]paint.setStyle(Paint.Style.STROKE);paint.setColor(viewState.checkStateColor);paint.setStrokeWidth(borderWidth + des * 2f);drawRoundRect(canvas,left + des, top + des, right - des, bottom - des,viewRadius, paint);//绘制按钮左边绿色长条遮挡paint.setStyle(Paint.Style.FILL);paint.setStrokeWidth(1);drawArc(canvas,left, top,left + 2 * viewRadius, top + 2 * viewRadius,90, 180, paint);canvas.drawRect(left + viewRadius, top,viewState.buttonX, top + 2 * viewRadius,paint);//绘制小线条if(showIndicator){drawCheckedIndicator(canvas);}//绘制按钮drawButton(canvas, viewState.buttonX, centerY);}/*** 绘制选中状态指示器* @param canvas*/protected void drawCheckedIndicator(Canvas canvas) {drawCheckedIndicator(canvas,viewState.checkedLineColor,checkLineWidth,left + viewRadius - checkedLineOffsetX, centerY - checkLineLength,left + viewRadius - checkedLineOffsetY, centerY + checkLineLength,paint);}/*** 绘制选中状态指示器* @param canvas* @param color* @param lineWidth* @param sx* @param sy* @param ex* @param ey* @param paint*/protected void drawCheckedIndicator(Canvas canvas,int color,float lineWidth,float sx, float sy, float ex, float ey,Paint paint) {paint.setStyle(Paint.Style.STROKE);paint.setColor(color);paint.setStrokeWidth(lineWidth);canvas.drawLine(sx, sy, ex, ey,paint);}/*** 绘制关闭状态指示器* @param canvas*/private void drawUncheckIndicator(Canvas canvas) {drawUncheckIndicator(canvas,uncheckCircleColor,uncheckCircleWidth,right - uncheckCircleOffsetX, centerY,uncheckCircleRadius,paint);}/*** 绘制关闭状态指示器* @param canvas* @param color* @param lineWidth* @param centerX* @param centerY* @param radius* @param paint*/protected void drawUncheckIndicator(Canvas canvas,int color,float lineWidth,float centerX, float centerY,float radius,Paint paint) {paint.setStyle(Paint.Style.STROKE);paint.setColor(color);paint.setStrokeWidth(lineWidth);canvas.drawCircle(centerX, centerY, radius, paint);}/*** @param canvas* @param left* @param top* @param right* @param bottom* @param startAngle* @param sweepAngle* @param paint*/private void drawArc(Canvas canvas,float left, float top,float right, float bottom,float startAngle, float sweepAngle,Paint paint){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {canvas.drawArc(left, top, right, bottom,startAngle, sweepAngle, true, paint);}else{rect.set(left, top, right, bottom);canvas.drawArc(rect,startAngle, sweepAngle, true, paint);}}/*** @param canvas* @param left* @param top* @param right* @param bottom* @param backgroundRadius* @param paint*/private void drawRoundRect(Canvas canvas,float left, float top,float right, float bottom,float backgroundRadius,Paint paint){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {canvas.drawRoundRect(left, top, right, bottom,backgroundRadius, backgroundRadius, paint);}else{rect.set(left, top, right, bottom);canvas.drawRoundRect(rect,backgroundRadius, backgroundRadius, paint);}}/*** @param canvas* @param x px* @param y px*/private void drawButton(Canvas canvas, float x, float y) {canvas.drawCircle(x, y, buttonRadius, buttonPaint);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(1);paint.setColor(0XffDDDDDD);canvas.drawCircle(x, y, buttonRadius, paint);}@Overridepublic void setChecked(boolean checked) {if(checked == isChecked()){postInvalidate();return;}toggle(enableEffect, false);}@Overridepublic boolean isChecked() {return isChecked;}@Overridepublic void toggle() {toggle(true);}/*** 切换状态* @param animate*/public void toggle(boolean animate) {toggle(animate, true);}private void toggle(boolean animate, boolean broadcast) {if(!isEnabled()){return;}if(isEventBroadcast){throw new RuntimeException("should NOT switch the state in method: [onCheckedChanged]!");}if(!isUiInited){isChecked = !isChecked;if(broadcast){broadcastEvent();}return;}if(valueAnimator.isRunning()){valueAnimator.cancel();}if(!enableEffect || !animate){isChecked = !isChecked;if(isChecked()){setCheckedViewState(viewState);}else{setUncheckViewState(viewState);}postInvalidate();if(broadcast){broadcastEvent();}return;}animateState = ANIMATE_STATE_SWITCH;beforeState.copy(viewState);if(isChecked()){//切换到uncheckedsetUncheckViewState(afterState);}else{setCheckedViewState(afterState);}valueAnimator.start();}/****/private void broadcastEvent() {if(onCheckedChangeListener != null){isEventBroadcast = true;onCheckedChangeListener.onCheckedChanged(this, isChecked());}isEventBroadcast = false;}@Overridepublic boolean onTouchEvent(MotionEvent event) {if(!isEnabled()){return false;}int actionMasked = event.getActionMasked();switch (actionMasked){case MotionEvent.ACTION_DOWN:{isTouchingDown = true;touchDownTime = System.currentTimeMillis();//取消准备进入拖动状态removeCallbacks(postPendingDrag);//预设100ms进入拖动状态postDelayed(postPendingDrag, 100);break;}case MotionEvent.ACTION_MOVE:{float eventX = event.getX();if(isPendingDragState()){//在准备进入拖动状态过程中,可以拖动按钮位置float fraction = eventX / getWidth();fraction = Math.max(0f, Math.min(1f, fraction));viewState.buttonX = buttonMinX+ (buttonMaxX - buttonMinX)* fraction;}else if(isDragState()){//拖动按钮位置,同时改变对应的背景颜色float fraction = eventX / getWidth();fraction = Math.max(0f, Math.min(1f, fraction));viewState.buttonX = buttonMinX+ (buttonMaxX - buttonMinX)* fraction;viewState.checkStateColor = (int) argbEvaluator.evaluate(fraction,uncheckColor,checkedColor);postInvalidate();}break;}case MotionEvent.ACTION_UP:{isTouchingDown = false;//取消准备进入拖动状态removeCallbacks(postPendingDrag);if(System.currentTimeMillis() - touchDownTime <= 300){//点击时间小于300ms,认为是点击操作toggle();}else if(isDragState()){//在拖动状态,计算按钮位置,设置是否切换状态float eventX = event.getX();float fraction = eventX / getWidth();fraction = Math.max(0f, Math.min(1f, fraction));boolean newCheck = fraction > .5f;if(newCheck == isChecked()){pendingCancelDragState();}else{isChecked = newCheck;pendingSettleState();}}else if(isPendingDragState()){//在准备进入拖动状态过程中,取消之,复位pendingCancelDragState();}break;}case MotionEvent.ACTION_CANCEL:{isTouchingDown = false;removeCallbacks(postPendingDrag);if(isPendingDragState()|| isDragState()){//复位pendingCancelDragState();}break;}}return true;}/*** 是否在动画状态* @return*/private boolean isInAnimating(){return animateState != ANIMATE_STATE_NONE;}/*** 是否在进入拖动或离开拖动状态* @return*/private boolean isPendingDragState(){return animateState == ANIMATE_STATE_PENDING_DRAG|| animateState == ANIMATE_STATE_PENDING_RESET;}/*** 是否在手指拖动状态* @return*/private boolean isDragState(){return animateState == ANIMATE_STATE_DRAGING;}/*** 设置是否启用阴影效果* @param shadowEffect true.启用*/public void setShadowEffect(boolean shadowEffect) {if(this.shadowEffect == shadowEffect){return;}this.shadowEffect = shadowEffect;if(this.shadowEffect){buttonPaint.setShadowLayer(shadowRadius,0, shadowOffset,shadowColor);}else{buttonPaint.setShadowLayer(0,0, 0,0);}}public void setEnableEffect(boolean enable){this.enableEffect = enable;}/*** 开始进入拖动状态*/private void pendingDragState() {if(isInAnimating()){return;}if(!isTouchingDown){return;}if(valueAnimator.isRunning()){valueAnimator.cancel();}animateState = ANIMATE_STATE_PENDING_DRAG;beforeState.copy(viewState);afterState.copy(viewState);if(isChecked()){afterState.checkStateColor = checkedColor;afterState.buttonX = buttonMaxX;afterState.checkedLineColor = checkedColor;}else{afterState.checkStateColor = uncheckColor;afterState.buttonX = buttonMinX;afterState.radius = viewRadius;}valueAnimator.start();}/*** 取消拖动状态*/private void pendingCancelDragState() {if(isDragState() || isPendingDragState()){if(valueAnimator.isRunning()){valueAnimator.cancel();}animateState = ANIMATE_STATE_PENDING_RESET;beforeState.copy(viewState);if(isChecked()){setCheckedViewState(afterState);}else{setUncheckViewState(afterState);}valueAnimator.start();}}/*** 动画-设置新的状态*/private void pendingSettleState() {if(valueAnimator.isRunning()){valueAnimator.cancel();}animateState = ANIMATE_STATE_PENDING_SETTLE;beforeState.copy(viewState);if(isChecked()){setCheckedViewState(afterState);}else{setUncheckViewState(afterState);}valueAnimator.start();}@Overridepublic final void setOnClickListener(OnClickListener l) {}@Overridepublic final void setOnLongClickListener(OnLongClickListener l) {}public void setOnCheckedChangeListener(OnCheckedChangeListener l){onCheckedChangeListener = l;}public interface OnCheckedChangeListener{void onCheckedChanged(SwitchButton view, boolean isChecked);}/*******************************************************/private static float dp2px(float dp){Resources r = Resources.getSystem();return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());}private static int dp2pxInt(float dp){return (int) dp2px(dp);}private static int optInt(TypedArray typedArray,int index,int def) {if(typedArray == null){return def;}return typedArray.getInt(index, def);}private static float optPixelSize(TypedArray typedArray,int index,float def) {if(typedArray == null){return def;}return typedArray.getDimension(index, def);}private static int optPixelSize(TypedArray typedArray,int index,int def) {if(typedArray == null){return def;}return typedArray.getDimensionPixelOffset(index, def);}private static int optColor(TypedArray typedArray,int index,int def) {if(typedArray == null){return def;}return typedArray.getColor(index, def);}private static boolean optBoolean(TypedArray typedArray,int index,boolean def) {if(typedArray == null){return def;}return typedArray.getBoolean(index, def);}/*******************************************************//*** 阴影半径*/private int shadowRadius;/*** 阴影Y偏移px*/private int shadowOffset;/*** 阴影颜色*/private int shadowColor ;/*** 背景半径*/private float viewRadius;/*** 按钮半径*/private float buttonRadius;/*** 背景高*/private float height ;/*** 背景宽*/private float width;/*** 背景位置*/private float left   ;private float top    ;private float right  ;private float bottom ;private float centerX;private float centerY;/*** 背景底色*/private int background;/*** 背景关闭颜色*/private int uncheckColor;/*** 背景打开颜色*/private int checkedColor;/*** 边框宽度px*/private int borderWidth;/*** 打开指示线颜色*/private int checkLineColor;/*** 打开指示线宽*/private int checkLineWidth;/*** 打开指示线长*/private float checkLineLength;/*** 关闭圆圈颜色*/private int uncheckCircleColor;/***关闭圆圈线宽*/private int uncheckCircleWidth;/***关闭圆圈位移X*/private float uncheckCircleOffsetX;/***关闭圆圈半径*/private float uncheckCircleRadius;/***打开指示线位移X*/private float checkedLineOffsetX;/***打开指示线位移Y*/private float checkedLineOffsetY;/*** 按钮最左边*/private float buttonMinX;/*** 按钮最右边*/private float buttonMaxX;/*** 按钮画笔*/private Paint buttonPaint;/*** 背景画笔*/private Paint paint;/*** 当前状态*/private ViewState viewState;private ViewState beforeState;private ViewState afterState;private RectF rect = new RectF();/*** 动画状态*/private int animateState = ANIMATE_STATE_NONE;/****/private ValueAnimator valueAnimator;private final android.animation.ArgbEvaluator argbEvaluator= new android.animation.ArgbEvaluator();/***是否选中*/private boolean isChecked;/*** 是否启用动画*/private boolean enableEffect;/*** 是否启用阴影效果*/private boolean shadowEffect;/*** 是否显示指示器*/private boolean showIndicator;/*** 收拾是否按下*/private boolean isTouchingDown = false;/****/private boolean isUiInited = false;/****/private boolean isEventBroadcast = false;private OnCheckedChangeListener onCheckedChangeListener;/*** 手势按下的时刻*/private long touchDownTime;private Runnable postPendingDrag = new Runnable() {@Overridepublic void run() {if(!isInAnimating()){pendingDragState();}}};private ValueAnimator.AnimatorUpdateListener animatorUpdateListener= new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float value = (Float) animation.getAnimatedValue();switch (animateState) {case ANIMATE_STATE_PENDING_SETTLE: {}case ANIMATE_STATE_PENDING_RESET: {}case ANIMATE_STATE_PENDING_DRAG: {viewState.checkedLineColor = (int) argbEvaluator.evaluate(value,beforeState.checkedLineColor,afterState.checkedLineColor);viewState.radius = beforeState.radius+ (afterState.radius - beforeState.radius) * value;if(animateState != ANIMATE_STATE_PENDING_DRAG){viewState.buttonX = beforeState.buttonX+ (afterState.buttonX - beforeState.buttonX) * value;}viewState.checkStateColor = (int) argbEvaluator.evaluate(value,beforeState.checkStateColor,afterState.checkStateColor);break;}case ANIMATE_STATE_SWITCH: {viewState.buttonX = beforeState.buttonX+ (afterState.buttonX - beforeState.buttonX) * value;float fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX);viewState.checkStateColor = (int) argbEvaluator.evaluate(fraction,uncheckColor,checkedColor);viewState.radius = fraction * viewRadius;viewState.checkedLineColor = (int) argbEvaluator.evaluate(fraction,Color.TRANSPARENT,checkLineColor);break;}default:case ANIMATE_STATE_DRAGING: {}case ANIMATE_STATE_NONE: {break;}}postInvalidate();}};private Animator.AnimatorListener animatorListener= new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {switch (animateState) {case ANIMATE_STATE_DRAGING: {break;}case ANIMATE_STATE_PENDING_DRAG: {animateState = ANIMATE_STATE_DRAGING;viewState.checkedLineColor = Color.TRANSPARENT;viewState.radius = viewRadius;postInvalidate();break;}case ANIMATE_STATE_PENDING_RESET: {animateState = ANIMATE_STATE_NONE;postInvalidate();break;}case ANIMATE_STATE_PENDING_SETTLE: {animateState = ANIMATE_STATE_NONE;postInvalidate();broadcastEvent();break;}case ANIMATE_STATE_SWITCH: {isChecked = !isChecked;animateState = ANIMATE_STATE_NONE;postInvalidate();broadcastEvent();break;}default:case ANIMATE_STATE_NONE: {break;}}}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}};/*******************************************************//*** 保存动画状态* */private static class ViewState {/*** 按钮x位置[buttonMinX-buttonMaxX]*/float buttonX;/*** 状态背景颜色*/int checkStateColor;/*** 选中线的颜色*/int checkedLineColor;/*** 状态背景的半径*/float radius;ViewState(){}private void copy(ViewState source){this.buttonX = source.buttonX;this.checkStateColor = source.checkStateColor;this.checkedLineColor = source.checkedLineColor;this.radius = source.radius;}}}

然后是修改activity_background_manager.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"android:fitsSystemWindows="true"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".ui.BackgroundManagerActivity"><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/white"app:layout_constraintEnd_toEndOf="parent"app:navigationIcon="@mipmap/icon_return"app:contentInsetLeft="@dimen/dp_16"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent"app:title=""app:popupTheme="@style/AppTheme.PopupOverlay"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:textSize="@dimen/sp_16"android:textColor="@color/black"android:text="背景管理" /></androidx.appcompat.widget.Toolbar><LinearLayoutandroid:orientation="vertical"android:layout_marginTop="@dimen/dp_8"android:layout_width="match_parent"android:layout_height="wrap_content"><!--每日一图--><LinearLayoutandroid:background="@color/white"android:paddingLeft="@dimen/dp_16"android:paddingRight="@dimen/dp_16"android:paddingTop="@dimen/dp_8"android:paddingBottom="@dimen/dp_8"android:gravity="center_vertical"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:text="每日一图"android:textColor="@color/black"android:textSize="@dimen/sp_16"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/><com.llw.mvplibrary.view.SwitchButtonandroid:id="@+id/wb_everyday"android:layout_width="wrap_content"android:layout_height="wrap_content"/></LinearLayout><!--透明分割线--><Viewandroid:layout_width="match_parent"android:layout_height="@dimen/dp_1"android:background="@color/transparent"/><!--图片列表--><LinearLayoutandroid:background="@color/white"android:paddingLeft="@dimen/dp_16"android:paddingRight="@dimen/dp_16"android:paddingTop="@dimen/dp_8"android:paddingBottom="@dimen/dp_8"android:gravity="center_vertical"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:text="图片列表"android:textColor="@color/black"android:textSize="@dimen/sp_16"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/><com.llw.mvplibrary.view.SwitchButtonandroid:id="@+id/wb_img_list"android:layout_width="wrap_content"android:layout_height="wrap_content"/></LinearLayout><Viewandroid:layout_width="match_parent"android:layout_height="@dimen/dp_1"android:background="@color/transparent"/><!--手动定义--><LinearLayoutandroid:background="@color/white"android:paddingLeft="@dimen/dp_16"android:paddingRight="@dimen/dp_16"android:paddingTop="@dimen/dp_8"android:paddingBottom="@dimen/dp_8"android:gravity="center_vertical"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:text="手动定义"android:textColor="@color/black"android:textSize="@dimen/sp_16"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/><com.llw.mvplibrary.view.SwitchButtonandroid:id="@+id/wb_custom_"android:layout_width="wrap_content"android:layout_height="wrap_content"/></LinearLayout></LinearLayout>
</LinearLayout>

接下来就是BackgroundManagerActivity.java

逻辑思路

从布局中可以看出来有三个开关按钮,但是只能开一个,并且开启这个的同时要关闭其他的按钮,所以这个有一个相互的监听事件,并且监听事件里面要对这个按钮的是否开启做一个结果存储,这里会用到缓存,缓存是在项目全局中适用的,所以可以跨页面,比如我在这个页面里面开启了每日一图的按钮,存储值为true,然后在回到MainActivity中时,取出这个缓存里面true,然后根据这个值启用每日一图,达到更换壁纸的目的,这是最简单的操作,至于本地更换背景就比较麻烦一点,会需要先在项目的drawable里面先放几张图片,然后通过弹窗来展示这个图片,点击某一个背景则切换成功,然后弹窗关闭,这时候你的图片列表对应的按钮才会开启,加入你打开弹窗之后并没有选中任何图片,则关闭弹窗的同时也会关闭这个按钮,最后一个手动定义,就是打开手机里面图库,任你选择图片,选择之后立即提交上去,这里要先进行动态权限的申请。同意之后打开相册,好了,目前是这样的。
然后开始写代码吧

首先创建两个java类
SPUtils.java

package com.llw.goodweather.utils;import android.content.Context;
import android.content.SharedPreferences;/*** sharepref工具类*/
public class SPUtils {private static final String NAME="config";public static void putBoolean(String key, boolean value, Context ctx) {SharedPreferences sp = ctx.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putBoolean(key, value).commit();}public static boolean getBoolean(String key, boolean defValue, Context ctx) {SharedPreferences sp = ctx.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getBoolean(key, defValue);}public static void putString(String key, String value, Context ctx) {SharedPreferences sp = ctx.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putString(key, value).commit();}public static String getString(String key, String defValue, Context ctx) {if(ctx!=null){SharedPreferences sp = ctx.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getString(key, defValue);}return "";}public static void putInt(String key, int value, Context ctx) {SharedPreferences sp = ctx.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().putInt(key, value).commit();}public static int getInt(String key, int defValue, Context ctx) {SharedPreferences sp = ctx.getSharedPreferences(NAME,Context.MODE_PRIVATE);return sp.getInt(key, defValue);}public static void remove(String key, Context ctx) {SharedPreferences sp = ctx.getSharedPreferences(NAME,Context.MODE_PRIVATE);sp.edit().remove(key).commit();}}

Constant.java

package com.llw.goodweather.utils;/*** 统一管理缓存中对应的KEY*/
public class Constant {public final static int SUCCESS_CODE = 200;public final static String CITY = "city";//市public final static String DISTRICT = "district";//区/县public final static String EVERYDAY_IMG = "everyday_img";//每日图片开关public final static String IMG_LIST = "img_list";//图片列表开关public final static String CUSTOM_IMG = "custom_img";//手动定义开关}

接下来修改mvplibrary中的BaseActivity.java

private static final int FAST_CLICK_DELAY_TIME = 500;
private static long lastClickTime;
//返回
public void Back(Toolbar toolbar){toolbar.setNavigationOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {context.finish();if(!isFastClick()) {context.finish();}}});}// 两次点击间隔不能少于500mspublic static boolean isFastClick() {boolean flag = true;long currentClickTime = System.currentTimeMillis();if ((currentClickTime - lastClickTime) >= FAST_CLICK_DELAY_TIME ) {flag = false;}lastClickTime = currentClickTime;return flag;}

在这里写好之后,到时候在Activity中直接调用即可。然后回到BackgroundManagerActivity

继承BaseActivity之后就可以用它里面的一些方法了。

initSwitchButton方法

 //初始化三个开关按钮  三个只能开一个private void initSwitchButton() {//每日一图按钮开关监听wbEveryday.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(SwitchButton view, boolean isChecked) {if(isChecked){//开SPUtils.putBoolean(Constant.EVERYDAY_IMG,true,context);wbImgList.setChecked(false);wbCustom.setChecked(false);}else {//关SPUtils.putBoolean(Constant.EVERYDAY_IMG,false,context);}}});//图片列表按钮开关监听wbImgList.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(SwitchButton view, boolean isChecked) {if(isChecked){SPUtils.putBoolean(Constant.IMG_LIST,true,context);wbEveryday.setChecked(false);wbCustom.setChecked(false);}else {SPUtils.putBoolean(Constant.IMG_LIST,false,context);}}});//手动定义按钮开关监听wbCustom.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(SwitchButton view, boolean isChecked) {if(isChecked){SPUtils.putBoolean(Constant.CUSTOM_IMG,true,context);wbEveryday.setChecked(false);wbImgList.setChecked(false);}else {SPUtils.putBoolean(Constant.CUSTOM_IMG,false,context);}}});}

然后在MainActivity中指定点击切换背景是页面跳转

现在你就可以运行一下了,一般情况下是不会报错的,运行出来的结果应该是,三个开关按钮,一开始就是默认都没有打开,但是你也只能开一个,开另一个的时候会关闭这个,你可以试一下。
每日一图的比较简单,只要缓存里面放了这个值就行了。
麻烦的是后面这两个

图片列表

首先是六张本地壁纸,放在drawable里,这里我还是贴一下这些壁纸吧
img_1.jpg

img_2.jpg

img_3.jpg

img_4.jpg

img_5.jpg

img_6.jpg

然后创建一个样式文件,用于图片选中后的样式。
现在colors.xml里面增加两个颜色

 <color name="green">#77d034</color><!--绿色--><color name="transparent_bg_white">#22FFFFFF</color><!--白色半透明-->


check_style.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@drawable/check_normal_rb" android:state_checked="false"/><item android:drawable="@drawable/check_checked_rb" android:state_checked="true"/>
</selector>

check_normal_rb.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><stroke android:width="1dip" android:color="@color/transparent" />
</shape>

check_checked_rb.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><!-- 实心 --><solid android:color="@color/transparent_bg_white" /><stroke android:width="2dip" android:color="@color/green" />
</shape>

然后在mvplibrary模块中的build.radle文件中的dependencies{}闭包中增加一个依赖库

 //自由嵌套的RadioGroupapi 'com.github.fodroid:XRadioGroup:v1.5'

然后Sync一下,否则不会生效的。

然后在app的layout下创建一个新的弹窗布局文件window_img_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:background="@color/white"android:layout_width="match_parent"android:layout_height="wrap_content"><!--自由嵌套的RadioGroup--><me.shihao.library.XRadioGroupandroid:id="@+id/xrg_img"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><!--线性布局 一横排三个 里面以权重自适应宽度--><LinearLayoutandroid:padding="8dp"android:layout_width="match_parent"android:layout_height="wrap_content"><RelativeLayoutandroid:layout_marginRight="@dimen/dp_4"android:layout_width="0dp"android:layout_weight="1"android:layout_height="200dp"><ImageViewandroid:layout_margin="5dp"android:background="@drawable/img_1"android:layout_width="match_parent"android:layout_height="match_parent"/><RadioButtonandroid:id="@+id/rb_img_1"android:button="@null"android:background="@drawable/check_style"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout><RelativeLayoutandroid:layout_marginLeft="@dimen/dp_4"android:layout_marginRight="@dimen/dp_4"android:layout_width="0dp"android:layout_weight="1"android:layout_height="200dp"><ImageViewandroid:layout_margin="5dp"android:background="@drawable/img_2"android:layout_width="match_parent"android:layout_height="match_parent"/><RadioButtonandroid:id="@+id/rb_img_2"android:button="@null"android:background="@drawable/check_style"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout><RelativeLayoutandroid:layout_marginLeft="@dimen/dp_4"android:layout_width="0dp"android:layout_weight="1"android:layout_height="200dp"><ImageViewandroid:layout_margin="5dp"android:background="@drawable/img_3"android:layout_width="match_parent"android:layout_height="match_parent"/><RadioButtonandroid:id="@+id/rb_img_3"android:button="@null"android:background="@drawable/check_style"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout></LinearLayout><!--线性布局 一横排三个 里面以权重自适应宽度--><LinearLayoutandroid:padding="8dp"android:layout_width="match_parent"android:layout_height="wrap_content"><RelativeLayoutandroid:layout_marginRight="@dimen/dp_4"android:layout_width="0dp"android:layout_weight="1"android:layout_height="200dp"><ImageViewandroid:layout_margin="5dp"android:background="@drawable/img_4"android:layout_width="match_parent"android:layout_height="match_parent"/><RadioButtonandroid:id="@+id/rb_img_4"android:button="@null"android:background="@drawable/check_style"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout><RelativeLayoutandroid:layout_marginLeft="@dimen/dp_4"android:layout_marginRight="@dimen/dp_4"android:layout_width="0dp"android:layout_weight="1"android:layout_height="200dp"><ImageViewandroid:layout_margin="5dp"android:background="@drawable/img_5"android:layout_width="match_parent"android:layout_height="match_parent"/><RadioButtonandroid:id="@+id/rb_img_5"android:button="@null"android:background="@drawable/check_style"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout><RelativeLayoutandroid:layout_marginLeft="@dimen/dp_4"android:layout_width="0dp"android:layout_weight="1"android:layout_height="200dp"><ImageViewandroid:layout_margin="5dp"android:background="@drawable/img_6"android:layout_width="match_parent"android:layout_height="match_parent"/><RadioButtonandroid:id="@+id/rb_img_6"android:button="@null"android:background="@drawable/check_style"android:layout_width="match_parent"android:layout_height="match_parent"/></RelativeLayout></LinearLayout></me.shihao.library.XRadioGroup>
</LinearLayout>

然后在BackgroundManagerActivity.java中用弹窗显示

 LiWindow liWindow;//弹窗RxPermissions rxPermissions;//权限请求private AnimationUtil animationUtil;//动画工具类private float bgAlpha = 1f;//背景透明度private boolean bright = false;//判断标识private static final long DURATION = 500;//0.5sprivate static final float START_ALPHA = 0.7f;//开始透明度private static final float END_ALPHA = 1f;//结束透明度

找次的弹窗我打算底部显示出现,里面的动画文件早就已经写好了,在前几篇就有,如果你是一步一步跟著我写过来的,那么就不会有问题,现在主要是修改LiWindow.java中的**showBottomPopupWindow()**方法,修改后的代码如下:

     /*** 底部显示* @param mView*/public void showBottomPopupWindow(View mView,PopupWindow.OnDismissListener onDismissListener) {mPopupWindow = new PopupWindow(mView,ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);mPopupWindow.setContentView(mView);mPopupWindow.setOutsideTouchable(true);//点击空白处不关闭弹窗  true为关闭mPopupWindow.setFocusable(true);mPopupWindow.setAnimationStyle(R.style.AnimationBottomFade); //设置动画mPopupWindow.showAtLocation(mView, Gravity.BOTTOM, 0, 0);mPopupWindow.setOnDismissListener(onDismissListener);}

主要是将里面的窗口关闭方法的监听放在里传入参数里,这样既可以在Activity中操作弹窗关闭时的事件了。
接下来是弹窗的使用

 /*** 显示图片弹窗*/private void showImgWindow() {liWindow = new LiWindow(context);final View view = LayoutInflater.from(context).inflate(R.layout.window_img_list, null);XRadioGroup xRadioGroup = (XRadioGroup) view.findViewById(R.id.xrg_img);//显示弹窗的时候,取缓存,判断里面有没有选中过图片int position = SPUtils.getInt(Constant.IMG_POSITION, -1, context);RadioButton rbImg1 = (RadioButton) view.findViewById(R.id.rb_img_1);RadioButton rbImg2 = (RadioButton) view.findViewById(R.id.rb_img_2);RadioButton rbImg3 = (RadioButton) view.findViewById(R.id.rb_img_3);RadioButton rbImg4 = (RadioButton) view.findViewById(R.id.rb_img_4);RadioButton rbImg5 = (RadioButton) view.findViewById(R.id.rb_img_5);RadioButton rbImg6 = (RadioButton) view.findViewById(R.id.rb_img_6);switch (position) {case 0:rbImg1.setChecked(true);break;case 1:rbImg2.setChecked(true);break;case 2:rbImg3.setChecked(true);break;case 3:rbImg4.setChecked(true);break;case 4:rbImg5.setChecked(true);break;case 5:rbImg6.setChecked(true);break;}//xRadioGroup的选中监听xRadioGroup.setOnCheckedChangeListener(new XRadioGroup.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(XRadioGroup xRadioGroup, int i) {//得出选中id对应的RadioButton,从而知道选中的是哪一个,并放入缓存,0~5 switch (xRadioGroup.getCheckedRadioButtonId()) {case R.id.rb_img_1:SPUtils.putInt(Constant.IMG_POSITION, 0, context);liWindow.closePopupWindow();break;case R.id.rb_img_2:SPUtils.putInt(Constant.IMG_POSITION, 1, context);liWindow.closePopupWindow();break;case R.id.rb_img_3:SPUtils.putInt(Constant.IMG_POSITION, 2, context);liWindow.closePopupWindow();break;case R.id.rb_img_4:SPUtils.putInt(Constant.IMG_POSITION, 3, context);liWindow.closePopupWindow();break;case R.id.rb_img_5:SPUtils.putInt(Constant.IMG_POSITION, 4, context);liWindow.closePopupWindow();break;case R.id.rb_img_6:SPUtils.putInt(Constant.IMG_POSITION, 5, context);liWindow.closePopupWindow();break;default:SPUtils.putInt(Constant.IMG_POSITION, 5, context);break;}ToastUtils.showShortToast(context,"已更换壁纸");}});//弹窗关闭监听  弹窗关闭时,如果什么都没有选中,则自然不会有缓存中的取会0~5,所以当为-1时,关闭图片列表的开关PopupWindow.OnDismissListener onDismissListener = new PopupWindow.OnDismissListener() {@Overridepublic void onDismiss() {toggleBright();int position = SPUtils.getInt(Constant.IMG_POSITION, -1, context);if (position != -1) {wbImgList.setChecked(true);} else {wbImgList.setChecked(false);}}};liWindow.showBottomPopupWindow(view, onDismissListener);//显示弹窗 ,传入关闭弹窗监听}/*** 计算动画时间*/private void toggleBright() {// 三个参数分别为:起始值 结束值 时长,那么整个动画回调过来的值就是从0.5f--1f的animationUtil.setValueAnimator(START_ALPHA, END_ALPHA, DURATION);animationUtil.addUpdateListener(new AnimationUtil.UpdateListener() {@Overridepublic void progress(float progress) {// 此处系统会根据上述三个值,计算每次回调的值是多少,我们根据这个值来改变透明度bgAlpha = bright ? progress : (START_ALPHA + END_ALPHA - progress);backgroundAlpha(bgAlpha);}});animationUtil.addEndListner(new AnimationUtil.EndListener() {@Overridepublic void endUpdate(Animator animator) {// 在一次动画结束的时候,翻转状态bright = !bright;}});animationUtil.startAnimator();}/*** 此方法用于改变背景的透明度,从而达到“变暗”的效果*/private void backgroundAlpha(float bgAlpha) {WindowManager.LayoutParams lp = getWindow().getAttributes();// 0.0-1.0lp.alpha = bgAlpha;getWindow().setAttributes(lp);// everything behind this window will be dimmed.// 此方法用来设置浮动层,防止部分手机变暗无效getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);}

注释都写在代码里面了,可能一开始你会看到云里雾里,不过我相信你会明白的,接下来就是这个弹窗的显示调用

到这里图片列表就写完了。你不妨运行一下

手动定义

在Constant.java

红框中为新增的Key

下面的内容会用到一个相机相册的工具类,代码我贴一下:

package com.llw.goodweather.utils;import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.ImageView;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;/*** 相机、相册工具类*/
public class CameraUtils {public static Intent getTakePhotoIntent(Context context, File outputImagepath){//获取系統版本int currentapiVersion = Build.VERSION.SDK_INT;// 激活相机Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);// 判断存储卡是否可以用,可用进行存储if (hasSdcard()) {if (currentapiVersion < 24) {// 从文件中创建uriUri uri = Uri.fromFile(outputImagepath);intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);} else {//兼容android7.0 使用共享文件的形式ContentValues contentValues = new ContentValues(1);contentValues.put(MediaStore.Images.Media.DATA, outputImagepath.getAbsolutePath());Uri uri = context.getApplicationContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);}}return intent;}public static Intent getSelectPhotoIntent(){Intent intent = new Intent("android.intent.action.GET_CONTENT");intent.setType("image/*");return intent;}/** 判断sdcard是否被挂载*/public static boolean hasSdcard() {return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);}/*** 4.4及以上系统处理图片的方法*/@TargetApi(Build.VERSION_CODES.KITKAT)public static String getImgeOnKitKatPath(Intent data, Context context) {String imagePath = null;Uri uri = data.getData();Log.d("uri=intent.getData :", "" + uri);if (DocumentsContract.isDocumentUri(context, uri)) {String docId = DocumentsContract.getDocumentId(uri);        //数据表里指定的行Log.d("getDocumentId(uri) :", "" + docId);Log.d("uri.getAuthority() :", "" + uri.getAuthority());if ("com.android.providers.media.documents".equals(uri.getAuthority())) {String id = docId.split(":")[1];String selection = MediaStore.Images.Media._ID + "=" + id;imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection,context);} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));imagePath = getImagePath(contentUri, null,context);}} else if ("content".equalsIgnoreCase(uri.getScheme())) {imagePath = getImagePath(uri, null,context);}return imagePath;}/*** 通过uri和selection来获取真实的图片路径,从相册获取图片时要用*/public static String getImagePath(Uri uri, String selection, Context context) {String path = null;Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);if (cursor != null) {if (cursor.moveToFirst()) {path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));}cursor.close();}return path;}//改变拍完照后图片方向不正的问题public static void ImgUpdateDirection(String filepath, Bitmap orc_bitmap, ImageView iv) {int digree = 0;//图片旋转的角度//根据图片的URI获取图片的绝对路径Log.i("tag", ">>>>>>>>>>>>>开始");//String filepath = ImgUriDoString.getRealFilePath(getApplicationContext(), uri);Log.i("tag", "》》》》》》》》》》》》》》》" + filepath);//根据图片的filepath获取到一个ExifInterface的对象ExifInterface exif = null;try {exif = new ExifInterface(filepath);Log.i("tag", "exif》》》》》》》》》》》》》》》" + exif);if (exif != null) {// 读取图片中相机方向信息int ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);// 计算旋转角度switch (ori) {case ExifInterface.ORIENTATION_ROTATE_90:digree = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:digree = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:digree = 270;break;default:digree = 0;break;}}//如果图片不为0if (digree != 0) {// 旋转图片Matrix m = new Matrix();m.postRotate(digree);orc_bitmap = Bitmap.createBitmap(orc_bitmap, 0, 0, orc_bitmap.getWidth(),orc_bitmap.getHeight(), m, true);}if (orc_bitmap != null) {iv.setImageBitmap(orc_bitmap);}} catch (IOException e) {e.printStackTrace();exif = null;}}/*** 4.4以下系统处理图片的方法*/public static String getImageBeforeKitKatPath(Intent data, Context context) {Uri uri = data.getData();String imagePath = getImagePath(uri, null,context);return imagePath;}//比例压缩public static Bitmap comp(Bitmap image) {ByteArrayOutputStream baos = new ByteArrayOutputStream();image.compress(Bitmap.CompressFormat.JPEG, 100, baos);if (baos.toByteArray().length / 1024 > 5120) {//判断如果图片大于5M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出baos.reset();//重置baos即清空baosimage.compress(Bitmap.CompressFormat.JPEG, 50, baos);//这里压缩50%,把压缩后的数据存放到baos中}ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());BitmapFactory.Options newOpts = new BitmapFactory.Options();//开始读入图片,此时把options.inJustDecodeBounds 设回true了newOpts.inJustDecodeBounds = true;Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);newOpts.inJustDecodeBounds = false;int w = newOpts.outWidth;int h = newOpts.outHeight;//现在主流手机比较多是800*480分辨率,所以高和宽我们设置为float hh = 800f;//这里设置高度为800ffloat ww = 480f;//这里设置宽度为480f//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可int be = 1;//be=1表示不缩放if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放be = (int) (newOpts.outWidth / ww);} else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放be = (int) (newOpts.outHeight / hh);}if (be <= 0)be = 1;newOpts.inSampleSize = be;//设置缩放比例newOpts.inPreferredConfig = Bitmap.Config.RGB_565;//降低图片从ARGB888到RGB565//重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了isBm = new ByteArrayInputStream(baos.toByteArray());bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);return bitmap;//压缩好比例大小后再进行质量压缩}
}

手动定义,顾名思义,就是打开本地相册,所以首先得版本判断,然后是权限请求,打开相册,返回图片的路径。

 //权限判断private void permissionVersion() {Intent intent = new Intent();if (Build.VERSION.SDK_INT >= 23) {//6.0或6.0以上//动态权限申请permissionsRequest();} else {//6.0以下//发现只要权限在AndroidManifest.xml中注册过,均会认为该权限granted  提示一下即可if (Build.VERSION.SDK_INT <19) {intent.setAction(Intent.ACTION_GET_CONTENT);}else {intent.setAction(Intent.ACTION_OPEN_DOCUMENT);}startActivityForResult(intent, SELECT_PHOTO);}}//动态权限申请private void permissionsRequest() {//使用这个框架需要制定JDK版本,建议用1.8rxPermissions = new RxPermissions(context);rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe(granted -> {if (granted) {//申请成功//得到权限之后打开本地相册Intent selectPhotoIntent = CameraUtils.getSelectPhotoIntent();startActivityForResult(selectPhotoIntent, SELECT_PHOTO);} else {//申请失败wbCustom.setChecked(false);ToastUtils.showShortToast(this, "权限未开启");}});}

返回结果中获取图片地址,然后放入缓存中

 /*** Activity返回结果* @param requestCode* @param resultCode* @param data*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);switch (requestCode) {//打开相册后返回case SELECT_PHOTO:if (resultCode == RESULT_OK) {String imagePath = null;//判断手机系统版本号if (Build.VERSION.SDK_INT > 19) {//4.4及以上系统使用这个方法处理图片imagePath = CameraUtils.getImgeOnKitKatPath(data, this);} else {imagePath = CameraUtils.getImageBeforeKitKatPath(data, this);}displayImage(imagePath);}break;default:break;}}/*** 从相册获取完图片(根据图片路径显示图片)*/private void displayImage(String imagePath) {if (!TextUtils.isEmpty(imagePath)) {//将本地上传选中的图片地址放入缓存,当手动定义开关打开时,取出缓存中的图片地址,显示为背景SPUtils.putString(Constant.CUSTOM_IMG_PATH, imagePath, context);ToastUtils.showShortToast(context,"已更换为你选择的壁纸");} else {wbCustom.setChecked(true);//关闭手动定义开关ToastUtils.showShortToast(context,"图片获取失败");}}


     boolean isEverydayImg = SPUtils.getBoolean(Constant.EVERYDAY_IMG, false, context);//每日图片boolean isImgList = SPUtils.getBoolean(Constant.IMG_LIST, false, context);//图片列表boolean isCustomImg = SPUtils.getBoolean(Constant.CUSTOM_IMG, false, context);//手动定义if (isEverydayImg == true) {wbEveryday.setChecked(true);} else if (isImgList == true) {wbImgList.setChecked(true);} else if (isCustomImg == true) {wbCustom.setChecked(true);}

完成这一步之后,这个BackgroundManagerActivity.java的代码就写完了,但具体的功能还没有完成,因为MainActivity才是显示你这个背景的地方啊。

回来MainActivity.java

这里要聊一下关于Actiivty的生命周期的问题,但是鉴于本篇博客的内容和字数已经够多了,所以我放一个链接Activity生命周期,你可以去看看这个生命周期,否则我后面的内容你就不是很能理解为什么。


然后在跳转壁纸管理页面的时候增加当前页面区/县、市的缓存,当在回到MainActivity时,取出缓存中的值,然后进行接口请求。

 @Overrideprotected void onResume() {super.onResume();showLoadingDialog();//在数据请求之前放在加载等待弹窗,返回结果后关闭弹窗if(district == null){//取出缓存district = SPUtils.getString(Constant.DISTRICT,"",context);city = SPUtils.getString(Constant.CITY,"",context);}isOpenChangeBg();//是否开启了切换背景//获取今天的天气数据mPresent.todayWeather(context, district);//获取天气预报数据mPresent.weatherForecast(context, district);//获取生活指数数据mPresent.lifeStyle(context, district);//获取逐小时天气数据mPresent.hourly(context, district);//获取空气质量数据mPresent.airNowCity(context, city);}//判断是否开启了切换背景,没有开启则用默认的背景private void isOpenChangeBg() {boolean isEverydayImg = SPUtils.getBoolean(Constant.EVERYDAY_IMG,false,context);//每日图片boolean isImgList = SPUtils.getBoolean(Constant.IMG_LIST,false,context);//图片列表boolean isCustomImg = SPUtils.getBoolean(Constant.CUSTOM_IMG,false,context);//手动定义//因为只有有一个为true,其他两个就都会是false,所以可以一个一个的判断if(isEverydayImg != true && isImgList != true && isCustomImg != true){//当所有开关都没有打开的时候用默认的图片bg.setBackgroundResource(R.drawable.pic_bg);}else {if(isEverydayImg!=false){//开启每日一图mPresent.biying(context);}else if(isImgList!=false){//开启图片列表int position = SPUtils.getInt(Constant.IMG_POSITION,-1,context);switch (position){case 0:bg.setBackgroundResource(R.drawable.img_1);break;case 1:bg.setBackgroundResource(R.drawable.img_2);break;case 2:bg.setBackgroundResource(R.drawable.img_3);break;case 3:bg.setBackgroundResource(R.drawable.img_4);break;case 4:bg.setBackgroundResource(R.drawable.img_5);break;case 5:bg.setBackgroundResource(R.drawable.img_6);break;}}else if(isCustomImg != false){String imgPath = SPUtils.getString(Constant.CUSTOM_IMG_PATH,"",context);Glide.with(context).asBitmap().load(imgPath).into(new SimpleTarget<Bitmap>() {@Overridepublic void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {Drawable drawable = new BitmapDrawable(context.getResources(), resource);bg.setBackground(drawable);}});}}}

到这一步,页面就算是真正的写完了。说实话写博客是一个耗费脑力和体力的活,思路很重要啊。

源码地址:GoodWeather
欢迎 StarFork

下一篇:Android 天气APP(十四)修复UI显示异常、优化业务代码逻辑、增加详情天气显示

Android 天气APP(十三)仿微信弹窗(右上角加号点击弹窗效果)、自定义背景图片、UI优化调整相关推荐

  1. Android 天气APP(十一)未来七天的天气预报、逐小时预报、UI优化

    上一篇:Android 天气APP(十)下拉刷新页面天气数据 逐小时天气预报.UI优化 新版------------------- 一.UI优化 二.逐小时天气预报 ① 添加逐小时天气API ② 使用 ...

  2. 仿微信朋友圈图片点击放大效果

    这两天写了个小项目,其中用到了点击一组图片中的一张可以查看它的放大版,用手向左滑动能看到下一张的这组图片中的第二张的放大版...依次类推,单击放大版的图片实现关闭效果,对于js能力不太好的我只能找度娘 ...

  3. Android 天气APP(十四)修复UI显示异常、优化业务代码逻辑、增加详情天气显示

    上一篇:Android 天气APP(十三)仿微信弹窗(右上角加号点击弹窗效果).自定义背景图片.UI优化调整 添加管理城市 新版------------------- 一.添加管理城市页面 二.沉浸式 ...

  4. Android 天气APP(十二)空气质量、UI优化调整

    上一篇:Android 天气APP(十一)未来七天的天气预报.逐小时预报.UI优化 空气质量数据.UI优化 新版------------------- 一.自定义View 二.修改XML布局 三.添加 ...

  5. Android 天气APP(十)继续优化、下拉刷新页面天气数据

    上一篇:Android 天气APP(九)细节优化.必应每日一图 修复每日一图,增加下拉刷新,滑动改变标题 新版------------------- 一.修复每日请求必应壁纸Bug 二.增加下拉刷新 ...

  6. Android 天气APP(三十一)每日提醒弹窗

    上一篇:Android 天气APP(三十)分钟级降水 效果图 每日提醒弹窗 前言 正文 一.弹窗背景 二.每天第一次弹窗 三.弹出每日提示弹窗 四.弹窗的开关 文末 前言   为了增强用户的体验,所以 ...

  7. Android 天气APP(二十三)增加灾害预警、优化主页面UI

    上一篇:Android 天气APP(二十二)改动些许UI.增加更多空气质量数据和生活建议数据展示 文章目录 效果图 前言 一.灾害预警 1.数据实体 2.新增API和方法 3.数据渲染 4.灾害预报详 ...

  8. Android 天气APP(三十四)语音搜索

    上一篇:Android 天气APP(三十三)语音播报 语音搜索 前言 正文 一.权限配置 二.用户体验优化 三.配置语音识别听写 四.语音搜索 五.地图天气添加语音搜索功能 六.城市搜索添加语音搜索功 ...

  9. Android 天气APP(一)开发准备

    好天气APP(天气预报.空气质量.生活建议.灾害预警.出行建议.城市切换.城市搜索.世界国家/地区的城市.常用城市.背景更换.应用自动更新) (运用百度定位.百度地图与和风天气API制作) 演示视频地 ...

最新文章

  1. 信息化道路上,这两家龙头企业做了什么
  2. 基于服务器的AAA作业(第二次)
  3. Spring MVC 单元调试和访问
  4. SQL语法练习 - 使用WITH AS提高性能简化嵌套SQL
  5. 描述符:property 迭代器
  6. 【推荐】让你事半功倍的交互体验自查清单
  7. BZOJ 1500 维修数列
  8. jdk8 接口默认方法_JDK 8中方便的新地图默认方法
  9. autojs遍历当前页面所有控件_PyQT5控件:容器(Containers Widgets)
  10. OpenGL ES 纹理设置
  11. [No0000101]JavaScript-基础课程1
  12. Spring学习(24)--- AOP之 Aspect instantiation models(aspect实例模式)特别说明
  13. java设计模式之模板方法模式(Template Method)
  14. python将excel导入数据库_使用python将excel数据导入数据库过程详解
  15. 软件构造之java类图
  16. [转]如何在NIOS II中读写EPCS剩余空间
  17. Quartus Prime 软件 USB-blaster 驱动安装失败【已解决】
  18. java-锁_自我理解
  19. PAT甲级 1151 LCA in a Binary Tree (30分) LCA算法/C++
  20. 嵌入式和物联网有什么关系?一文教你搞明白。

热门文章

  1. JavaScript字符串操作
  2. Python的excel操作——PasteSpecial实现选择性粘贴自动化
  3. (四)activiti的几个核心类
  4. 【app开发学习】APP开发的标准流程
  5. 大数据技术之电商推荐系统(6) | 基于LFM的离线推荐模块
  6. 如何使用轻量应用服务器搭建高颜值的YesPlayMusic网易云播放器
  7. linux如何给某个文件加密码,如何通过密码保护Linux上的文件夹或目录?
  8. 华为HCIE RS笔记-01以太网基础
  9. 齐岳供应TCPP-Fe(3+)四羧基苯基卟啉铁;TCPP-Zn(2+)四羧基苯基卟啉锌
  10. 用计算机解开手机密码,手机忘记锁屏密码?维修老师傅教你一招,按下这里就能解开...