1、向金成-精读

Wumengyang_branch
向金成 2 years ago
parent dfcbfd2df2
commit c39d1c5661

@ -0,0 +1,339 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
version 表示应该用什么版本号的解析器去解析该XML文件
encoding 表示应该用什么样的编码去翻译
-->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- 1、xmlns为命名空间属性android 为命名空间名字http://schemas.android.com/apk/res/android URI标识命名空间资源在互联网上位置
2、XML中有元素便签和属性2个概念元素的形式为<元素名 属性名="属性的值“> </元素名>
3、<manifest> </manifest>为根元素(便签) 即第一个出现的元素,是后面元素的根
4、<uses-sdk android:minSdkVersion="14"/> 为空便签,与属性配合 空便签形式为<元素名/>
5、元素可以进行嵌套
6、每个AndroidManifestxml文件中必须包含其只能包含一个manifest元素
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.micode.notes"
android:versionCode="1"
android:versionName="0.1" >
<!--
1、package的属性值是java目录下的软件包的包名也是默认的进程名同时也是命名空间名且值在后面组件中会替换以.开头的声明,
如android:name=".ui.NotesListActivity" 会解析成android:name="net.micode.notes.ui.NotesListActivity"
2、versionCode为命名空间android的属性表示应用版本号是用于更新的应为1、2、3这样的形式
3、versionName也为命名空间android的属性是给用户看的当前版本号可以为1.11.21.2.1 这样的形式
-->
<uses-sdk android:minSdkVersion="14" />
<!--
配置SDK版本的在此标签下可以给android:minSdkVersion android:maxSdkVersionandroid:targetSdkVersion 这3种属性
进行设置属性值为API版本与android的版本有对应关系minSdkVersion的值指定了能使用该软件的最小API也间接指定了最小的安卓版本
maxSdkVersion的值指定了最大的安卓版本当前安卓版本高于设置的则不能使用用于targetSdkVersion的值指定了目标安卓版本在此版本下
已通过测试
-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--
该便签为使用应用必须授予的权限android:name为权限名名字android.permission.WRITE_EXTERNAL_STORAGE为权限名属性值
不同属性值表示要授予不同的权限,该便签表示必须授权应用将数据写入外部储存的权限
-->
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!--
应用必须授予的权限是 能在桌面创建快捷方式
-->
<uses-permission android:name="android.permission.INTERNET" />
<!--
申请访问网络的权限
-->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!--
申请读取联系人信息的权限
-->
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<!--
申请管理账户的权限
-->
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<!--
申请验证账户的权限
-->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!--
申请获得gmail账户的权限
-->
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<!--
申请使用凭证的权限,与账户管理有关
-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!--
申请开机启动应用的权限
-->
<application
android:icon="@drawable/icon_app"
android:label="@string/app_name" >
<!--
1、manifest元素必须且只能包含一个application元素application元素中声明了应用需要用到的组件及其属性
2、android:icon 属性是应用的图标,属性值是对包含图片的可绘制资源的引用,即@res文件夹下的文件名/图片
3、android:label 属性是应用的标签,属性值是对字符串资源的引用
-->
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan" >
<!--
1、activity元素 声明了一个可视化界面是Activity类的一个子类应用所有的可视化界面都必须在清单里进行声明未声明的不会进行服务
2、android:name属性 是activity类的类名属性值必须是完全限定类名即带包名用.隔开的路径名,当前类名以.开头,.等于manifest
中的package的属性值所以完整类名是net.micode.notes.ui.NotesListActivity必须设置名字
3、android:configChanges属性 是指定了该activity类自动处理的配置变更若无该属性当配置变更时会重启该界面有该属性不会重启
保证一直活跃,属性值为该界面会自动处理的配置变更。
keyboardHidden 用户打开虚拟键盘orientation 用户更改手机界面反向旋转手机screenSize 界面方向改变后,界面尺寸改变
多个属性值之间用|分隔
4、android:label属性 是指定当前界面的便签和该activity元素嵌套的intent-filter元素的默认便签若未指定则为application元素里指定的便签
5、android:launchMode属性 是该activity类的启动模式一般用standard和singeTop模式standard默认模式下系统始终会
实例化一个新的activity进行intent传递singleTop模式下如果目标任务的栈顶有activity实例不会再实例化而是通过方法传递intent。
6、android:theme属性 指定了activity的主题是对样式资源的引用
7、android:uiOptions属性 指定了界面的额外选项属性值splitActionBarWhenNarrow是当竖屏是分隔出一个操作栏
8、android:windowSoftInputMode属性 指定了主窗口与包含软键盘窗口之间的交互方式
-->
<intent-filter>
<!--
intent-filter元素 指定了父activity、receiver、provider组件能够响应的intent对象不是指定的类型则过滤掉
通过子元素actioncategory、data来进行过滤器的大部分内容设置
组件之间通过intent传递消息
-->
<action android:name="android.intent.action.MAIN" />
<!--
action元素 指定了可接受的intent对象的操作android:name属性为操作名属性值为操作
intent对象有一些标准的操作形式为ACTION_MAIN、ACTION_INSERT等等在将标准操作分配给action元素时属性值为
android.intent.action.MAIN 这样的类型,若分配自定义操作,则属性值最好为 软件包名.操作名
android.intent.action.MAIN 该属性值表名当前activity为主程序入口即最先启动的activity
-->
<category android:name="android.intent.category.LAUNCHER" />
<!--
category元素 指定了可接受intent对象的类别 android:name为类别名属性intent中也有一些标准category在使用标准
category时也想action属性一样应加前缀android.intent.category.类别名,若要用自定义类别也最好软件包名.类别名
android.intent.category.LAUNCHER属性值 指定该activity是第一个启动的即应用主界面与android.intent.action.MAIN
配对使用
-->
</intent-filter>
</activity>
<activity
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/NoteTheme" >
<!--
1、指定类名为 net.micode.notes.ui.NoteEditActivity
2、自动处理的配置变更 软硬盘|屏幕旋转|界面大小改变
3、启动模式 singleTop 当目标任务的栈顶有该activity实例时不新实例化而只向其传递intent
4、界面主题 引用样式资源 NoteTheme
-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<!--
intent过滤器的action元素的 属性值为android.intent.action.VIEW指定该activity 是显示用户数据的,根据不同数据
显示不同界面
-->
<category android:name="android.intent.category.DEFAULT" />
<!--
指定该activity能接受隐式intent
-->
<data android:mimeType="vnd.android.cursor.item/text_note" />
<!--
data属性 是指定该intent携带的数据形式android:mimeType属性 是指定该数据的类型
/前面的与URI的数据有关如果URI的数据是图片则为image/后面的是具体的类型如图片的格式可以是jpg
vnd.android.cursor.item/text_note属性值 指定URI包含的数据是单条的且具体的类型是text_note
这个类型是自定义的URI的数据类型是通过provider的getTypeURL uri获得的
-->
<data android:mimeType="vnd.android.cursor.item/call_note" />
<!--
URI的数据是单条的类型是call_note
-->
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<!--
指定了该activity能响应的操作是 添加编辑联系人
-->
<category android:name="android.intent.category.DEFAULT" />
<!--
指定该intent的类别可以是隐式
-->
<data android:mimeType="vnd.android.cursor.item/text_note" />
<!--
指定了该intent携带的数据的类型数据是单条的类型为text_note
-->
<data android:mimeType="vnd.android.cursor.item/call_note" />
<!--
指定携带的数据类型数据是单条的类型为call_note
-->
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<!--
指定activity能响应的intent的动作是搜索即能开始搜索操作
-->
<category android:name="android.intent.category.DEFAULT" />
<!--
指定该intent为默认类别,可以是隐式intent
-->
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<!--
1、meta-data元素 向父组件提供任一数据项的名值对(数据名称 对应值)
2、android:name="android.app.searchable" 指定该元素名称
3、android:resource 对资源的引用资源的ID为该名值对的值
4、如为android:value 也是对资源的引用,资源包含的值为该名值对的值
-->
</activity>
<provider
android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" />
<!--
1、provider元素 声明了一个content provider组件即一个ContentProvider的子类是用来向外界提供可访问内容的
2、该组件的名字为net.micode.notes.data.NotesProvider
3、android:authorities属性 是指定提供该内容的授权方,用来标识提供的数据的,至少一个,多个则分号分隔
4、android:multiprocess属性 指定了若应用在多个进程间运行时是否每个进程都有一个content provider实例true则是
-->
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2" >
<!--
1、receiver元素 声明了一个广播接受器即BroadcastReceiver的子类能接受来着系统其他应用自己应用的intent对象
即使其他组件未运行
2、该组件名称为 net.micode.notes.widget.NoteWidgetProvider_2x
3、标签 对字符串资源的引用 @string/app_widget2x2
-->
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<!--
指定该receiver能响应的intent的动作为 应用挂件更新
-->
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<!--
指定响应intent的动作为 应用挂件删除
-->
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
<!--
指定响应的intent的动作为 私人模式变更
-->
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
<!--
向该父组件 receiver 提供一个数据项的名值对,名为 android.appwidget.provider值为 资源@xml/widget_2x_info的id
-->
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4" >
<!--
声明了一个完全限定类名为net.micode.notes.widget.NoteWidgetProvider_4x的广播接收器子类标签为对@string/app_widget4x4
的引用
-->
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
<!--
指定的响应动作 应用挂件更新,应用挂件删除,私人模式变更
-->
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_4x_info" />
<!--
向该父组件提供一个数据项的名值对
-->
</receiver>
<receiver android:name=".ui.AlarmInitReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<!--
声明一个广播接收器组件响应的intent的动作为 开机自启
-->
</receiver>
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
<!--
声明一个广播接收器组件android:process=":remote"属性 指定该组件的进程名为:remote
-->
</receiver>
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
<!--
声明了一个界面android:launchMode属性 指定了该activity的启动模式为singleInstance即为该activity创建一个单独的任务栈
且只能有一个该activity其他activity不能进入界面主题 @android:style/Theme.Holo.Wallpaper.NoTitleBar的引用
-->
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
<!--
声明了一个界面启动模式singleTop主题@android:style/Theme.Holo.Light的引用
-->
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
<!--
1、声明了一个服务组件Service的子类完全限定类名为net.micode.notes.gtask.remote.GTaskSyncService
2、android:exported属性 指定了该组件是否能被其他应用所访问,交互
-->
</service>
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
<!--
向父组件 application提供了一个数据项的名值对名为android.app.default_searchable值为net.micode.notes.ui.NoteEditActivity
类包含的值
-->
</application>
</manifest>

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--
该XML文件是应用的颜色资源文件能为VIEW组件中的处于不同状态的组件提供显示的颜色如点击、不点击等等状态下VIEW组件显示的颜色
以selector为根元素必须有命名空间子元素为item 用于定义不同状态下,应该显示的颜色,状态属性定义状态,颜色属性定义颜色
-->
<item android:state_pressed="true" android:color="#88555555" />
<!--
1、android:state_pressed属性 定义了该状态为按下状态,属性值为true则指定了应在按下状态会显示该颜色为false则指定了在未按下状态显示该颜色
2、android:color属性 定义了该状态下应该显示的颜色,属性值指定了什么颜色,以#开头的16进制颜色
-->
<item android:state_selected="true" android:color="#ff999999" />
<!--
定义该状态为选中状态属性值为true则应在选中的状态下显示该颜色
-->
<item android:color="#ff000000" />
<!--
默认颜色默认颜色item元素必须在最后一个
-->
</selector>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
<!--
定义了默认颜色
-->
</selector>

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--
drawable文件夹 为可绘制图形的资源可以是直接的图片九宫格等等也可以是XML文件对资源进行进一步的配置
该XML文件为状态列表文件是对一个对象的不同状态下显示的可绘制图形的定义与color资源一样selector为根元素
子元素为item不同状态下显示的不同可绘制图形
-->
<item android:state_pressed="true"
android:drawable="@drawable/new_note_pressed" />
<!--
1、android:state_pressed定义了按下状态属性值为true则应当在按下状态下显示当前定义的可绘制图形
2、android:drawable属性 定义了应显示的可绘制图形,是对可绘制图形的引用 new_note_pressed图形是在drawable-hdpi文件夹下
该文件夹也是可绘制图形,但为高密度的图形,对其应用也是 @drawable/名字
-->
<item
android:drawable="@drawable/new_note_normal" />
<!--
定义了默认的可绘制图形
-->
</selector>

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<!--
1、layout文件是界面或控件的布局资源布局资源是XML文件根元素是ViewGroup一个布局容器包含多个View对象如文本按钮或作为View
对象的子布局等等或View布局容器有多种类型如线性布局相对布局等等
2、该布局XML文件的布局容器为线性布局线性布局是水平或垂直的方向由LinearLayout的android:orientation属性设置
3、android:layout_width属性 指定了布局的宽度 属性值可以是尺寸单位或关键字match_parent 替代fill_parent 尺寸与父元素一样
warp_content 包装内容,尺寸大小适配内容大小 等等) 该元素尺寸是适配父元素
4.android:layout_height属性 指定的布局的高度
5、android:orientation属性 指定线性布局是水平还是垂直 vertical指定垂直
-->
<TextView
android:id="@+id/account_dialog_title"
style="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center"
android:layout_marginTop="-2.7dip"
android:layout_marginBottom="-2.7dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
tools:ignore="VisualLintBounds" />
<!--
1、TextView元素声明了一个向用户显示文本的view子类不能被用户编辑要编辑则为EditText
2、android:id属性 指定了该view对象资源的id用于引用属性值为 @+id/名字 +代表如果该id不在id文件里则新建一个id
3、style属性 是对安卓预定义样式的引用,属性值为你要引用的样式,当前是文本外观中等
4、android:singleLine属性 指定了文本是否单行显示已废弃使用lines属性
5、android:ellipsize属性 指定了省略号在那个位置显示ellipsize是拼写错误
6、android:gravity属性 指定了该控件里的元素的对齐方式,当前是横纵居中
7、android:layout_marginTop属性 指定了上外边距的大小即与父组件上边界的距离当前属性值为负数则该view对象的上边距在父组件里
有一部分重叠,用于遮盖
8、android:layout_marginBottom属性 指定了下外边距的大小,即与父组件下边界的距离,值为负,则与父组件有重叠
9、android:layout_width属性 当前指定该view的宽与父组件一样android:layout_height属性 指定当前的高于内容相适配
-->
<TextView
android:id="@+id/account_dialog_subtitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dip"
android:layout_marginBottom="1dip"
android:gravity="center"/>
<!--
1、声明了一个显示文本的view对象id指定为account_dialog_subtitle当id文件中没有时新建
2、指定宽与父组件一样高于内容匹配
3、指定了上外边距为5dip下外边距为1dip
4、指定了该控件里的内容的对齐方式为横纵居中
-->
</LinearLayout>

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dip"
android:gravity="center_vertical"
android:orientation="vertical">
<!--
1、声明了一个线性布局容器宽度与父组件一样高度与内容匹配即能刚好包含内容
2、android:minHeight属性 指定了最小高度当前值50dip
3、对齐方式 垂直居中
4、方向 垂直
-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/preferences_add_account" />
<!--
1、声明了一个显示文本view对象高度和宽度与内容适配
2、android:layout_gravity属性 是指定了该控件整体与父组件的对齐方式,而不是内容 当前很横纵居中
3、android:textAppearance属性 指定了文本外观,当前是对于预定义样式的引用
4、android:text属性 指定该view对象显示的文本字符串资源引用
-->
</LinearLayout>

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!--
1、声明了一个线性布局方向为水平相对于父组件的对齐方式是 水平居中
2、指定了布局的宽高尺寸都适配内容
-->
<NumberPicker
android:id="@+id/date"
android:layout_width="120dip"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<!--
1、NumberPicker为数值选择器声明了一个数组选择器id为date
2、指定了宽为恒定的大小120dip高度是自适应的适配内容
3、android:focusable属性 指定了该组件是否可以通过物理操作获取焦点即物理键盘,同一时间只能有一个控件能获得焦点,焦点控件能对消息进行响应处理,
当前为true可以获得焦点
4、android:focusableInTouchMode属性 指定了该控件能否通过触摸方式获得焦点,当前真。
-->
<NumberPicker
android:id="@+id/hour"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/><!--
1、声明了一个数值选择器id为hour
2、宽为50dip高适配内容左外边距5dip
3、focusable属性为真则能通过物理操作方式使该控件获得焦点
4、focusableInTouchMode属性为真 控件能通过触摸方式获得焦点
-->
<NumberPicker
android:id="@+id/minute"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/><!--
1、声明了一个数值选择器id为minute
2、宽50dip高适配内容左外边距5dip
3、可以通过物理方式触摸方式获得焦点
-->
<NumberPicker
android:id="@+id/amPm"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/><!--
1、声明了一个数值选择器id为amPm
2、宽50dip高适配内容左外边距5dip
3、可以通过物理方式触摸方式获得焦点
-->
</LinearLayout>

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/et_foler_name"
android:layout_width="fill_parent"
android:hint="@string/hint_foler_name"
android:layout_height="fill_parent" />
<!--
1、view对象为布局xml文件的根元素声明了一个可以供用户编辑的文本id为et_foler_name
2、宽、高都与父布局一样
3、android:hint属性 指定了提示,当前是引用了一个字符串资源,可以直接输入字符串
-->

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="50dip" ><!--
1、声明了一个线性布局默认水平布局
2、宽高都与父布局一样
3、指定了内部元素的最小高度为50dip
-->
<TextView
android:id="@+id/tv_folder_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textAppearance="@style/TextAppearancePrimaryItem" /><!--
1、声明了一个显示文本的view对象id为tv_folder_name
2、宽高都与父组件一样
3、内容对齐方式为横纵居中
4、文本外观是对样式资源的引用
-->
</LinearLayout>

