Android MVVM框架搭建(八)高德地图定位、天气查询、BottomSheetDialog

简介: Android MVVM框架搭建(八)高德地图定位、天气查询、BottomSheetDialog

前言


 在上一篇文章中完成了对个人用户信息的修改,同时讲述了对弹窗数据的处理,权限的使用,本文将在App中接入一个地图SDK,同时完成天气的查询,完成后的效果如下图所示:


f9fe6b435daf4471a3c2452a94013482.gif


正文


 实际上地图和天气我在之前的好天气App上都写过了,那个上面写的就比较多了,也比较复杂了,在这里第一个是为了丰富MVVM的使用场景,同时满足在Fragment中使用地图的需要,第三个就是底部弹窗与MVVM的结合。


可以扫描二维码下载体验一下:


2756fbf10c1c47b59e65ee2c36645055.png


一、集成SDK


 本文中将会使用地图,这里我选择使用高德地图,需要去集成SDK,首先需要登录高德开放平台去注册,创建应用,获取key,由于之前写过这样的文章,因此不用再重复了,不了解的可以看看Android 高德地图API,看完步骤一,拿到了key就可以了,没有问题再回来本文。


4ed921582fc74ed98d287abfd0caf33f.png


点击提交就会有一个key了。


5d8aafae09024a1c84f951f4efe43a8a.png


点击下载SDK,对SDK进行选择,如下图所示:


80df6938522a4c6488cbab60eff89c36.png


点击下载,到本地之后解压,如下图所示:


f8171342a4374c179dbc193ef9dbdb7a.png


将这些文件复制到你的项目的libs下,如下图所示:


a7999c95d6c44c4a88ac02fda043cf93.png


现在这个SDK还没有集成的,你只是放到了项目中,打开app模块下的build.gradle,在android{}闭包下添加如下代码:


defaultConfig {
        ndk {
            //设置支持的SO库架构
            abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }


如下图所示:


8f1008516ab243df9f149b35b4ab6e88.png


然后在dependencies{}闭包下添加如下代码:


//加载lib下的jar文件
    implementation fileTree(include: '*.jar', dir: 'libs')


添加位置如下:这行代码老版本的AS是自带的,后面新版本的没有了。


dee6cc3a3643415a9f3037f04c220546.png


然后点击右上角的Sync Now,进行依赖库下载配置同步,此时你的jar包可以展开了,就说明SDK集成成功了。


二、基础配置


 SDK已经引入,要使用还需要进行一些配置,首先是权限配置。


① 权限配置


在AndroidManifest.xml下新增如下权限:


<!--允许获取粗略位置-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!--允许获取精准位置-->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <!--允许获取wifi状态改变,用于网络定位-->
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />


其他的所需权限,在之前写相应功能的时候都已经添加好了,因此就不用重复添加了,添加位置如下图所示:


f77f7bf1ba9149138686bb4eb8c0bf60.png


下面配置Key


② 配置Key


然后配置高德地图Key,在application标签内


<!--高德地图Key-->
        <meta-data
            android:name="com.amap.api.v2.apikey"
            android:value="597d296d723c5adb7165b732bc6f831e"/>


添加位置如下图所示:


757a69f414a04de0842ce545a534e47b.png


现在来说,前期的准备工作就差不多了,下面就要去实现地图显示和定位了。


三、显示地图


 我们让地图显示在HomeActivity上,因此我们需要创建一个MapFragment去加载地图,一些读者只看到我用Activity加载过地图,换到Fragment上就不会用了,我很心痛啊。痛定思痛之后,我决定在Fragment上演示一下地图怎么操作,当然这是很简单的,我也会慢慢提高难度,你准备好了吗?


① MapFragment


首先在fragment包下新建一个MapFragment,对应的布局是map_fragment.xml,布局代码如下:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.fragment.MapFragment">
        <com.amap.api.maps.MapView
            android:id="@+id/map_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </FrameLayout>
</layout>


很简单,就一个地图。下面回到MapFragment,里面的代码如下:


public class MapFragment extends BaseFragment {
    private MapFragmentBinding binding;
    public static MapFragment newInstance() {
        return new MapFragment();
    }
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(inflater,R.layout.map_fragment,container,false);
        return binding.getRoot();
    }
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // TODO: Use the ViewModel
    }
}


