Android 天气APP(二十八)地图搜索定位

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Android 天气APP(二十八)地图搜索定位

开发流程


一、前情提要

二、修改布局和优化业务


效果图


20200907110032961.gif


一、前情提要


 在我写完地图天气之后就有一种如释重负的感觉,但是这种感觉没有保持多久,就被新的需求功能所取代。因为我会让我身边的朋友帮忙测试使用,并提一些建议,我来决定是否汲取,这一次我收到了一个很好的建议,所以就有了这一篇文章,当然在我写的时候,功能就已经是完成了的,需求是这样的,之前的地图是通过手动点击地图然后定位到某一个点,然后获取天气信息,那么很多人一进入这个页面并不知道地图可以点击,那么这个时候该怎么去定位呢?于是就想到有一个地方能够让用户去输入,输入城市名之后,定位到这个城市,然后获取城市天气,功能就是这样,说起来是比较简单的功能,但是做起来可就不那么容易了,因为我是比较在意用户体验的,所以有的地方比较的执着,至于为什么?进入正题吧。

二、修改布局和优化业务


先来优化业务吧。

进入MapWeatherActivity找到onGetReverseGeoCodeResult方法返回


20200904171903516.png


这样就算是优化了一下下,OK开始修改布局了,布局会用到一个背景样式和两个图标

shape_search_bg.xml


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


图标如下:


20200904172445745.png

20200904172445739.png

202009041725563.png


下面来看看新增的布局具体的代码


<!--顶部搜索布局-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:paddingLeft="@dimen/dp_12"
        android:paddingTop="@dimen/dp_28"
        android:paddingRight="@dimen/dp_12">
        <RelativeLayout
            android:id="@+id/lay_search"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="right"
            android:background="@drawable/shape_search_bg">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:orientation="horizontal">
                <ImageView
                    android:id="@+id/iv_search"
                    android:layout_width="36dp"
                    android:layout_height="36dp"
                    android:padding="6dp"
                    android:src="@mipmap/icon_search" />
                <EditText
                    android:id="@+id/ed_search"
                    android:layout_width="@dimen/dp_0"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:background="@null"
                    android:ems="8"
                    android:hint="输入城市名"
                    android:imeOptions="actionSearch"
                    android:padding="@dimen/dp_4"
                    android:singleLine="true"
                    android:textColor="@color/black"
                    android:textCursorDrawable="@drawable/cursor_style"
                    android:textSize="@dimen/sp_14"
                    android:visibility="gone" />
                <ImageView
                    android:id="@+id/iv_close"
                    android:layout_width="36dp"
                    android:layout_height="36dp"
                    android:padding="8dp"
                    android:src="@mipmap/icon_close"
                    android:visibility="gone" />
            </LinearLayout>
        </RelativeLayout>
    </LinearLayout>


还是比较简单的,然后进入到MapWeatherActivity


  ImageView ivSearch;//搜索图标
    @BindView(R.id.ed_search)
    EditText edSearch;//搜索输入框
    @BindView(R.id.iv_close)
    ImageView ivClose;//关闭图标
    @BindView(R.id.lay_search)
    RelativeLayout laySearch;//搜索布局


定义一个控制变量

private AutoTransition autoTransition;//过渡动画
private Animation bigShowAnim;//放大显示
private Animation smallHideAnim;//缩小隐藏
private int width;//屏幕宽度
private boolean isOpen = false;//顶部搜索布局的状态


然后在点击方法中增加需求点击的控件id


20200907094144740.png


因为展开之后是不能设置固定的宽度,所以需要获取屏幕的宽度,在initView方法中写入

     //获取屏幕宽高
        WindowManager manager = getWindowManager();
        DisplayMetrics metrics = new DisplayMetrics();
        manager.getDefaultDisplay().getMetrics(metrics);
        width = metrics.widthPixels;  //获取屏幕的宽度 像素


而屏幕的宽度是px又需要转换为dip,所以要写转换的方法;

  // dp 转成 px
    private int dip2px(float dpVale) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dpVale * scale + 0.5f);
    }
    // px 转成 dp
    private int px2dip(float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }


然后写一个过渡动画的方法,会在展开和收缩的方法中调用

  //过渡动画
    @TargetApi(Build.VERSION_CODES.KITKAT)
    private void beginDelayedTransition(ViewGroup view) {
        autoTransition = new AutoTransition();
        autoTransition.setDuration(500);
        TransitionManager.beginDelayedTransition(view,autoTransition);
    }