@ -0,0 +1,238 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;/*
androidappActivityactivity
*/
import android.app.AlertDialog;/*
androidappAlertDialog
*/
import android.content.Context;/*
androidcontentContextcontext
*/
import android.content.DialogInterface;/*
androidcontentDialogInterfaceDialog
*/
import android.content.DialogInterface.OnClickListener;/*
androidcontentDialogInterfaceOnClickListener
*/
import android.content.DialogInterface.OnDismissListener;/*
androidcontentDialogInterfaceOnDismissListener
*/
import android.content.Intent;/*
Intentintent
*/
import android.media.AudioManager;/*
androidmediaAudioManager
*/
import android.media.MediaPlayer;/*
MediaPlayer
*/
import android.media.RingtoneManager;/*
RingtoneManager
*/
import android.net.Uri;/*
netUriuri
*/
import android.os.Bundle;/*
osBundleactivityintentbundlebundle
intent
*/
import android.os.PowerManager;/*
osPowerManager
*/
import android.provider.Settings;/*
Settings
*/
import android.view.Window;/*
Windowviewview
*/
import android.view.WindowManager;/*
*/
import net.micode.notes.R;/*
Rid
*/
import net.micode.notes.data.Notes;/*
net.micode.notesdataNotes
*/
import net.micode.notes.tool.DataUtils;/*
net.micode.notestoolDataUtils
*/
import java.io.IOException;/*
java ioIOException
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
/* activityActivityOnClickListener, OnDismissListener
*/
private long mNoteId; //便签id变量
private String mSnippet;//字符串变量
private static final int SNIPPET_PREW_MAX_LEN = 60;//静态不可修改的整形变量
MediaPlayer mPlayer;//定义了一个MediaPlaye变量
@Override
protected void onCreate(Bundle savedInstanceState) {/*
onCreateactivitybundle
*/
super.onCreate(savedInstanceState);/*
*/
requestWindowFeature(Window.FEATURE_NO_TITLE);/*
window
*/
final Window win = getWindow();/*
getWindow
*/
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);/*
windowflagsflagswindowManagerLayoutParams
*/
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
/* 屏幕不亮时增加新的flags显示逻辑*/
}
Intent intent = getIntent();/*
getIntent intent
*/
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));/*
getDataintenturigetPathSegments()uri/list
intent便idmNoteId
*/
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);/*
DataUtilsgetSnippetByIdmSnippetgetContentResolveractivity
contentResolveractivity
*/
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)/*
getResources() ResourcesgetStringid id
R.string.notelist_string_info notelist_string_infoid
*/
: mSnippet;
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer();//实例化MediaPlayer类
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
/* 判断便签是否在便签数据库里,在则显示对话框,播放闹钟声音*/
showActionDialog();
playAlarmSound();
} else {
finish();//关闭该activity打开的一切资源finish后调用onDestory销毁该activity
}
}
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);/*
getSystemServicecontext,owerManager
*/
return pm.isScreenOn();//判断屏幕是否亮
}
private void playAlarmSound() {
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);/*
getActualDefaultRingtoneUriURI
*/
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);/*
getInt
*/
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);/*
*/
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
mPlayer.setDataSource(this, url);//设置播放的多媒体资源URI
mPlayer.prepare();//准备中
mPlayer.setLooping(true);//设置是否一直循环播放
mPlayer.start();//开始播放
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);/*
AlertDialogbuilder
*/
dialog.setTitle(R.string.app_name);//设置对话框标题
dialog.setMessage(mSnippet);//设置对话框内容
dialog.setPositiveButton(R.string.notealert_ok, this);//设置响应一般YES的按钮按钮的标题为参数
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);//屏幕亮,设置一般响应取消的按钮
}
dialog.show().setOnDismissListener(this);//显示对话框,并且设置一个监听用户取消对话框的监听器
}
public void onClick(DialogInterface dialog, int which) {/*
*/
switch (which) {
case DialogInterface.BUTTON_NEGATIVE://如果用户点击negative按钮
Intent intent = new Intent(this, NoteEditActivity.class);/*
intentNoteEditActivity
*/
intent.setAction(Intent.ACTION_VIEW);//设置intent的action接受的activity显示数据
intent.putExtra(Intent.EXTRA_UID, mNoteId);//设置activity响应操作所需的额外的数据是键值对
startActivity(intent);//将intent对象传递给NoteEditActivity类
break;
default:
break;
}
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}/*响应用户取消停止播放闹钟结束该activity调用的一切资源*/
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}/* 非空,停止播放,释放资源,置空*/
}