这里只是简单的继承了一下BaseFragment,然后就是DataBinding的绑定。


② Navigation绑定


然后我们需要在nav_graph.xml中添加这个布局。


<!--地图-->
    <fragment
        android:id="@+id/map_fragment"
        android:name="com.llw.mvvm.ui.fragment.MapFragment"
        android:label="map_fragment"
        tools:layout="@layout/map_fragment" />


 这里配置了,那么底部的菜单同样要配置,毕竟我们是通过菜单去控制Fragment的切换的,打开navigation_menu.xml,在里面添加一个item,代码如下:


<item
        android:id="@+id/map_fragment"
        android:icon="@mipmap/ic_map"
        android:title="地图" />


这个图标可以去我的源码里面去找,或者自己去网上找一个也行。


下面进入到HomeActivity中去配置,配置切换菜单时的Fragment布局改变,如下图所示:


6242413214c041a886f216c827ad1ee8.png


③ Fragment中地图生命周期绑定


要显示地图需要将地图的生命周期与Fragment的生命周期绑定起来,如下图所示:


314dce0d16944d75939f14adaabd802d.png


如果你是线上的项目你需要在隐私政策中引入高德SDK的说明,然后在MapFragment中绑定地图的生命周期。


@Override
    public void onSaveInstanceState(@NonNull @NotNull Bundle outState) {
        super.onSaveInstanceState(outState);
        binding.mapView.onSaveInstanceState(outState);
    }
  @Override
    public void onResume() {
        super.onResume();
        binding.mapView.onResume();
    }
    @Override
    public void onPause() {
        super.onPause();
        binding.mapView.onPause();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        binding.mapView.onDestroy();
    }


下面运行一下:


2b3ecabed23d441585cefcb3d414444c.png


现在只是显示了地图,但是并没有定位到我当前所在地,这当然是不行的。


四、显示当前所在地


 显示当前所在地则需要定位权限,之前在AndroidManifest.xml中已经配置好了,下面则需要在代码中动态请求。


① 定位动态权限申请


在上一篇文章中写过一个PermissionUtils类,这里给这个类再加一点东西进去,在PermissionUtils中增加如下代码:


public static final String LOCATION = Manifest.permission.ACCESS_FINE_LOCATION;
    public static final int REQUEST_LOCATION_CODE = 1003;


然后在getPermissionRequestCode方法中增加一个case,如下图所示:


0d9704fd0d0044649b610f26e9b269ca.png


下面就是在HomeActivity中请求动态权限了,在HomeActivity中新增如下方法:


/**
     * 请求定位权限
     */
    private void requestLocation() {
        if (isAndroid6()) {
            if (!hasPermission(PermissionUtils.LOCATION)) {
                requestPermission(PermissionUtils.LOCATION);
            }
        } else {
            showMsg("您无需动态请求权限");
        }
    }


然后在initView方法中调用它,如下图所示:


76dcae0998c647cbb752c1014b2bb06e.png


② 地图定位当前所在地


下面回到MapFragment,新增如下代码:


private static final String TAG = MapFragment.class.getSimpleName();
  /**
     * 初始化地图
     */
    private void initMap() {
        //初始化地图控制器对象
        AMap aMap = binding.mapView.getMap();
        // 设置为true表示显示定位层并可触发定位,false表示隐藏定位层并不可触发定位,默认是false
        aMap.setMyLocationEnabled(true);
        MyLocationStyle style = new MyLocationStyle();//初始化定位蓝点样式类myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);//连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动。(1秒1次定位)如果不设置myLocationType,默认也会执行此种模式。
        style.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATE) ;//定位一次,且将视角移动到地图中心点。
        aMap.setMyLocationStyle(style);//设置定位蓝点的Style
        aMap.getUiSettings().setMyLocationButtonEnabled(true);//设置默认定位按钮是否显示,非必需设置。
        aMap.setMyLocationEnabled(true);// 设置为true表示启动显示定位蓝点,false表示隐藏定位蓝点并不进行定位,默认是false。
        //设置SDK 自带定位消息监听
        aMap.setOnMyLocationChangeListener(this);
    }
    @Override
    public void onMyLocationChange(Location location) {
        // 定位回调监听
        if(location != null) {
            Log.e(TAG, "onMyLocationChange 定位成功, lat: " + location.getLatitude() + " lon: " + location.getLongitude());
        } else {
            Log.e(TAG, "定位失败");
        }
    }


