从无到有写一个日记APP

把一部动漫(未来日记)中的日记软件搬到现实!

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是什么)。

  1. 让初始Activity继承FragmentActivity,方便我们操作Fragment。

  2. 继承FragmentPagerAdapter,得到一个专门管理Fragment的适配器。一开始我搞不懂适配器的作用,后来才明白,我把许多Fragment放在一个普通数组里,如果没有适配器,我就不知道数组里有多少Fragment,也不知道自己是否已经越界,所以说,适配器的作用是:管理数组。

  3. 弄一个ViewPager,捕获Fragment适配器(mViewPager.setAdapter(mFragmentPagerAdapter);

  4. 实现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
2
3
4
5
6
7
8
9
<Button
android:id="@+id/buttonLeft"
android:layout_width="70dp"
android:layout_height="match_parent"
android:background="@color/black"
android:onClick="onClickListenerTabBar"
android:text="@string/today"
android:textColor="@color/white"
android:textSize="14sp" />

为了使用不同的style,以便让button的背景颜色随着style的变化而变化,我们需要稍微修改红色加粗的地方

1
2
3
4
5
6
7
8
9
<Button
android:id="@+id/buttonLeft"
android:layout_width="70dp"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
android:onClick="onClickListenerTabBar"
android:text="@string/today"
android:textColor="@color/white"
android:textSize="14sp" />

“?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
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/green_pink</item>
<item name="colorPrimaryDark">@color/green_pink</item>
<item name="colorAccent">@color/green_pink</item>

</style>

<!-- Night mode theme. -->
<style name="AppTheme.Night" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryDark">@color/black</item>
<item name="colorAccent">@color/black</item>
</style>

就这么几句(注意粗体内容),当我们切换了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版本,功能更新如下:

  1. UI改版,更轻更快

  2. 增加手势密码

  3. 增加夜间模式

  4. 修复一些BUG

编写界面截图:write_day

日记列表截图:device-2015-12-08-043640

2015年12月9日

今天有更新了最新版本的用户反映说:“在新版本上,每打一个字就会弹出一次输入法。”

我想了想,觉得可能是我在onResume()里面监听输入法造成的(出于交互体验考虑,输入法会自动开启和自动关闭),于是我去掉了监听代码,暂时舍弃输入法自动关闭功能,重新上架,希望不会有太多差评。