@ -0,0 +1,80 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};//定义了一个字符串数组
private static final int COLUMN_ID = 0;//一栏的id
private static final int COLUMN_ALERTED_DATE = 1;//一栏的闹钟响的日期
@Override
public void onReceive(Context context, Intent intent) {/*
receiverReceiveronReceive广
*/
long currentDate = System.currentTimeMillis();//获取当前时间,毫秒为单位
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,//URI代表数据库的中的某个表
PROJECTION,//要查询的列
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,/*
where selectionArgs
*/
new String[] { String.valueOf(currentDate) },//替代占位符的
null);//order by 排序当前null默认升序
/* queryselect
cursor
*/
if (c != null) {//查询结果成功
if (c.moveToFirst()) {//移动到第一行
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);//将COLUMN_ALERTED_DATE列的值转化为long赋值给变量
Intent sender = new Intent(context, AlarmReceiver.class);//向AlarmReceiver类传递intent对象
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));/*
senderdataURIURIid,idURIid
*/
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);/*
PendingIntent.getBroadcastPendingIntent
activityservicebroadcast3broadcast广intent
sender
*/
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);//通过系统服务来获得当前的闹钟管理器
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);/*
使
*/
} while (c.moveToNext());//c指向下一行
}
c.close();//关闭游标
}
}
}