实现位置监听


98b8bc4d58dd4f0f98fb0494dd60865b.png


调用initMap()


5545d90599804722ab9d22921f25b84b.png


运行一下


f0d370a543c740cfb6bafe9b2a41c41c.gif


五、获取详细的地址信息


 通过地图定位的方式定位在了当前所在地,但是onMyLocationChange的location中只有当前所在地的经纬度,如果我们需要拿到详细的地址信息要怎么做呢?通过SearchSDK实现,通过逆地理编码来将坐标转换成详细的地址。依然是在MapFragment,继承GeocodeSearch.OnGeocodeSearchListener,实现两个回调方法。代码如下:


/**
     * 坐标转地址
     */
    @Override
    public void onRegeocodeSearched(RegeocodeResult regeocodeResult, int rCode) {
    }
    /**
     * 地址转坐标
     */
    @Override
    public void onGeocodeSearched(GeocodeResult geocodeResult, int rCode) {
    }


然后这个也需要初始化,代码如下:


//解析成功标识码
    private static final int PARSE_SUCCESS_CODE = 1000;
    private GeocodeSearch geocoderSearch = null;
    private String district = null;// 区/县


下面写一个方法初始化搜索


/**
     * 初始化搜索
     */
    private void initSearch() {
        try {
            geocoderSearch = new GeocodeSearch(requireActivity());
            geocoderSearch.setOnGeocodeSearchListener(this);
        } catch (AMapException e) {
            e.printStackTrace();
        }
    }


调用的地方如下图:


54729f30c50940a2981da66dd7cea952.png


当收位置信息改变时,进行坐标的搜索,在onMyLocationChange中添加如下代码:


//创建一个经纬度点,参数一是纬度,参数二是经度
            LatLonPoint latLonPoint = new LatLonPoint(location.getLatitude(), location.getLongitude());
            // 第一个参数表示一个Latlng,第二参数表示范围多少米,第三个参数表示是火系坐标系还是GPS原生坐标系
            RegeocodeQuery query = new RegeocodeQuery(latLonPoint, 20, GeocodeSearch.AMAP);
            //通过经纬度获取地址信息
            geocoderSearch.getFromLocationAsyn(query);


添加位置如下:


58d1b62e6e794259b50eeb7cdbdaaacf.png


 然后就会触发onRegeocodeSearched的回调,在onRegeocodeSearched中则对所在地的信息进行打印和简单的区/县赋值,在onRegeocodeSearched方法中添加如下代码:


//解析result获取地址描述信息
        if (rCode == PARSE_SUCCESS_CODE) {
            RegeocodeAddress regeocodeAddress = regeocodeResult.getRegeocodeAddress();
            //显示解析后的地址
            Log.e(TAG, "地址: " + regeocodeAddress.getFormatAddress());
            district = regeocodeAddress.getDistrict();
            Log.e(TAG, "区: " + district);
        } else {
            showMsg("获取地址失败");
        }


添加位置如下:


6d043432b33445df973031fb17abcb1e.png


下面运行一下:


490f5ad6a30843bda66a46b193577a80.png


这样就拿到了详细的位置信息。


六、获取天气数据


 高德是自带了天气数据接口的,可以用,只不过数据不是很多,如果需要更多的数据的话可以自己去对接天气API,例如和风、彩云。

在MapFragment创建变量


private LocalWeatherLive liveResult;
    private LocalWeatherForecast forecastResult;


然后MapFragment继承WeatherSearch.OnWeatherSearchListener,实现两个方法。


