github地址
因为是基于《第一行代码第二版》的天气app的扩展开发,所以就直接使用了书中提到的一些底层依赖库:litepal、okhttp、glide、gson,加上后续功能的开发还用到了recyclerview
、
eventbus
、
databinding,还使用了高德地图api实现了定位功能。天气api依然是使用和风天气,和原书不同的地方在于多用了一个城市搜索的api,用来替换原书中的省市县选择,直接使用输入城市名的方法得到所想查看的城市,这也是和风天气自带的api,具体可查看
https://www.heweather.com/documents/api/v5/search
。
功能在原书的基础上实现了:1、主界面可以左右滑动切换,看到所有所选城市的天气信息。2、城市管理功能:点击城市,在主界面显示所点击城市的天气信息,长按进行城市的删除。3、搜索城市功能:输入城市名的中文或者拼音可以得到一个或多个城市,点击后可以在主界面生成新页显示天气数据。4、设置界面:可以选择是否自动更新,以及输入自动更新时间。5、首次进入app时,可以下拉定位,显示所在地天气。有数据时下拉可以刷新所有城市的天气信息。
主界面与城市管理与搜索界面的逻辑设计
1.打开app进入MainActivity时,使用litepal数据库得到上次存下的天气信息,如果没有缓存,当使用下拉刷新则使用自动定位、显示刷新圈圈,然后添加litepal数据库并显示天气信息。如果有缓存则使用for循环,添加viewpager页。
2.点击MainActivity中的城市管理按键,进入cityManagementActivity,在这里读取数据库中的所有城市,解析其weather字符串,使用for循环添加item显示到页面上。点击item时用eventbus传递一个刷新的命令,之后结束cityManagementActivity,回到MainActivity中,显示按下城市名的那一页信息。长按item时显示一个删除按键。删除操作,根据position从数组中得到当前按下的item的weatherId,检索数据库进行删除,之后重新加载界面。
3.当点击cityManagementActivity中的添加城市时跳转到SearchCityActivity中,输入城市名的中文或拼音可以得到一个列表,当点击想要查看的城市时,用eventbus发送一个结束与刷新命令,结束SearchCityActivity与cityManagementActivity,并刷新MainActivity中显示的天气信息。
关键点:SearchCityActivity得到新城市之后,对litpal进行查询操作,如果不存在将新城市存入litrpal数据库。
Litepal数据库需要记录的值为cityName,weatherId,weather字符串。
MainActivity中的swipeRefreshLayout会和viewpager、ScrollView会产生冲突 ,与viewpager的冲突使用 http://blog.csdn.net/u010386612/article/details/50548977#bottom解决,与ScrollView的冲突使 http://blog.csdn.net/qq_16628781/article/details/52025366
解决。
MainActivity中Title的城市名,在首次显示时,直接读取数据库第一位,显示。之后的城市名通过viewpager滑动监听器得到position之后查询数组实现,当切换时可以直接读取显示。
定位功能,在当前数据库不存在数据时,通过下拉刷新得到当地weatherId,刷新界面。
关键功能的代码实现
1、实现对viewpager的添加与信息更新以及小圆点的添加
根据之前逻辑设计的思路,在我们添加城市或者删除城市时,会使用eventbus发送一个refresh来进行更新界面。
@Subscribe(threadMode = ThreadMode.MAIN) //eventbus3.0自带的特性 可以指定在哪个线程中运行
public void onMoonEvent(MessageEvent event)
{
if (event.getMessage().equals("onItemClick"))
{
for(int i=0 ; i
在每次需要刷新时,会重新读取数据库数据得到所有的城市信息,当weather字符串不为空时,直接显示,为空则联网刷新。
private void initViewpager()
{
viewList = new ArrayList();
cityList.clear();
binding.title.llContainer.removeAllViews();
cityList = DataSupport.findAll(City.class);
if (cityList != null & cityList.size() > 0)
{
for (int i = 0; i < cityList.size(); i++)
{
final WeatherinfoBinding weatherinfoBinding = DataBindingUtil.inflate(getLayoutInflater().from(this), R.layout.weatherinfo, null, false);//使用databing的方法得到布局文件
Weather weather = Utility.handleWeatherResponse(cityList.get(i).getWeather()); //解析json数组
if (weather != null)
{
showWeatherInfo(weatherinfoBinding, weather);
}
else
{
requestWeather(cityList.get(i).getWeatherId());
}
viewList.add(weatherinfoBinding.getRoot()); //将此页添加到viewpager显示数组中
//这里是解决swipeRefreshLayout与ScrollView冲突的地方,意思是只有当ScrollView滑到最顶端时才enable swipeRefreshLayout
weatherinfoBinding.weatherScrollview.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
binding.swipeRefresh.setEnabled(weatherinfoBinding.weatherScrollview.getScrollY() == 0);
return false;
}
});
//以下代码的功能是新建小圆点显示
Button bt = new Button(this);
bt.setLayoutParams(new ViewGroup.LayoutParams(18, 18)); //设置长宽
if (i == 0)
{
bt.setBackgroundResource(R.drawable.home_page_dot_select);
mPreSelectedBt = bt;
binding.title.titleCtiy.setText(cityList.get(i).getCityName());
}
else
{
bt.setBackgroundResource(R.drawable.icon_dot_normal);
}
binding.title.llContainer.addView(bt);
}
}
else
{
binding.title.titleCtiy.setText(null);
}
binding.pager.setAdapter(new ViewPagerAdapter(viewList));
binding.pager.setCurrentItem(0);
binding.pager.setPageTransformer(true, new DepthPageTransformer()); //添加页面切换动画
}
2、每小时天气的显示
由于api的原因,免费的api只能得到一天中每三个小时的天气显示,所以导致此处显示看起来很挫,付费api则可以得到一至三天的每小时天气信息,这样就好很多了。关于每小时天气的显示,其实就是一个横向的一个RecyclerView,代码中使用的是databing结合RecyclerView的方法实现,我也是本着减少findViewById的使用才使用databing来实现的。RecyclerView结合databing的方法可查看 http://www.jianshu.com/p/c27462a405fb
。
这里还有最重要的一点是databing结合glide实现图片加载,参考以下实现 http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0930/3536.html。
具体代码在HourlyForecaseItemBean中。下面是代码中的具体实现。
@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView view, String imageUrl)
{
Glide.with(view.getContext()).load(imageUrl).into(view);
}
布局文件在hourlyforecaseitem中
3、城市管理界面的RecyclerView item长按删除
网上可以找到很多RecyclerView的长按的实现方法,我使用的方法是在首先实现
OnitemClickListener与OnItemLongClickListener接口然后在RecyclerViewAdapter的onBindViewHolder方法对长按短按进行绑定。
/*
* 设置item点击事件
* */
public void setOnItemClickListener(OnItemClickListener OnItemClickListener)
{
this.mOnItemClickListener = OnItemClickListener;
}
public void setmOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener)
{
this.mOnItemLongClickListener = onItemLongClickListener;
}
/*
* 数据绑定
* */
@Override
public void onBindViewHolder(final BindingHolder holder, final int position)
{
holder.bindData(items.get(position));
if(mOnItemClickListener!=null)
{
holder.itemView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemClick(pos);
}
});
}
if(mOnItemLongClickListener !=null)
{
holder.itemView.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View view)
{
int pos = holder.getLayoutPosition();
mOnItemLongClickListener.onItemClick(view,pos);
return true ;
}
});
}
}
实现了长按之后就可以来实现删除功能。根据之前的逻辑设计,删除操作其实是得到按下item的weatherId然后查询数据库进行删除,之后在重新加载界面完成的。
adapter.setmOnItemLongClickListener(new OnItemLongClickListener()
{
@Override
public void onItemClick(View view, int position)
{
// Toast.makeText(CityManagementActivity.this, "onLongClick事件 您点击了第:" + position + "个Item" + "对应weatherid为" + cityList.get(position).getWeatherId(), LENGTH_SHORT).show();
showPopMenu(view, position);
}
});
public void showPopMenu(View view, final int position)
{
PopupMenu popupMenu = new PopupMenu(this, view);
popupMenu.getMenuInflater().inflate(R.menu.delect_menu, popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener()
{
public boolean onMenuItemClick(MenuItem item)
{
City mcity = cityList.get(position);//得到对应数组
Utility.delectCityInfo(mcity.getWeatherId()); //查找数据库删除
EventBus.getDefault().post(new MessageEvent("Refresh",null)); //通知mainactivity更新界面
quayCities();
return false;
}
});
popupMenu.show();
}
先写这么几个我觉得比较关键的地方吧 以后有机会在添加 具体还是查看代码吧 自觉代码写的比较烂 有bug请留言