把一部动漫(未来日记)中的日记软件搬到现实!
2015年2月,出于个人需求,我以“未来日记”为初始原型,编了一个日记APP。现在它也有了不少用户,得到了许多的反馈,也是时候重启更新进程了…
新版本的APP会保持开源状态,直至加入云同步功能。
回顾(之前版本的截图)
版本1.3.4(简约风格)
规划目标
以后的APP更新内容,我就奔着用户需求去了。
此次重新设计,代码要规范,变量名函数名等要符合潮流。
实事求是
2015年9月16日
Axure太复杂,用在这个APP上有点“杀鸡用牛刀”。谷歌一番,我找到一个名为Mockplus的原型设计,普通功能免费,高级功能收费。下载下来,我觉得还可以,还有web端,够用了。
拖动一些组件,搭建了这么几幅UI界面:
疾风日记V1.4的今日界面
打开APP第一眼看到的界面
疾风日记V1.4的今日输入界面
写日记时的界面
下边的界面超长
疾风日记V1.4的往昔界面
原型设计的确是累人的活,尤其是字体大小与排版,这3幅图花了大约我3个 小时,顶不顺-_-。
不管怎么说,今天的工作就到这了,明天再继续。
2015年10月3日
原型设计完成之后,是相对轻松的码代码和调试。既然在这之前我已经有了相关的代码和经验,我就“站在巨人的肩膀上”,以便“看得更远”。
先选择开发环境吧,以前我用eclipse,总感觉哪里不对劲,后来发现eclipse不太适合我这疯狂强迫症,它终究是个插件,没有正统的血脉不够正宗。此番试试谷歌儿子Android Studio,感觉还行,虽然时不时冒出个internal error之类的bug出来,但语法补全和内容提示功能强大了不止一丢丢,很好。
具体的下载过程…真真是一把辛酸泪。官方的IDE被屏蔽,官方的SDK被屏蔽,官方的API参考网站被屏蔽,唉,谷歌也不容易,中国的Android开发者也不容易。冒着随时被伟大的网警发现的危险,我找到了一个良心网站:www.androiddevtools.cn,先从上面下载最新的Android Studio,然后再利用镜像下载SDK。这看起来很容易就像是花一两个小时便可以完成的任务,实则叫我和中国防火墙斗争了一天哪。
2015年10月4日
IDE准备完毕,当然还不能直接开始写代码-_-,代码量估计有几万行,我需要版本控制,上github生成一个repository,地址:
https://github.com/qiuyongchen/wind-diary
2015年10月5日
从github上check out整个项目,正式打开Android Studio,见证代码奇迹的时刻到了。
包名是com.qiuyongchen.diary,代码版本是1.4,版本代码是9(之前分别是1.3.4和8,为了保持兼容性,第一时间修改它们)。在原型设计里面,主界面拥有“今日”和“往昔”两个子页面,两个子页面处在同一个Activity中,左滑右滑互相切换。在Android平台,可以用ViewPager+Fragment来实现。
ViewPager+Fragment实现多页面滑动
网络上已经有很多人都给出了viewpager和fragment的用法,如
Android ViewPager使用详解 (Viewpager的说明介绍)
Android ViewPager多页面滑动切换以及动画效果 (Viewpager的具体使用)
但每个人的情况都不一样,我也简单说明一下自己的实现方案(假设读者知道ViewPager是什么,Fragment是什么)。
让初始Activity继承FragmentActivity,方便我们操作Fragment。
继承FragmentPagerAdapter,得到一个专门管理Fragment的适配器。一开始我搞不懂适配器的作用,后来才明白,我把许多Fragment放在一个普通数组里,如果没有适配器,我就不知道数组里有多少Fragment,也不知道自己是否已经越界,所以说,适配器的作用是:管理数组。
弄一个ViewPager,捕获Fragment适配器(mViewPager.setAdapter(mFragmentPagerAdapter);
实现ViewPager的监听器,监听用户行为,用户点了哪个页面就滑动到对应的页面,顺带着,也让小白条滑动起来,很有喜感。
只需以上四步,我就很轻松地实现了多页面滑动。
代码:
// 存放多个Fragment的数组,每个Fragment都对应一个页面 mFragments = new ArrayList(); Fragment fb1 = new FragmentWriteOff(); mFragments.add(fb1); Fragment fb2 = new FragmentView(); mFragments.add(fb2); // 数组的适配器,方便管理数组 MyFragmentPagerAdapter mFragmentPagerAdapter = new MyFragmentPagerAdapter( getSupportFragmentManager(), mFragments); mFragmentPagerAdapter.setFragments(mFragments); // ViewPager捕获自己的适配器和监听器 mViewPager.setAdapter(mFragmentPagerAdapter); mViewPager .setOnPageChangeListener(new MyFragmentPageChangeListener()); // 起始页面 mViewPager.setCurrentItem(0);
代码打包:wind-diary-2015-10-5 19_55_32
2015年10月19日
考虑到很多人都有临睡前写日记的习惯,我自身也是个“夜间模式”崇拜者,这次势必要加入夜间模式。
借theme/style实现夜间模式
1.夜间模式到底是怎么一回事呢?聪明的童鞋会猜到,是修改各个控件的color和background。而且,我们不会在代码中一股脑修改所有控件的颜色,这边我们是利用了Android的style和theme。
style是个神奇的东西,你在attr中定义了某个属性,在不同的style中给这属性赋不同值,然后你调用不同的style,得到的属性值就不一样。
比如说,我在attr里加一句
1 | <attr name="textString" format="string" /> |
在StyleA里写上
1 | <item name="textString">白天模式</item> |
在StyleB里加上
1 | <item name="textString">夜间模式</item> |
setTheme(R.style.StyleA)后textString的值是“白天模式”
setTheme(R.style.StyleB)后textString的值是“夜间模式”
按这个道理,借用style,我们就能实现夜间模式了。
2.设置了不同的style,我们要么就是在XML中引用属性值,要么就是在代码中引用属性值(暂时没搞明白),我先描述一下在XML中引用属性值的方法。
举个button的例子,button的XML布局一般是这样的
1 | <Button |
为了使用不同的style,以便让button的背景颜色随着style的变化而变化,我们需要稍微修改红色加粗的地方
1 | <Button |
“?attr/“这个前缀提示系统:该属性值随style变化而改变
3.点击按钮改变style
把切换style的代码放在某个button的点击响应函数里,用户一点击,立刻判断当前style,调用setTheme()函数加载相应的style,调用recreate()重新绘制APP。
public void OnClickNight(View view) { SharedPreferences.Editor editor = sharedPreferences.edit(); if (isNight) { setTheme(R.style.AppTheme_Night); isNight = false; } else { setTheme(R.style.AppTheme); isNight = true; } editor.putBoolean("isNight", isNight); editor.commit(); recreate(); }
4.状态栏背景色切换
为了用户体验,APP支持4.4以及以上的系统状态栏沉浸。
Android从5.0开始,正式支持状态栏颜色全部改变,而不是4.4那种渐变到黑色的模式(不是很好看),两者的代码实现相差很大。
对于Android4.4,我引入了一个名为SystemBarTintManager的开源控件,仅仅是针对4.4,因为Android5.0及以上的系统会强制覆盖此控件的效果。因为我暂时没找到在代码中引用当前style的attr的方法,所以代码复杂了些,手动检测当前style,再手动设置状态栏的颜色(注意粗体内容)
// 用于android4.4以上平台的状态栏变色(android5.0系统已经原生支持变色)
private void setStatusStyle() { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { setTranslucentStatus(true); } SystemBarTintManager tintManager = new SystemBarTintManager(this); tintManager.setStatusBarTintEnabled(true); if (isNight) tintManager.setStatusBarTintResource(R.color.black); else tintManager.setStatusBarTintResource(R.color.green_pink ); } private void setTranslucentStatus(boolean on) { Window win = getWindow(); WindowManager.LayoutParams winParams = win.getAttributes(); final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; if (on) { winParams.flags |= bits; } else { winParams.flags &= ~bits; } win.setAttributes(winParams); }
对于Android5.0,做法很简单,我们只需要在不同的style里设置不同colorPrimaryDark值
1 | <!-- Base application theme. --> |
就这么几句(注意粗体内容),当我们切换了style后,系统会自动帮我们变色。
至此,系统状态栏沉浸式也跟进到Android5.0了。
2015年10月19日
轻轻地记录一下:在layout布局文件里引用某张图的时候,文件名只能由字母数字和下划线组成,不然就坑了,”Failed to convert @drawable/ic_combo_chart-100 into a drawable.”
2015年11月21日
引入EventbuS的原因
在设置界面打开夜间模式后,整个APP是如何切换的呢,或者说,主界面是如何知道夜间模式已经被打开的呢?我思考良久,在高人指点之下,搜索到了EventBus这个开源库。利用这个库,我可以在设置页面发布一个全局消息:夜间模式开启了!那些监听并等待的对象就可以收到这个消息并做出反应(比如主界面改变自己的颜色)
EVENTBUS简单使用
在EventBus里面,消息是一个类,消息者发布消息的时候,就是给监听者传去一个类,监听者从这个类里能获取到消息的具体情况。
我们首先要创建一个类来表示消息,比如说,创建一个NightModeChangedEvent类:
public class NightModeChangedEvent { boolean nightMode; public NightModeChangedEvent(boolean n) { nightMode = n; } public boolean getNightMode() { return nightMode; } public void setNightMode(boolean n) { this.nightMode = n; } }
当用户在设置页面开启夜间模式后,EventBus会实例化NightModeChangedEvent类,并将这个类当做消息给发布出去。
EventBus.getDefault().post(new NightModeChangedEvent(true));
主页面之前已经告诉EventBus:“我要监听所有消息”。
@Override public void onStart() { super.onStart(); if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this); } @Override public void onDestroy() { EventBus.getDefault().unregister(this); super.onDestroy(); }
主页面监听到夜间模式被开启的消息后,会调用recreate函数来重绘界面。
// 监听夜间模式 public void onEventMainThread(NightModeChangedEvent event) { Log.e("onEvent", "got a message"); if (event.getNightMode()) { this.recreate(); } }
EventBus小结
“发布/监听”模式的一种实现方式。
2015年12月4日
手势密码的实现
本想自定义控件,后来我觉得重复造轮子不是好主意,就借用了开源控件android-lockpattern,改成了对话框形式的解锁界面。
2015年12月8日
正式发布V1.4.0版本,功能更新如下:
UI改版,更轻更快
增加手势密码
增加夜间模式
修复一些BUG
编写界面截图:write_day
日记列表截图:device-2015-12-08-043640
2015年12月9日
今天有更新了最新版本的用户反映说:“在新版本上,每打一个字就会弹出一次输入法。”
我想了想,觉得可能是我在onResume()里面监听输入法造成的(出于交互体验考虑,输入法会自动开启和自动关闭),于是我去掉了监听代码,暂时舍弃输入法自动关闭功能,重新上架,希望不会有太多差评。