/**
     * 实时天气返回
     */
    @Override
    public void onWeatherLiveSearched(LocalWeatherLiveResult localWeatherLiveResult, int code) {
        liveResult = localWeatherLiveResult.getLiveResult();
        if (liveResult != null) {
            Log.e(TAG, "onWeatherLiveSearched: " + new Gson().toJson(liveResult));
        } else {
            showMsg("实时天气数据为空");
        }
    }
    /**
     * 天气预报返回
     */
    @Override
    public void onWeatherForecastSearched(LocalWeatherForecastResult localWeatherForecastResult, int code) {
        forecastResult = localWeatherForecastResult.getForecastResult();
        if (forecastResult != null) {
            Log.e(TAG, "onWeatherForecastSearched: " + new Gson().toJson(forecastResult));
        } else {
            showMsg("天气预报数据为空");
        }
    }


在方法回调中打印一下返回的数据,然后写一个搜索天气的方法,根据传入不同的天气类型,进行不同的天气数据搜索,代码如下:


/**
     * 搜索天气
     *
     * @param type WEATHER_TYPE_LIVE 实时天气   WEATHER_TYPE_FORECAST  预报天气
     */
    private void searchWeather(int type) {
        WeatherSearchQuery weatherSearchQuery = new WeatherSearchQuery(district, type);
        try {
            WeatherSearch weatherSearch = new WeatherSearch(requireActivity());
            weatherSearch.setOnWeatherSearchListener(this);
            weatherSearch.setQuery(weatherSearchQuery);
            weatherSearch.searchWeatherAsyn(); //异步搜索
        } catch (AMapException e) {
            e.printStackTrace();
        }
    }


最后在onRegeocodeSearched中,拿到地址信息时调用searchWeather方法,代码如下:


//搜索天气  实时天气和预报天气
            searchWeather(WeatherSearchQuery.WEATHER_TYPE_LIVE);
            searchWeather(WeatherSearchQuery.WEATHER_TYPE_FORECAST);

 


添加位置如下:


3441c4a291294b62a9672c232d3e4051.png


下面运行一下,查看日志,天气的数据就有了


9c67df4366c644928028ab4f1670c327.png


七、显示天气数据


 有了天气数据之后就是显示天气数据了,这里我们可以这么做,就是在MapFragment中添加一个浮动按钮,点击之后从屏幕底部弹出一个,先来修改一下map_fragment中的代码,我们增加一个浮动按钮。


<com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab_weather"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|bottom"
            android:layout_margin="20dp"
            android:visibility="gone"
            android:contentDescription="天气"
            android:src="@mipmap/ic_weather"
            app:backgroundTint="@color/white"
            app:fabSize="auto"
            tools:ignore="UsingOnClickInXml" />


添加的位置就如下图所示:


1311069bbef64c40b3b7a443a85b9516.png


 这里我先把这个按钮给隐藏了,当地图上获取了当前的位置,查询到了天气之后再显示这个按钮。下面回到MapFragment中,在initMap方法中增加一行代码,


    //修改放大缩小按钮的位置
        aMap.getUiSettings().setZoomPosition(AMapOptions.ZOOM_POSITION_RIGHT_CENTER);

 

 

这样做就是让我们的浮动按钮不至于挡住这个地图的放大缩小按钮。


在MapFragment中添加一个变量


//天气预报列表
    private List<LocalDayWeatherForecast> weatherForecast;


然后在onWeatherForecastSearched回调中对找个变量赋值,这才是实际的天气数据


8d0822ca912a47cdad89da7b99b98fed.png


查询到天气预报数据后,显示这个按钮。


① 实时天气数据


 现在已经可以看到所在地的天气了,当需要显示出来的时候你会发现找个数据里面是没有所在地的区/县的,只有省和市。因此在model包下新建一个LiveWeather,把我们在通过你地理编码返回时的区/县的值放进去,代码如下:


public class LiveWeather {
    private String district;
    private LocalWeatherLive localWeatherLive;
    public LiveWeather(String district, LocalWeatherLive localWeatherLive) {
        this.district = district;
        this.localWeatherLive = localWeatherLive;
    }
    public String getDistrict() {
        return district;
    }
    public void setDistrict(String district) {
        this.district = district;
    }
    public LocalWeatherLive getLocalWeatherLive() {
        return localWeatherLive;
    }
    public void setLocalWeatherLive(LocalWeatherLive localWeatherLive) {
        this.localWeatherLive = localWeatherLive;
    }
}


