【Android学习】案例学开发,天气记事本项目学习总结。RxJava+Retrofit2+greenDAO

来源:互联网 发布:模拟动物的游戏知乎 编辑:程序博客网 时间:2024/06/06 09:13

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

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

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");

这样就会显示出来。

点击确定后通过实现的接口进行数据的返回。

@Overridepublic 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 1
原创粉丝点击