之前一直没怎么做过涉及数据库的应用(因为嫌麻烦^_^),只会书上讲的的基础方法进行增删改查。

最近学了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"));

第二种

 <android.support.design.widget.TabLayout
android:layout_height="wrap_content"
android:layout_width="match_parent">

<android.support.design.widget.TabItem
android:text="@string/tab_text"/>

<android.support.design.widget.TabItem
android:icon="@drawable/ic_android"/>

</android.support.design.widget.TabLayout>

其他布局略、、


接下来就是代码java部分,首先是从网络获取bing今日的图片。我之前有文章写怎么获取:

【Android学习】获取Bing 15天前到明天的壁纸,并设置为背景


使用Retrofit 获取图片地址;

定义接口:

public interface BingApi {
@GET("bing/day/{what_day}/mkt/{country}")
Observable<ResponseBody> getBingPicPath(@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操作符的使用吧:


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<ResponseBody, 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.<Boolean>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Boolean>() {
@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 :


鸿洋大神的教程,很详细:


Android 官方推荐 : 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 = {"学习", "工作", "运动", "日常"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(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<Object>() {
@Override
public void call(Object event) {
if (event instanceof Event.AddEvent) {
//如果 传来的 新增事件 和当前 查询结果类型一致 则直接往里面填充
if (((Event.AddEvent) event).getMduty().getType() == mytype) {
qadapter.add(0, ((Event.AddEvent) event).getMduty());

}
}
}
});

完美实现:






到这里就算结束了,在无人指引的情况下,多看书,打基础,然后在代码中获得收获。




代码已经传github:https://github.com/VongVia1209/WeatherAndNote



更新:增加了设置壁纸功能



首先需要给权限:

<uses-permission android:name = "android.permission.SET_WALLPAPER"/>
<uses-permission 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);
Observable<Void> setBack = Observable.create(new Observable.OnSubscribe<Void>() {
@Override
public void call(Subscriber<? super 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.<Void>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
setBack.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
Toast.makeText(MainActivity.this, "设置成功", Toast.LENGTH_SHORT).show();
}
});


}


效果:


更多相关文章

  1. “树”不倒,人不散—数据结构的核心
  2. Android复习练习十二(自定义ContentProvider实现其他应用操作本
  3. Android项目实战--手机卫士01--启动界面
  4. android 输入法界面显示的开关
  5. 问题记录-Activity跳转后显示空白界面
  6. 【Android学习】数据传递三种方式
  7. 实现基于注解(Annotation)的数据库框架(一)反射的基本了解
  8. 在活动中管理多个asynctask什么显示数据(Survey App)?
  9. Google Play服务API(位置服务)是否需要数据计划?

随机推荐

  1. android背景选择器selector用法汇总
  2. android操作sqlite3的blob字段
  3. : Failed to read row 0, column -1 from
  4. 安卓自学,手机上的横竖屏切换,状态栏隐藏
  5. Xcode 4.2 编译 Android
  6. 进程博客纳入
  7. Android(安卓)- 销毁指定Activity
  8. android常用控件(二)
  9. 基本Dalvik VM调用
  10. Android AVD启动失败