这个数据将会绑定到我们的天气弹窗,现在来创建这个弹窗的布局。


② 天气弹窗布局


 弹窗布局分为两个环节,一个是实时天气,一个是预报天气。首先colors.xml创建一个颜色值,如下:


<color name="translucent">#90000000</color>


然后在drawable下创建一个shape_translucent_radius_12.xml样式文件,代码如下:


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="12dp"/>
    <solid android:color="@color/translucent"/>
</shape>


下面创建弹窗的布局,在layout下新建一个dialog_weather.xml,里面的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="liveWeather"
            type="com.llw.mvvm.model.LiveWeather" />
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#00000000"
        android:orientation="vertical"
        android:paddingStart="12dp"
        android:paddingEnd="12dp"
        android:paddingBottom="?attr/actionBarSize">
        <TextView
            android:id="@+id/tv_city"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="12dp"
            android:background="@drawable/shape_translucent_radius_12"
            android:gravity="center"
            android:padding="12dp"
            android:text="@{liveWeather.district}"
            android:textColor="@color/white"
            android:textSize="28sp" />
        <TextView
            android:id="@+id/tv_weather"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/tv_report_time"
            android:layout_below="@+id/tv_city"
            android:layout_alignParentStart="true"
            android:layout_marginEnd="12dp"
            android:layout_marginBottom="12dp"
            android:background="@drawable/shape_translucent_radius_12"
            android:gravity="center"
            android:padding="12dp"
            android:text="@{liveWeather.localWeatherLive.weather}"
            android:textColor="@color/white"
            android:textSize="24sp" />
        <LinearLayout
            android:id="@+id/wind_lay"
            android:layout_width="wrap_content"
            android:layout_height="44dp"
            android:layout_below="@+id/tv_city"
            android:layout_toStartOf="@+id/tv_temp"
            android:layout_toEndOf="@+id/tv_weather"
            android:background="@drawable/shape_translucent_radius_12"
            android:gravity="center"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_wind_direction"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="12dp"
                android:text="@{liveWeather.localWeatherLive.windDirection+`风`}"
                android:textColor="@color/white" />
            <TextView
                android:id="@+id/tv_wind_power"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="12dp"
                android:text="@{liveWeather.localWeatherLive.windPower+`级`}"
                android:textColor="@color/white" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/humidity_lay"
            android:layout_width="wrap_content"
            android:layout_height="44dp"
            android:layout_below="@+id/wind_lay"
            android:layout_marginTop="12dp"
            android:layout_toStartOf="@+id/tv_temp"
            android:layout_toEndOf="@+id/tv_weather"
            android:background="@drawable/shape_translucent_radius_12"
            android:gravity="center"
            android:orientation="horizontal">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="24dp"
                android:text="湿度"
                android:textColor="@color/white" />
            <TextView
                android:id="@+id/tv_humidity"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{liveWeather.localWeatherLive.humidity+`%`}"
                android:textColor="@color/white" />
        </LinearLayout>
        <TextView
            android:id="@+id/tv_temp"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_below="@+id/tv_city"
            android:layout_alignParentEnd="true"
            android:layout_marginStart="12dp"
            android:layout_marginBottom="12dp"
            android:background="@drawable/shape_translucent_radius_12"
            android:gravity="center"
            android:padding="12dp"
            android:text="@{liveWeather.localWeatherLive.temperature+`℃`}"
            android:textColor="@color/white"
            android:textSize="32sp" />
        <TextView
            android:id="@+id/tv_report_time"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_temp"
            android:background="@drawable/shape_translucent_radius_12"
            android:gravity="center"
            android:padding="12dp"
            android:text="@{liveWeather.localWeatherLive.reportTime+`发布`}"
            android:textColor="@color/white" />
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_forecast"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_report_time"
            android:layout_marginTop="12dp" />
    </RelativeLayout>
</layout>


