之前一直没怎么做过涉及数据库的应用(因为嫌麻烦^_^),只会书上讲的的基础方法进行增删改查。
最近学了greenDAO,就试着结合以前学的写个记事本的小应用练手,顺便巩固一下之前所学。
项目很简单,CollapsingToolbarLayout 配合 CoordinatorLayout 使用。
效果图:
来看设计图:
我觉得这种控件就得自己保存一个样例,不然时间一长不去用就会忘掉。
内容用Tablayout+ ViewPager来展示数据。
TabLayout有两种设置标签的方式:
TabLayout tabLayout = ...;
tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
其他布局略、、
接下来就是代码java部分,首先是从网络获取bing今日的图片。我之前有文章写怎么获取:
【Android学习】获取Bing 15天前到明天的壁纸,并设置为背景
使用Retrofit 获取图片地址;
定义接口:
public interface BingApi { @GET("bing/day/{what_day}/mkt/{country}") ObservablegetBingPicPath(@Path("what_day") String what_day, @Path("country") String country); }
创建Retrofit:
public static WeatherApi weatherApi; //获取bing壁纸地址 public static BingApi getBingApi() { if (bingApi == null) { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://test.dou.ms/") .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); bingApi = retrofit.create(BingApi.class); } return bingApi; }
截取字符串:
// 截取字符串中 图片的地址 public static String GetBingImageUrl(String str) { String[] strArray = str.split("地址:"); return strArray[1]; }
Bing图片和天气信息我是在启动界面展示时获取的。
天气信息和之前那一篇文章获取方式一致,不再贴了。
链接:使用聚合数据的接口进行的RxAndroid学习
需要他俩都获取到数据后在进行跳转。当然了,我会先判断有没有网络,没有就等2秒后跳转到主界面,
有网络就获取后再跳转。不过要记得自定义超时时间,毕竟网速慢的话不能在启动界面停留10秒啊(默认是几秒来着,反正很长啦)。
写到这里我想起来我没有处理进入主界面后如果有网络了怎么破 ,啊咧。
天气信息还好,只要切换城市就能再次发出请求获取数据,壁纸就不行了。
这种事情很简单啦,可以定义一个服务来监听网络状态,对的,正好service生疏了,那么就下次配合RxBus再写吧。
所以先看看使用combineLatest操作符的使用吧:
当两个Observables中的任何一个发射了数据时,使用一个函数结合每个Observable发射的最近数据项,并且基于这个函数的结果发射数据。
CombineLatest
操作符行为类似于zip
,但是只有当原始的Observable中的每一个都发射了一条数据时zip
才发射数据。CombineLatest
则在原始的Observable中任意一个发射了数据时发射一条数据。当原始Observables的任何一个发射了一条数据时,CombineLatest
使用一个函数结合它们最近发射的数据,然后发射这个函数的返回值。
所以用这个来观测获取图片和天气后进行跳转。
(异常也直接跳转)
代码:
Observable.combineLatest(NetWork.getBingApi().getBingPicPath("0", "ZH-CN"), NetWork.getWeatherApi() .getWeatherInfo(city, API_KEY), new Func2, Weather, Boolean>() { @Override public Boolean call(ResponseBody responseBody, Weather weather) { try { AppUtils.back_url = GetBingImageUrl(responseBody.string()); } catch (IOException e) { e.printStackTrace(); } // 判断并传值 if (weather.getError_code() == 0) //查询成功 可以保存 { AppUtils.today_weather = weather; } return true; } }).compose(this. bindToLifecycle()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer () { @Override public void onCompleted() { } @Override public void onError(Throwable e) { goHome(); } @Override public void onNext(Boolean aBoolean) { goHome(); } });
好的,数据获取完就开始展示。
效果如图:
有一点要说的,就是天气图片。聚合数据提供了两套图片,都挺好看的,我取了其中一套放到了去年申请的虚拟主机上(免费两年,快到期了,之前都没怎么用过)。
地址在源码里,欢迎给Star。
好的,到这里要说数据库的事情了。
greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。
我也是在网上看别人的教程。在这就推荐一个:
黄帅(音译)的文章
快速入门GreenDao框架并实现增删改查案例
但我觉得这都不是重点。因为greenDAO封装好了Api,我们不需要写sql语句。轻松简单,所以我遇到的问题
是viewpager 显示Fragment的时候。
首先 我在创建Fragment时 传入了一个参数:
ViewPagerAdapter vpAdapter = new ViewPagerAdapter(getSupportFragmentManager()); vpAdapter.addFragment(new DailyFragment().newInstance("学习"), "学习"); vpAdapter.addFragment(new DailyFragment().newInstance("工作"), "工作"); vpAdapter.addFragment(new DailyFragment().newInstance("运动"), "运动"); vpAdapter.addFragment(new DailyFragment().newInstance("日常"), "日常"); main_vp_container.setAdapter(vpAdapter);
然后fragment 获取到这个参数,通过这个参数去 查询数据库:
public DailyFragment newInstance(String type) { Bundle args = new Bundle(); args.putString(TYPE, type); DailyFragment dailyFragment = new DailyFragment(); dailyFragment.setArguments(args); return dailyFragment; }
这都是我想象的流程,实际上并没有这样正常进行。
为什么呢,这就得从Fragment的生命周期和 viewpager缓存说起了。
Viewpager不设置默认缓存页面数量的话,默认是两个。
我们现在有 1 、 2、 3 、 4 ,4个界面。
通过跟踪声明周期函数可以知道。 先是(中间的就不追踪了,大家知道中间还有生命周期函数就行) onCreate 1 、onResume 1、 onCreate 2、 onResume 2
这时候显示的是 第一个界面。 滑动到第2个后 开始执行onCreate 3、 onResume 3
为什么呢,因为2已经创建了,只是你没看到 。有人问滑动到能看到的时候为啥没onResume ,
这就相当于一个很大的图片,你在手机上只看到一部分,滑动之后看到其余部分差不多一个意思。
然后再向右滑动 就是onCreate 4、 onResume 4.
这时候滑动到第四个界面 发现什么也没执行,原因就像上面说的,在显示3的时候已经加载完毕了。
然后接下来无论怎么滑动都是执行onResume,且加载的是相邻的。
也就是说我们无法在获取create 时传入的参数。 那么我们该向数据库查询什么呢?显然结果会是错误的。
在一开始我的解决方法是创建4个一模一样的fragment,当然名字不一样。分别执行查询"学习","运动",什么的。
这样当然没问题,但咱这也太low了,一模一样的fragment还写4个。。。
所以我就想在tab改变的时候可以获取到此时的TabTitle。然后传给 fragment,让它执行查询方法,再次获取数据。
没错,这样也能解决。但有一个小bug。当时是使用RxBus,发车之后在fragment中监听获取事件,但由于之前说,此时有两个fragment存在,它们都在监听。
所以如果不加以限制,他们会触发重新查询的方法,所以你会在滑动的时候发现相邻的界面数据和你当前的一样。
最简单的办法就是设置缓存页数:
这就是这种办法的最简单解决办法。
main_vp_container.setOffscreenPageLimit(4); //设置4页缓存
好尴尬0 0,没关系,加深了对fragment生命周期和tablayout+viewpager的用法。
当然你也可以对fragmentadapter进行改造使其对数据进行缓存。
来看信息展示界面:
然后是添加界面:
使用是DialogFragment :
鸿洋大神的教程,很详细:
不过我也贴一下简单的代码:
布局就不贴了。
直接上java代码,很简单。
public class AddDialogFragment extends DialogFragment { private EditText et_title; private EditText et_info; private MaterialSpinner bp; //创建接口在Acitvity中调用 public interface AddDutyInputListener { void onAddDutyInputComplete(String title, String type, String info); } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { String[] ITEMS = {"学习", "工作", "运动", "日常"}; ArrayAdapteradapter = new ArrayAdapter (getActivity(), android.R.layout.simple_spinner_item, ITEMS); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); LayoutInflater inflater = getActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.dialog_add, null); et_title = (EditText) view.findViewById(R.id.et_title); et_info = (EditText) view.findViewById(R.id.et_info); bp = (MaterialSpinner) view.findViewById(R.id.spinner); bp.setAdapter(adapter); builder.setView(view) .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { AddDutyInputListener listener = (AddDutyInputListener) getActivity(); listener.onAddDutyInputComplete(et_title.getText().toString(), bp.getSelectedItem().toString(), et_info.getText().toString()); } }).setNegativeButton("取消", null); return builder.create(); } }
然后在activity中调用显示:
AddDialogFragment adddialog = new AddDialogFragment(); adddialog.show(getFragmentManager(), "addDialog");
这样就会显示出来。
点击确定后通过实现的接口进行数据的返回。
@Override public void onAddDutyInputComplete(String title, String type, String info) { if (title.trim().isEmpty()) { Toast.makeText(MainActivity.this, "标题不能为空!", Toast.LENGTH_SHORT).show(); } else { Duty newduty = new Duty(null, title, info, type, false, new Date()); DbServices.getInstance(this).saveNote(newduty); if (_rxBus.hasObservers()) { //是否有观察者,有,则发送一个事件 _rxBus.send(new Event.AddEvent(newduty,type)); } } }
在这里我使用了rxBus 来通知fragment 添加了一个数据, 让他们看看是不是属于自己那一组的,
属于的话就自己往adapter里增添一条数据。
代码如下:(注意生命周期)
_rxBus.toObserverable() .compose(this.bindToLifecycle()) .subscribe(new Action1
完美实现:
到这里就算结束了,在无人指引的情况下,多看书,打基础,然后在代码中获得收获。
代码已经传github:https://github.com/VongVia1209/WeatherAndNote
更新:增加了设置壁纸功能
首先需要给权限:
android:name = "android.permission.SET_WALLPAPER"/>
android:name="android.permission.SET_WALLPAPER_HINTS"/>
然后就是使用picasso获取bitmap 然后设置就好。
代码如下:(picasso创建bitmap属于io操作)
void setBackground() { final WallpaperManager instance = WallpaperManager.getInstance(this); int desiredMinimumWidth = this.getWindowManager().getDefaultDisplay().getWidth(); int desiredMinimumHeight = this.getWindowManager().getDefaultDisplay().getHeight(); instance.suggestDesiredDimensions(desiredMinimumWidth, desiredMinimumHeight); ObservablesetBack = Observable.create(new Observable.OnSubscribe () { @Override public void call(Subscribersuper Void> subscriber) { try { Bitmap bmp = Picasso.with(MainActivity.this).load(AppUtils.back_url).get(); instance.setBitmap(bmp); } catch (Exception e) { e.printStackTrace(); } subscriber.onNext(null); subscriber.onCompleted(); } }).compose(this. bindToLifecycle()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); setBack.subscribe(new Action1 () { @Override public void call(Void aVoid) { Toast.makeText(MainActivity.this, "设置成功", Toast.LENGTH_SHORT).show(); } }); }
效果: