实战:Android 时间、天气widget
2015-11-14 00:32
549 查看
Android widget可以在用户不进入APP的情况下进行操作,同时也可呈现给用户更多的信息。所以今天就来看看widget的基本使用(建议阅读官方文档)。
file => new => widget=> App Widget
即可添加一系列相关文件。
主要有:
res/xml/new_app_widget_info
作用:官方的解释是 The AppWidgetProviderInfo defines the essential qualities of an App Widget。其中主要的几个属性:
android:initialKeyguardLayout:锁屏界面对应的布局
android:initialLayout:桌面对应布局
android:previewImage:选择小部件时的预览图
android:updatePeriodMillis:widget更新周期,单位:ms。注意:规定不能小于30min,如果小于30min,则系统会设置为30min。
res/layout/new_app_widget
作用:对应widget的布局
同时也要在AndroidManifest.xml中声明
NewAppWidget
该类继承于AppWidgetProvider,是控制widget生命周期的一个类。
其中主要的方法有:
onEnabled:当widget被添加到桌面上时被调用
onUpdate:当widget更新时调用
onDisabled:当widget被移除时调用
通过系统自动添加的文件,运行后即可在桌面widget中找到了!
接下来就是实现显示时间和天气的功能了。
方案一:使用大公司提供的天气查询接口,比如百度。
优点:准确,方便。
缺点:需要申请key,好像还要用百度的包什么的。
方案二:使用民间抓包的接口。
优点:可以直接用,返回数据信息足够详细。
缺点:找到的大多接口,比如中国气象网的接口,会用到cityid这个参数,也有很多类似都接口都用到了这个参数,这里给出各个城市对应的cityid。如果是使用这个方案的话,就有两条路:
获取经纬度-》再找一个能通过经纬度找到地名的API-》根据地名找到最接近的cityid-》最后传cityid、调用天气API、获得天气情况。
获取IP-》再找一个通过ip获得地址的API-》根据地名找到最接近的cityid-》最后传cityid、调用天气API、获得天气情况。
方案三:使用Yahoo的API
鉴于方案二分析,如果能找到直接通过经纬度就能得到天气的API那时极好的。而Yahoo的API刚好符合这点。而且是免费使用(有次数限制),不需要申请key,添加包。
缺点:返回数据类型有限,比如大家最关心的PM值没有、需要学习Yahoo的数据可查询语句YQL。
综上,最后决定使用Yahoo的API。
使用可参见这两个链接:
https://developer.yahoo.com/weather/
https://developer.yahoo.com/weather/documentation.html
project已传到github,初步实现了上述功能。如果有错误,欢迎指出!
widget基本使用
Android studio 环境下可选择file => new => widget=> App Widget
即可添加一系列相关文件。
主要有:
res/xml/new_app_widget_info
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/new_app_widget" android:initialLayout="@layout/new_app_widget" android:minHeight="40dp" android:minWidth="250dp" android:previewImage="@drawable/example_appwidget_preview" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="108005" android:widgetCategory="home_screen"></appwidget-provider>
作用:官方的解释是 The AppWidgetProviderInfo defines the essential qualities of an App Widget。其中主要的几个属性:
android:initialKeyguardLayout:锁屏界面对应的布局
android:initialLayout:桌面对应布局
android:previewImage:选择小部件时的预览图
android:updatePeriodMillis:widget更新周期,单位:ms。注意:规定不能小于30min,如果小于30min,则系统会设置为30min。
res/layout/new_app_widget
作用:对应widget的布局
同时也要在AndroidManifest.xml中声明
<receiver android:name=".NewAppWidget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/new_app_widget_info" /> </receiver>
NewAppWidget
该类继承于AppWidgetProvider,是控制widget生命周期的一个类。
其中主要的方法有:
onEnabled:当widget被添加到桌面上时被调用
onUpdate:当widget更新时调用
onDisabled:当widget被移除时调用
通过系统自动添加的文件,运行后即可在桌面widget中找到了!
接下来就是实现显示时间和天气的功能了。
时间
每分钟刷新界面只需要注册 android.intent.action.TIME_TICK 广播即可。private BroadcastReceiver minBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //update time every minute updateTime(context); } }; //... IntentFilter updateIntent = new IntentFilter(); updateIntent.addAction("android.intent.action.TIME_TICK"); context.getApplicationContext().registerReceiver(minBroadcast, updateIntent); //...
天气
获取天气信息肯定是要通过网络请求的,于是开始找API,能找到free use 的是最好。方案一:使用大公司提供的天气查询接口,比如百度。
优点:准确,方便。
缺点:需要申请key,好像还要用百度的包什么的。
方案二:使用民间抓包的接口。
优点:可以直接用,返回数据信息足够详细。
缺点:找到的大多接口,比如中国气象网的接口,会用到cityid这个参数,也有很多类似都接口都用到了这个参数,这里给出各个城市对应的cityid。如果是使用这个方案的话,就有两条路:
获取经纬度-》再找一个能通过经纬度找到地名的API-》根据地名找到最接近的cityid-》最后传cityid、调用天气API、获得天气情况。
获取IP-》再找一个通过ip获得地址的API-》根据地名找到最接近的cityid-》最后传cityid、调用天气API、获得天气情况。
方案三:使用Yahoo的API
鉴于方案二分析,如果能找到直接通过经纬度就能得到天气的API那时极好的。而Yahoo的API刚好符合这点。而且是免费使用(有次数限制),不需要申请key,添加包。
缺点:返回数据类型有限,比如大家最关心的PM值没有、需要学习Yahoo的数据可查询语句YQL。
综上,最后决定使用Yahoo的API。
使用可参见这两个链接:
https://developer.yahoo.com/weather/
https://developer.yahoo.com/weather/documentation.html
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.Criteria; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.AsyncTask; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.RemoteViews; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.text.SimpleDateFormat; public class NewAppWidget extends AppWidgetProvider { private static boolean isFirst = true; enum weatherType { condition, sunset, weather; } private LocationListener locationListener = null; private final String INFO = "AppWidget_INFO"; private final String IS_LOCATING = "locating", FRESH_FAIL = "fresh fail", LOCATION_FAIL = "location fail "; private LocationManager locationManager; private Location location; private BroadcastReceiver minBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //update time every minute updateTime(context); } }; private URL weatherUrl; @Override public void onEnabled(Context context) { //when attach to screen Log.i(INFO, "onEnabled"); acquireLocation(context); updateTime(context); // detected minute change IntentFilter updateIntent = new IntentFilter(); updateIntent.addAction("android.intent.action.TIME_TICK"); context.getApplicationContext().registerReceiver(minBroadcast, updateIntent); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget); views.setTextViewText(R.id.appwidget_temp, IS_LOCATING); views.setViewVisibility(R.id.appwidget_weather, View.INVISIBLE); views.setViewVisibility(R.id.appwidget_date, View.INVISIBLE); AppWidgetManager.getInstance(context.getApplicationContext()).updateAppWidget(new ComponentName(context.getApplicationContext(), NewAppWidget.class), views); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // There may be multiple widgets active, so update all of them Log.i(INFO, "onUpdate"); if (location != null) { new freshWeatherTask(context).execute(acquireUrl(weatherType.condition, location)); } else if (!isFirst) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget); views.setTextViewText(R.id.appwidget_temp, LOCATION_FAIL); views.setViewVisibility(R.id.appwidget_weather, View.INVISIBLE); views.setViewVisibility(R.id.appwidget_date, View.INVISIBLE); AppWidgetManager.getInstance(context.getApplicationContext()).updateAppWidget(new ComponentName(context.getApplicationContext(), NewAppWidget.class), views); } isFirst = false; } @Override public void onDisabled(Context context) { // Enter relevant functionality for when the last widget is disabled if (minBroadcast.isOrderedBroadcast()) context.getApplicationContext().unregisterReceiver(minBroadcast); if (locationManager != null && locationListener != null) locationManager.removeUpdates(locationListener); } /** * get location by GPS or NetWork * * @param context */ private void acquireLocation(final Context context) { if (locationManager == null) { locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } if (locationListener == null) { locationListener = new LocationListener() { @Override public void onLocationChanged(Location loc) { if (loc != null) { Log.i(INFO, "get new location"); location = loc; new freshWeatherTask(context).execute(acquireUrl(weatherType.condition, location)); } } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } }; } Criteria criteria = new Criteria(); criteria.setAccuracy(Criteria.ACCURACY_COARSE); criteria.setAltitudeRequired(false); criteria.setBearingRequired(false); criteria.setCostAllowed(true); criteria.setPowerRequirement(Criteria.POWER_LOW); locationManager.requestLocationUpdates(locationManager.getBestProvider(criteria, true), 1000 * 600, 10, locationListener); } /** * get the url by different type. * in the example ,the type I used is condition. if you use different type , the json construct may change. see https://developer.yahoo.com/weather/ * * @param type * @param l * @return */ private String acquireUrl(weatherType type, Location l) { if (l != null) { switch (type) { case condition: return "https://query.yahooapis.com/v1/public/yql?q=select%20item.condition%20from%20weather.forecast%20where%20woeid%20in%20(SELECT%20woeid%20FROM%20geo.placefinder%20WHERE%20text%3D%22" + l.getLatitude() + "%2C" + l.getLongitude() + "%22%20and%20gflags%3D%22R%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"; case weather: return "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(SELECT%20woeid%20FROM%20geo.placefinder%20WHERE%20text%3D%22" + l.getLatitude() + "%2C" + l.getLongitude() + "%22%20and%20gflags%3D%22R%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"; case sunset: return "https://query.yahooapis.com/v1/public/yql?q=select%20astronomy.sunset%20from%20weather.forecast%20where%20woeid%20in%20(SELECT%20woeid%20FROM%20geo.placefinder%20WHERE%20text%3D%22" + l.getLatitude() + "%2C" + l.getLongitude() + "%22%20and%20gflags%3D%22R%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"; default: return null; } } else { return null; } } private void updateTime(Context context) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget); views.setTextViewText(R.id.appwidget_text, new SimpleDateFormat("hh:mm").format(System.currentTimeMillis())); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context.getApplicationContext()); ComponentName componentName = new ComponentName(context.getApplicationContext(), NewAppWidget.class); appWidgetManager.updateAppWidget(componentName, views); } class freshWeatherTask extends AsyncTask<String, String, String> { private Context context; private AppWidgetManager manager; private ComponentName componentName; freshWeatherTask(Context c) { context = c; manager = AppWidgetManager.getInstance(context.getApplicationContext()); componentName = new ComponentName(context.getApplicationContext(), NewAppWidget.class); } @Override protected String doInBackground(String... params) { BufferedReader in = null; String rst = ""; if (params.length > 0) { try { weatherUrl = new URL(params[0]); URLConnection connection = weatherUrl.openConnection(); connection.connect(); in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String tmpRst; while ((tmpRst = in.readLine()) != null) { rst += tmpRst; } Log.i(INFO, rst); } catch (Exception e) { e.printStackTrace(); } finally { try { if (in != null) { in.close(); } } catch (IOException ioE) { ioE.printStackTrace(); } } } return rst; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); Log.i("Async", s); if (!TextUtils.isEmpty(s)) { try { JSONObject mainJson = new JSONObject(s).getJSONObject("query"); JSONObject subJson; if (mainJson.getInt("count") == 1) { if (mainJson.has("results") && mainJson.getJSONObject("results").has("channel")) { subJson = mainJson.getJSONObject("results").getJSONObject("channel"); if (subJson.has("item") && subJson.getJSONObject("item").has("condition")) { subJson = subJson.getJSONObject("item").getJSONObject("condition"); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget); if (subJson.has("code")) { // views.setTextViewText(R.id.appwidget_temp, subJson.getString("temp")); } if (subJson.has("date")) { views.setViewVisibility(R.id.appwidget_date, View.VISIBLE); views.setTextViewText(R.id.appwidget_date, subJson.getString("date")); } if (subJson.has("temp")) { views.setViewVisibility(R.id.appwidget_temp, View.VISIBLE); float temp = (subJson.getInt("temp") - 32) / 1.8f; views.setTextViewText(R.id.appwidget_temp, (Math.round(temp * 100)) / 100f + "ºC"); } if (subJson.has("text")) { views.setViewVisibility(R.id.appwidget_weather, View.VISIBLE); views.setTextViewText(R.id.appwidget_weather, subJson.getString("text")); } manager.updateAppWidget(componentName, views); } } } } catch (Exception e) { e.printStackTrace(); } } else { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget); views.setTextViewText(R.id.appwidget_temp, FRESH_FAIL); views.setViewVisibility(R.id.appwidget_weather, View.INVISIBLE); views.setViewVisibility(R.id.appwidget_date, View.INVISIBLE); manager.updateAppWidget(componentName, views); } } } }
project已传到github,初步实现了上述功能。如果有错误,欢迎指出!
相关文章推荐
- Android中调试获取Log
- android图片的缩放和旋转功能
- Android LruCache究竟是什么
- Android AsyncTask异步实现大文件下载
- android中的Dialog的使用
- 几步实现Android 开发中的弹窗效果
- Android 开发之创建桌面快捷图标
- Android studio 解决问题Default Activity not found
- [实践] Android源码 - Android系统初始化时创建自定义目录
- 详解Android触摸事件处理程序
- 【Android粒子动画】Android粒子动画渲染学习
- Android Launcher3的自定义修改总结
- Android按下返回键后,程序不退出,进入后台运行。使用moveTaskToBack(boolean nonRoot)
- Android消息机制
- 从源码角度解析Android消息机制
- 彻底搞懂Android Handler消息机制
- Android_SVG概述及生成使用SVG详解
- Android最牛的开源整理
- android抽屉效果
- Android新特性v7 - CardView