有列表就有对应的item布局,在layout下创建item_forecast.xml布局,代码如下:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="forecast"
            type="com.amap.api.services.weather.LocalDayWeatherForecast" />
        <!--引入一个工具类-->
        <import type="com.llw.mvvm.utils.EasyDate" />
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="12dp"
        android:background="@drawable/shape_translucent_radius_12"
        android:padding="12dp">
        <TextView
            android:id="@+id/tv_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{forecast.date}"
            android:textColor="@color/white" />
        <TextView
            android:id="@+id/tv_week"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:layout_toEndOf="@+id/tv_date"
            android:text="@{EasyDate.getWeek(forecast.date)}"
            android:textColor="@color/white" />
        <TextView
            android:id="@+id/tv_temp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:text="@{forecast.dayTemp+`° /`+ forecast.nightTemp+`°`}"
            android:textColor="@color/white" />
    </RelativeLayout>
</layout>


 这里我引入的数据源是高德的,同时我还插入了一个工具类,这个工具类用于对日期进行处理,可以直接在xml中调用,例如将2021-12-22,转换成星期三。


布局都写好了,下面在themes.xml中增加一个样式,代码如下:


<style name="BottomSheetDialogStyle_Light" parent="Theme.Design.BottomSheetDialog">
        <item name="android:windowFrame">@null</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:background">@android:color/transparent</item>
        <item name="android:backgroundDimEnabled">false</item>
    </style>


这表示底部弹窗出现是不会使我们的屏幕变暗。


③ BottomSheetDialog使用


在写之前,我们先写一个ForecastAdapter,这是天气预报的列表适配器,在adapter包下创建它,代码如下:


public class ForecastAdapter extends BaseQuickAdapter<LocalDayWeatherForecast, BaseDataBindingHolder<ItemForecastBinding>> {
    public ForecastAdapter(@Nullable List<LocalDayWeatherForecast> data) {
        super(R.layout.item_forecast, data);
    }
    @Override
    protected void convert(@NotNull BaseDataBindingHolder<ItemForecastBinding> bindingHolder, LocalDayWeatherForecast localDayWeatherForecast) {
        ItemForecastBinding binding = bindingHolder.getDataBinding();
        if (binding != null) {
            binding.setForecast(localDayWeatherForecast);
            binding.executePendingBindings();
        }
    }
}


下面在MapFragment中添加一个方法,代码如下:


/**
     * 显示天气弹窗
     */
    private void showWeatherDialog() {
        //隐藏浮动按钮
        binding.fabWeather.hide();
        BottomSheetDialog dialog = new BottomSheetDialog(requireActivity(), R.style.BottomSheetDialogStyle_Light);
        DialogWeatherBinding weatherBinding = DataBindingUtil.inflate(LayoutInflater.from(requireActivity()), R.layout.dialog_weather, null, false);
        //设置数据源
        weatherBinding.setLiveWeather(new LiveWeather(district,liveResult));
        //配置天气预报列表
        ForecastAdapter forecastAdapter = new ForecastAdapter(weatherForecast);
        weatherBinding.rvForecast.setLayoutManager(new LinearLayoutManager(requireActivity()));
        weatherBinding.rvForecast.setAdapter(forecastAdapter);
        dialog.setContentView(weatherBinding.getRoot());
        dialog.getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundColor(Color.TRANSPARENT);
        //弹窗关闭时显示浮动按钮
        dialog.setOnDismissListener(dialog1 -> binding.fabWeather.show());
        dialog.show();
    }


 当点击浮动按钮的时候就会出现这个弹窗,出现弹窗后就隐藏浮动按钮,然后我们对弹窗的样式进行了修改,同时绑定数据,设置数据到xml中,在再配置列表数据和适配器,最后是设置背景透明,以及弹窗消失时显示浮动按钮。


最后在onActivityCreated的方法中进行浮动按钮的点击事件处理,代码如下:


//点击按钮显示天气弹窗
        binding.fabWeather.setOnClickListener(v -> showWeatherDialog());

8d6030c2d3874d6f9d8a2a9eacee0b74.png


下面来运行一下:


f9fe6b435daf4471a3c2452a94013482.gif


效果就是这个样子,应该是比较的明显了。


相关文章
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
13天前
|
算法 JavaScript Android开发
|
22天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
88 1
|
2月前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
169 4
Android性能测试——发现和定位内存泄露和卡顿
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
346 3
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
77 8
|
3月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
4天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
6天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。