@ -0,0 +1,32 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class);//将接受到的intent对象传给AlarmAlertActivity类
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);/*
intentflagsAlarmAlertActivityactivity
*/
context.startActivity(intent);//启动activity传递intent
}
}

@ -0,0 +1,500 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
public class DateTimePicker extends FrameLayout {/*
FrameLayoutview
*/
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
private final NumberPicker mDateSpinner;//选择日期
private final NumberPicker mHourSpinner;//选择时间
private final NumberPicker mMinuteSpinner;//选择分钟
private final NumberPicker mAmPmSpinner;//选择上下午
/*
NumberPicker4
*/
private Calendar mDate;//定义一个Calendar类对象用于获取时间
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];//创建一个字符串数组显示日期的值DAYS_IN_ALL_WEEK为值
private boolean mIsAm;//定义一个布尔类型变量,判断是否是上午
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;//判断是否能够使用
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
/* 实例化OnValueChangeListener用于监听数组选择器值的改变,当前是创建一个监听日期变化的监听器 */
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
//实例化时重写接口方法,该方法来响应值变化
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);//mDate 日历添加日期
updateDateControl();//更新
onDateTimeChanged();//同步
}
};
/* 创建一个监听小时变化的监听器*/
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();//实例化一个calendar
if (!mIs24HourView) {//不是24h制时间
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());//获取ms单位的时间并设置cal的时间
cal.add(Calendar.DAY_OF_YEAR, 1);//增加日期
isDateChanged = true;//下午11h、12h交替更改
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;//上午12点11点交替的更改
}
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;//更改Amam pm
updateAmPmControl();
}
} else {//是24h时间
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;//23点到0点的交替时更改
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;//0点到23点交替时更改
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);/*
hourampm
*/
mDate.set(Calendar.HOUR_OF_DAY, newHour);//设置日期
onDateTimeChanged();//同步
if (isDateChanged) {//日期变化后,更改年、月。日
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
/* 创建一个监听min改变的监听器*/
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();//获得数组选择器能选择的最大、最小值
int offset = 0;//标志是否hour改变
if (oldVal == maxValue && newVal == minValue) {
offset += 1;//hour+1
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;//hour-1
}
if (offset != 0) {//hour改变
mDate.add(Calendar.HOUR_OF_DAY, offset);//新增一个日期
mHourSpinner.setValue(getCurrentHour());//获得当前hour值设置mHourSpinner选择器的值
updateDateControl();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {//更改am、pm
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);//修改date值
onDateTimeChanged();
}
};
/* 监听am、pm改变*/
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
//监听data改变的监听器接口
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}//通过当前时间构造
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));//是24小时制的选择器
}
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();//实例化calendar
mInitialising = true;//标志正在初始化
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);//将datetime_picker XML布局文件转化为view对象
mDateSpinner = (NumberPicker) findViewById(R.id.date);//将id为date的view实例化
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);//设置可以选择的最小值
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);//设置可以选择的最大值
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);//设置监听选择的值改变的监听器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);//实例化用于选择hour的选择器
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);//设置长按改变数值增加的量
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();//返回表示上午下午格式的字符串
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);//实例化选择ampm的选择器
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);//设置显示的文本
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
}
@Override//设置能否使用
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
//以下都是控制日期小时ampm转化的
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
//响应日期改变
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,92 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();//calendar变量便于时间操作
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;//监听用户设置时间
private DateTimePicker mDateTimePicker;//view对象选择器
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
super(context);//实例化
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);//添加视图
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);//设置当前时间
updateTitle(mDate.getTimeInMillis());
}
});//向选择时间的选择器设置监听时间改变的监听器
mDate.setTimeInMillis(date);//设置时间毫秒单位
mDate.set(Calendar.SECOND, 0);//秒数设置为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());//选择器设置当前时间
setButton(context.getString(R.string.datetime_dialog_ok), this);//设置按钮
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);//设置按钮
set24HourView(DateFormat.is24HourFormat(this.getContext()));//设置日期是24小时制
updateTitle(mDate.getTimeInMillis());//更新显示日期
}
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
//用于格式日期显示
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;//DateUtils类日期格式工具类
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
//响应用户点击设置按钮
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,65 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;//下拉菜单
private Menu mMenu;
//响应用户下拉菜单
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);//设置背景
mPopupMenu = new PopupMenu(context, mButton);//mButton代表下拉菜单要通过点击mButton来打开
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);//获取布局文件
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});//监听用户点击
}
//设置监听下拉菜单中选项被选中的监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
//查找菜单中的项
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
//设置按钮的显示文本
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -0,0 +1,86 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
/* CursorAdapter类 是用于 cursor和listview的连接*/
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};//要显示的数据库的列
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}//在文件夹列表中创建一个新的项,即一个新的文件夹
@Override
//如果view是文件夹列表中的一个项设置view的名字
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);//判断设置什么名字
((FolderListItem) view).bind(folderName);
}
}
//获得文件夹名
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
//创建一个类用于文件夹列表中的项
private class FolderListItem extends LinearLayout {
private TextView mName;
//绘制项
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);//实例化布局文件
mName = (TextView) findViewById(R.id.tv_folder_name);//获得名字
}
//设置文本
public void bind(String name) {
mName.setText(name);
}
}
}