现在可以来写具体的关于点击展开和收缩的方法了。

  /**
     * 展开
     */
    public void initExpand() {
        isOpen = true;
        edSearch.setVisibility(View.VISIBLE);//显示输入框
        ivClose.setVisibility(View.VISIBLE);//显示关闭按钮
        LinearLayout.LayoutParams LayoutParams = (LinearLayout.LayoutParams) laySearch.getLayoutParams();
        LayoutParams.width = dip2px(px2dip(width) - 24);//设置展开的宽度
        LayoutParams.setMargins(dip2px(0), dip2px(0), dip2px(0), dip2px(0));
        laySearch.setPadding(14, 0, 14, 0);
        laySearch.setLayoutParams(LayoutParams);
        //开始动画
        beginDelayedTransition(laySearch);
        if (markerLatitude != 0) {//手动定位时
            btnAutoLocation.hide();//隐藏自动定位按钮
        }
    }


这里做一下简单的说明LayoutParams.width = dip2px(px2dip(width) - 24);//设置展开的宽度这里我先将屏幕的宽由px转dp,然后剪去24,24就是屏幕左右各12的边距,然后再转成px赋值给LayoutParams.width,这样LayoutParams就知道我这个控件到时候要展开多大了。而我在开始动画的时候也加了一个对于定位按钮的判断,因为这个控件和定位按钮在同一水平线上,又因为底层的布局用的是FrameLayout,所以会出现覆盖的情况,这并不是我想要的,所以我加了一个控制,如果展开的时候处于手动定位则隐藏自动定位按钮,当然我也在收缩的方法里面做了相应的处理,下面来看收缩的方法。


    /**
     * 收缩
     */
    private void initClose() {
        isOpen = false;
        edSearch.setVisibility(View.GONE);
        edSearch.setText("");
        ivClose.setVisibility(View.GONE);
        LinearLayout.LayoutParams LayoutParams = (LinearLayout.LayoutParams) laySearch.getLayoutParams();
        LayoutParams.width = dip2px(48);
        LayoutParams.height = dip2px(48);
        LayoutParams.setMargins(dip2px(0), dip2px(0), dip2px(0), dip2px(0));
        laySearch.setLayoutParams(LayoutParams);
        //隐藏键盘
        InputMethodManager inputMethodManager = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(this.getWindow().getDecorView().getWindowToken(), 0);
        //开始动画
        beginDelayedTransition(laySearch);
        if (markerLatitude != 0) {//自动定位
            btnAutoLocation.show();//隐藏自动定位按钮
        }
    }


当然我们需要在点击的时候调用这两个方法


20200907102314972.png


收缩的方法比较的简单一些,加了收缩时关闭键盘的动作。下面演示一下


image.gif


重点注意看上边的效果。下面要对输入框做一下简单的控制,

在initView中增加


    /**
         * 输入法键盘的搜索监听
         */
        edSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    String city = edSearch.getText().toString();
                    if (!TextUtils.isEmpty(city)) {
                        //得到输入的内容
                    } else {
                        ToastUtils.showShortToast(context, "请输入城市名称");
                    }
                }
                return false;
            }
        });


这里其实就是对输入法简单的回车按钮的监听,这也是现在很多app的通用做法,不需要再自己去写一个搜索按钮来控制,而使用输入法的回车键作为搜索按钮,那么是怎么改的呢?通过布局


20200907103033648.png


 相信你已经知道了,好的,我在点击搜索的时候获取输入框的内容,为空则提示一下,不为空就获取内容,那么获取到的内容要怎么办呢?首先想一下获取到的是什么内容?当然是城市的名称了,可以是区/县、市。那么得到这个数据就去直接通过搜索城市拿到城市id,再通过城市id去请求天气数据吗?这样是可以的,但是忽略了地图,你不要忘记了,这个页面是地图天气,所以要和地图有联动才行啊。回想一下之前我通过定位获取到坐标,通过坐标拿到了城市的具体信息,那么反过来通过地址信息,拿到坐标,再把坐标渲染在地图上,通过定位到这个坐标,听起来是不是觉得比较难?实际上真的难吗?你只要想清楚逻辑,剩下的就是实现而已了。下面来看看怎么实现的吧。


20200907103717656.png


通过这个做解析,不光是坐标转地址,也可以地址转坐标


20200907103822237.png


在输入法搜索按钮点击后,获取到的内容不为空则进行地址的解析,new一个GeoCodeOpting(),传入城市和地址,这里可以传同样的值。那么解析的结果呢?


20200907104254381.png


 要知道我之前就给这个编码结果做了监听,当时只在onGetReverseGeoCodeResult中做了处理,因为这个返回是负责坐标转地址的。而onGetGeoCodeResult是负责地址转坐标的。很好,这正是我想要的。我在返回值中做了一些简单的处理,如果解析不到数据我们就认定你输入的城市名有问题,提示你一下并清空这个输入框让你重新输入,如果输入的内容没啥问题,我们就获取坐标,然后打印出地址和经纬度。你可以自己运行试一下绝对就是这样的,OK,既然现在拿到了坐标那就可以定位了对不对,先来看看之前的地图上手动定位绘制标点的代码


20200907104937830.png


这里你还记得吗?在点击地图是重新绘制,然后标点,最后重新定位的,那么你同样可以把这一段代码复制过去那边,就可以了,不过为了不写重新代码,可以写一个方法两个地方使用,反正你只要传入一个坐标的对象就可以了,不是吗?方法如下:


  /**
     * 重新定位
     *
     * @param latLng 坐标
     */
    private void resetLocation(LatLng latLng) {
        bitmap = BitmapDescriptorFactory.fromResource(R.mipmap.icon_marka);// 设置marker图标
        //通过LatLng获取经纬度
        markerLatitude = latLng.latitude;//获取纬度
        markerLongitude = latLng.longitude;//获取经度
        mBaiduMap.clear();//清除之前的图层
        MarkerOptions options = new MarkerOptions()//创建标点marker设置对象
                .position(latLng)//设置标点的定位
                .icon(bitmap);//设置标点图标
        marker = (Marker) mBaiduMap.addOverlay(options);//在地图上显示标点
        //重新定位
        initLocation();
    }


调用


20200907105153369.png


然后再回到之前通过位置获取到坐标的那个返回方法里调用即可


20200907105323868.png


这里我还多加了一个关闭搜索布局的方法代码。来运行一下吧。



你以为这就完了吗?当然没有!我真是猜不透我自己啊!哈哈哈哈!

OK,我们还需要与这个底部控件做协调,比如我们之前有过这样一个操作就是当手动定位时,拖动底部布局到顶部然后隐藏这个按钮,回到底部时显示这个按钮,那么同理我是不是也应该对这个搜索布局做同样的事呢?当然了,当然了,裤衩着火,裆燃了。最近相声听得比较多,学到了这么一句俏皮话。

好的,继续往下看啊,因为我用的不是浮动按钮,所以就没有默认的动画了,那么就需要自己来写动画效果,这个其实也不难啊。


20200907111004718.png


在mvplibrary中的anim包下新建两个动画xml文件

scale_big_expand.xml


<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:fromXScale="0"
    android:fromYScale="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1"
    android:toYScale="1" />


scale_small_close.xml

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:fromXScale="1"
    android:fromYScale="1"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="0"
    android:toYScale="0" />


然后回到MapWeatherActivity,在initView中增加

    //放大
        bigShowAnim = AnimationUtils.loadAnimation(context, R.anim.scale_big_expand);
        //缩小
        smallHideAnim = AnimationUtils.loadAnimation(context, R.anim.scale_small_close);


可以看到这里加了动画的xml,下面就要写一个方法用于控制显示和隐藏分别调用不用的动画,方法如下:

  /**
     * 缩放动画
     * @param view 需要缩放的控件
     * @param state 状态  显示或者隐藏
     */
    private void scaleAnimation(View view,String state) {
        switch (state){
            case "show":
                view.startAnimation(bigShowAnim);
                view.setVisibility(View.VISIBLE);
                break;
            case "hide":
                view.startAnimation(smallHideAnim);
                smallHideAnim.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                    }
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        view.setVisibility(View.GONE);
                    }
                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }
                });
                break;
        }
    }


方法比较的简单,相信都不用我明说了。下面来看在哪里调用这个方法呢?当然是在底部布局拖动的时候啊


20200907111752520.png


收缩


2020090711185134.png


展开


20200907111932785.png


展开这里稍微讲解一下,因为展开的时候我并不知道你的搜索布局是否展开,所以加了一个判断,如果你的搜索布局是展开的,那么先收缩的搜索布局,再隐藏的搜索布局,这里新开了一个延时线程,500毫秒后执行隐藏动画,为什么是500毫秒呢?因为搜索布局收缩的过渡动画设置的时间就是500毫秒,这样就能做到无缝连接了,有没有恍然不明白的感觉啊?同样,如果底部布局展开时,搜索布局没有展开则直接隐藏即可。那么再来运行一下看看效果如何?


image.gif


其实到这里文章就已经完毕了,然而还差一丢丢。再把每日描述给优化一下吧,打开WeatherUtil,修改uvIndexInfo方法


  /**
     * 紫外线等级描述
     * @param uvIndex
     * @return
     */
    public static String uvIndexInfo(String uvIndex) {
        String result = null;
        Log.d("uvIndex-->", uvIndex);
        int level = Integer.parseInt(uvIndex);
        if (level <= 2) {
            result = "较弱";
        } else if (level <= 5) {
            result = "弱";
        } else if (level <= 7) {
            result = "中等";
        } else if (level <= 10) {
            result = "强";
        } else if (level <= 15) {
            result = "很强";
        }
        return result;
    }