@ -0,0 +1,912 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
private class HeadViewHolder {
public TextView tvModified;
public ImageView ivAlertIcon;
public TextView tvAlertDate;
public ImageView ibSetBgColor;
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();//存储资源
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}//存储资源id和对应资源
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}//以上都是①map存储数据
private static final String TAG = "NoteEditActivity";
private HeadViewHolder mNoteHeaderHolder;
private View mHeadViewPanel;//声明一个view对象用于第一个显示
private View mNoteBgColorSelector;//用于便签颜色选择的view
private View mFontSizeSelector;//用于设置字体对象的view
private EditText mNoteEditor;//用于文本编辑的v控件
private View mNoteEditorPanel;//用于文本编辑的面板 view
private WorkingNote mWorkingNote;//初始化便签
private SharedPreferences mSharedPrefs;//声明了SharedPreferences类数据存储类
private int mFontSizeId;
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
private LinearLayout mEditTextList;//声明了一个线性布局
private String mUserQuery;
private Pattern mPattern;
//绘制界面
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
}
/**
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*/
//通过保存的activity信息来恢复一个activity
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { //如果intent动作为显示的话即显示便签
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
/**
* Starting from the searched result
*/
//如果有数据键的话根据键查便签id
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
}
//在数据库中无id
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {
Intent jump = new Intent(this, NotesListActivity.class);
startActivity(jump);
showToast(R.string.error_note_not_exist);//显示便签不存在
finish();
return false;
} else {//在数据库中有id
mWorkingNote = WorkingNote.load(this, noteId);//加载id
if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId);
finish();
return false;//初始化便签失败,打印错误信息
}
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);/*
*/
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {
//接受的intent的action是ACTION_INSERT_OR_EDIT编辑便签
// New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this));
//获得intent中携带的数据
// Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
if (callDate != 0 && phoneNumber != null) {//intent中有电话号码calldate
if (TextUtils.isEmpty(phoneNumber)) {//如果未记录电话号
Log.w(TAG, "The call record number is null");
}
long noteId = 0;
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {//如果根据电话号码有id
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load call note failed with note id" + noteId);
finish();
return false;
}
} else {//没有则创建新的便签,电话号给电话簿
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate);
}
} else {//没有电话号则创建空的便签
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId);
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {//不是显示便签也不是编辑便签,报错
Log.e(TAG, "Intent not specified action, should not support");
finish();
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);
return true;
}
//恢复activity启动
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}
//初始化便签界面
private void initNoteScreen() {
//设置文本外观
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));
/**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader();
}
//设置闹钟
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {//便签设置了闹钟
long time = System.currentTimeMillis();
if (time > mWorkingNote.getAlertDate()) {//系统时间大于设置的闹钟时间
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);//设置闹钟图标,可见
} else {//未设置闹钟图标,设置闹钟日期,图标过期
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
};
}
@Override
protected void onNewIntent(Intent intent) {//当前activity再次被调用且一个实例在栈顶通过onNewIntent方法接受intent
super.onNewIntent(intent);
initActivityState(intent);
}
//activity被销毁前储存当前activity信息
@Override
protected void onSaveInstanceState(Bundle outState) {//离开界面时保存的数据
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {//便签不在数据库进行保存
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
@Override//分发触屏事件
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}//设置颜色选择器
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}//设置字体大小选择器
return super.dispatchTouchEvent(ev);
}
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}//如果触摸坐标位置超出view返回false
return true;
}
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);//设置颜色选择器的显示view和监听器
}//对开头声明的变量的初始化,初始化布局等
mFontSizeSelector = findViewById(R.id.font_size_selector);
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);//设置字体大小选择器的各字体的view和监听器
};
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);//获得默认字体大小id
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {//默认字体大小id超出范围进行更改
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);//实例化布局
}
//暂停activity淡出
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
//更新桌面挂件,进行同步
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
//响应用户对view对象的点击颜色字体大小选择器
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
}
//响应用户点击back按键
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
//响应back前先清空设置
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
//响应便签背景颜色改变,当用户通过颜色选择器选择颜色
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
//准备选择菜单
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);//实例化menu
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);//实例化菜单中的项
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}
//响应菜单中某项被选择
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {//根据项的id进行操作
case R.id.menu_new_note:
createNewNote();//选择创建便签调用createNewNote方法
break;
case R.id.menu_delete://选择删除便签
AlertDialog.Builder builder = new AlertDialog.Builder(this);//弹出一个对话框,进行选择
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,//设置确定按钮
new DialogInterface.OnClickListener() {//对该按钮设置点击监听器
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);//设置取消按钮
builder.show();//显示对话框
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE);//设置字体选择器可见
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);//应用字体
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop://送到桌面
sendToDesktop();
break;
case R.id.menu_alert://设置闹钟
setReminder();
break;
case R.id.menu_delete_remind://删除闹钟
mWorkingNote.setAlertDate(0, false);
break;
default:
break;
}
return true;
}
//设置闹钟
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());//实例闹钟对话框
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {//响应日期设置
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true);
}
});
d.show();
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
//向某个应用发送
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);//设置传递信息,
intent.setType("text/plain");//设置类型
context.startActivity(intent);//传递
}
//创建新的便签
private void createNewNote() {
// Firstly, save current editing notes
saveNote();
// For safety, start a new NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);//设置动作为编辑
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());//设置键值对数据文件夹id
startActivity(intent);
}
//删除当前便签
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {//在数据库中
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);//不是头文件id保存下来
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {//不是同步模式,进行删除
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true);//标记已删除
}
//判断是否是同步模式
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
//响应闹钟改变
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {//当前便签不在数据库中先保存
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {//有便签,进行闹钟更改
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {//没有便签
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {
updateWidget();
}
//响应编辑文本框删除
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();//返回子元素个数view
if (childCount == 1) {//子view个数=1则无文本编辑框
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index);//根据index删除view
NoteEditText edit = null;
if(index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);
edit.requestFocus();//请求焦点,用于处理
edit.setSelection(length);
}
//响应打开文本编辑框
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);
mEditTextList.addView(view, index);//向其添加一个新的view
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();//请求焦点,用于处理
edit.setSelection(0);
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
//切换到列表模式
private void switchToListMode(String text) {
mEditTextList.removeAllViews();//移除所有view
String[] items = text.split("\n");
int index = 0;
for (String item : items) {
if(!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
index++;//item不为空向编辑文本列表变量中加该view
}
}
mEditTextList.addView(getListItem("", index));
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mNoteEditor.setVisibility(View.GONE);//文本编辑器不可见
mEditTextList.setVisibility(View.VISIBLE);//设置文本列表可见
}
//获得高亮的结果
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
//获取列表项
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//实例化view
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));//设置复选框
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
});
if (item.startsWith(TAG_CHECKED)) {//被勾选
cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
item = item.substring(TAG_CHECKED.length(), item.length()).trim();
} else if (item.startsWith(TAG_UNCHECKED)) {//未被勾选
cb.setChecked(false);
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
//响应文本修改
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {//位置超过范围
Log.e(TAG, "Wrong index, should not happen");
return;
}
if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
}
//响应点击列表模式改变
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {//切换到列表模式
switchToListMode(mNoteEditor.getText().toString());
} else {
if (!getWorkingText()) {
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
""));
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
}
//
private boolean getWorkingText() {
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//当前模式是列表
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {//变量列表中列表项
View view = mEditTextList.getChildAt(i);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
hasChecked = true;//选中
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");//不选中
}
}
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
}
return hasChecked;
}
//保存便签
private boolean saveNote() {
getWorkingText();
boolean saved = mWorkingNote.saveNote();
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
}
return saved;
}
//送到桌面
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();//先保存
}
if (mWorkingNote.getNoteId() > 0) {//有便签
Intent sender = new Intent();//建立一个intent用于发送信息保存有要发送的信息
Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent()));
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");//动作为安装到桌面
showToast(R.string.info_note_enter_desktop);
sendBroadcast(sender);//进行广播
} else {//无便签报错
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");
showToast(R.string.error_note_empty_for_send_to_desktop);
}
}
//编辑小图标标题
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, "");
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
//进行提示显示
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
//进行提示显示
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
}

@ -0,0 +1,229 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
//hashmap 建立电话号邮件web地址的连接
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
//监听文本改变监听器接口
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
//根据context设置文本
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
//设置光标
public void setIndex(int index) {
mIndex = index;
}
//设置监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
//AttributeSet自定义控件的属性
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override//响应触摸事件
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();//更新坐标
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);//更新布局
Selection.setSelection(getText(), off);//更新光标位置
break;
}
return super.onTouchEvent(event);
}
//响应用户按下按键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER://点击enter按键
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL://点击del按键
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);//其他按键执行父类方法
}
//响应松开按键
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {//被修改的话
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {//之前删除过且文本非空
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {//修改过
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);//调用监听器的对应方法
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
//响应焦点改变
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {//如果有改变
if (!focused && TextUtils.isEmpty(getText())) {//有焦点,文本不空
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
//创建上下文菜单
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {//有文本
int selStart = getSelectionStart();//获取开始位置
int selEnd = getSelectionEnd();//获取结束位置
int min = Math.min(selStart, selEnd);//获取从开头到结尾的最小值
int max = Math.max(selStart, selEnd);//最大值
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
//设置url的信息的范围值
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
//建立菜单
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}
}

@ -0,0 +1,229 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
//数据库中保存的便签的属性
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
};
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;//设置属性值
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
//构造函数,设置通过光标能获得的属性
public NoteItemData(Context context, Cursor cursor) {
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
//初始化电话号
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
if (mName == null) {
mName = "";
}
checkPostion(cursor);
}
//检测cursor指定项的位置
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
mIsOneNoteFollowingFolder = true;
}
}
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
//都是获取便签项的信息的方法
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}
Loading…
Cancel
Save