之前我根据和风的文档写只有1~5,结果发现有一次出现了11,当然我就意识到和风坑了我,所以我去百度了紫外线的等级划分,于是就有了上面的代码。同样也衍生出另一个方法


  /**
     * 紫外线详细描述
     * @param uvIndexInfo
     * @return
     */
    public static String uvIndexToTip(String uvIndexInfo) {
        String result = null;
        switch (uvIndexInfo) {
            case "较弱":
                result = "紫外线较弱,不需要采取防护措施;若长期在户外,建议涂擦SPF在8-12之间的防晒护肤品。";
                break;
            case "弱":
                result = "紫外线弱,可以适当采取一些防护措施,涂擦SPF在12-15之间、PA+的防晒护肤品。";
                break;
            case "中等":
                result = "紫外线中等,外出时戴好遮阳帽、太阳镜和太阳伞等;涂擦SPF高于15、PA+的防晒护肤品。";
                break;
            case "强":
                result = "紫外线较强,避免在10点至14点暴露于日光下.外出时戴好遮阳帽、太阳镜和太阳伞等,涂擦SPF20左右、PA++的防晒护肤品。";
                break;
            case "很强":
                result = "紫外线很强,尽可能不在室外活动,必须外出时,要采取各种有效的防护措施。";
                break;
        }
        return result;
    }


这个方法就体现出紫外线的强度的效果描述,然后回到MapWeatherActivity中

找到getAirNowResult


tvTodayInfo.setText("今天,白天" + dayInfo + ",晚上" + nightInfo +
                        ",现在" + tvTemperature.getText().toString() + "," +
                        WeatherUtil.apiToTip(data.getCategory()) + WeatherUtil.uvIndexToTip(tvUvIndex.getText().toString()));


修改这一行代码,然后就OK了,运行一下:


20200907141442614.png


然后这一部分的代码就算是写完了。虽然看起来功能不多,但是自己来写的话却没有那么容易啊。

相关文章
|
1月前
|
XML Java 数据库
安卓项目:app注册/登录界面设计
本文介绍了如何设计一个Android应用的注册/登录界面,包括布局文件的创建、登录和注册逻辑的实现,以及运行效果的展示。
138 0
安卓项目:app注册/登录界面设计
|
2月前
|
存储 开发工具 Android开发
使用.NET MAUI开发第一个安卓APP
【9月更文挑战第24天】使用.NET MAUI开发首个安卓APP需完成以下步骤:首先,安装Visual Studio 2022并勾选“.NET Multi-platform App UI development”工作负载;接着,安装Android SDK。然后,创建新项目时选择“.NET Multi-platform App (MAUI)”模板,并仅针对Android平台进行配置。了解项目结构,包括`.csproj`配置文件、`Properties`配置文件夹、平台特定代码及共享代码等。
146 2
|
2月前
|
XML Android开发 数据格式
🌐Android国际化与本地化全攻略!让你的App走遍全球无障碍!🌍
在全球化背景下,实现Android应用的国际化与本地化至关重要。本文以一款旅游指南App为例,详细介绍如何通过资源文件拆分与命名、适配布局与方向、处理日期时间及货币格式、考虑文化习俗等步骤,完成多语言支持和本地化调整。通过邀请用户测试并收集反馈,确保应用能无缝融入不同市场,提升用户体验与满意度。
96 3
|
1月前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
205 0
|
2月前
|
XML 数据库 Android开发
10分钟手把手教你用Android手撸一个简易的个人记账App
该文章提供了使用Android Studio从零开始创建一个简单的个人记账应用的详细步骤,包括项目搭建、界面设计、数据库处理及各功能模块的实现方法。
|
28天前
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
422 7
|
28天前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
480 1
|
14天前
|
小程序 数据挖掘 UED
开发1个上门家政小程序APP系统,都有哪些功能?
在快节奏的现代生活中,家政服务已成为许多家庭的必需品。针对传统家政服务存在的问题,如服务质量不稳定、价格不透明等,我们历时两年开发了一套全新的上门家政系统。该系统通过完善信用体系、提供奖励机制、优化复购体验、多渠道推广和多样化盈利模式,解决了私单、复购、推广和盈利四大痛点,全面提升了服务质量和用户体验,旨在成为家政行业的领导者。
|
1月前
|
JavaScript 前端开发 小程序
uniapp一个人开发APP关键步骤和考虑因素
uniapp一个人开发APP关键步骤和考虑因素
117 1
uniapp一个人开发APP关键步骤和考虑因素
|
28天前
|
JavaScript 前端开发 UED
Vue与uni-app开发中通过@font-face巧妙引入自定义字体
Vue与uni-app开发中通过@font-face巧妙引入自定义字体
62 9

热门文章

最新文章