目录
- 一、前言
- 二、开发环境
- 三、效果预览
- 四、应用介绍
-
- 4.1 主界面
- 4.2 新增界面
- 4.3 编辑界面
- 4.4 搜索界面
- 4.5 数据库
- 4.6 黑夜模式
- 五、文件列表
- 六、可能出现的问题
- 七、关键功能
-
- 7.1 RecyclerView的使用
- 7.2 Material库控件的使用
- 7.3 FloatingActionButton的使用
- 7.4 滑动菜单
- 7.5 TimePickerDialog的使用
- 7.6 通知的使用
- 7.7 服务的使用
- 7.8 数据库的使用
- 八、后记
一、前言
上学期做了个简单的Android的记事本APP(功能基本就是书里的杂糅),也是我第一次接触Android,一时兴起,趁还没忘光先记录下来,一直以来都在看别人的自己没有分享过东西,如果能提供到一点点的帮助也是极好的。
教材选的是郭霖老师的《第一行代码》,买了第二版回来以后,没想到没过几周,在逛csdn的时候就看到第三版也出了,果断也买了。第三版里根据Android版本的更新更新了很多控件的使用,但是全书使用的是Kotlin语言,我的做法是将第二、三版结合起来一起看,仅供参考。
二、开发环境
Android Studio3.4
JDK13.0.2
版本:
Android Q(10.0)
API 29
Gradle 6.2.2
在项目中的一些控件的引用与方法的使用都是基于上述版本的,可能会出现和其他版本中不一致的情况。
三、效果预览
四、应用介绍
功能包括笔记的增删查改和定时提醒之类,总体上将最主要的功能放在最显眼的地方展示出来,而次要的功能放在菜单里。应用总共有四个界面,每个界面都是一个Activity。
4.1 主界面
进入应用看到的界面,应用的所有界面都隐藏了原先自带的标题栏ActionBar,改为使用自定义的Toolbar。向下滑动以后顶部的图片会折叠起来,向上滑动会重新显示。具体是使用CoordinatorLayout + AppBarLayout + CollapsingToolbarLayout + ImageView
用RecyclerView显示笔记,新建功能单独放在下方的FloatingActionButton里,其他的功能放在标题栏的菜单中。
活动模式在AndroidManifest里选用的是singleTop,这样从其他界面回到主界面的时候就可以保证数据更新。
笔记显示方式可以选用默认的垂直式(LinearLayoutManager)或者网格式(StaggeredGridLayoutManager)。虽然上面写着瀑布布局但不是真的瀑布式
CoordinatorLayout、AppBarLayout、CollapsingToolbarLayout
1、CoordinatorLayout可以监听所有子控件的所有事件,可以响应滚动事件,加强版的FrameLayout;
2、AppBarLayout解决RecyclerView和Toolbar之间互相覆盖的问题,垂直方向的LinearLayout;
3、CollapsingToolbarLayout作用于Toolbar之上,可以实现折叠图片的效果;
4、CollapsingToolbarLayout只能作为AppBarLayout的直接子布局,AppBarLayout只能作为CoordinatorLayout的子布局。
我对三者的嵌套关系认识是 :
< CoordinatorLayout>
< AppbarLayout>
< CollapsingToolbarLayout>
< /CollapsingToolbarLayout>
< /AppbarLayout>
< /CoordinatorLayout>
RecyclerView
滚动显示内容的控件,可以自定义子项的布局。使用的时候需要为RecyclerView准备一个适配器以将数据传递给控件,通过泛型来指定要适配的数据类型,然后数据传入。
FloatingActionButton
悬浮按钮,可以自行调整位置和悬浮的高度,需要Material库
其他:
1.折叠图片
在res - values - styles.xml中,将AppTheme改成
name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge"
在布局文件中嵌套使用CoordinatorLayout、AppBarLayout、CollapsingLayout、ImageView
在build.gradle中添加Glide库的依赖,Glide用来加载图片
implementation 'com.github.bumptech.glide:glide:3.7.0
加载图片
Glide.with(this).load(R.mipmap.图片名称).into(ImageView的id);
2.实现标题栏和状态栏融为一体的效果
在CoordinatorLayout、AppBarLayout、CollapsingLayout都加上属性
android:fitsSystemWindow="true"
styles.xml(res目录New - Directory新建values-v21目录,在values-v21中New - Values resource file)
<resources>
<style name="MainActivityTheme" parent="AppTheme"> <item name="android:statusBarColor">@android:color/transparent</item> </style>
</resources>
在res - values - styles.xml中加上
<style name="MainActivityTheme" parent="AppTheme">
在AndroidManifest.xml中的activity标签里加上
android:theme="@style/MainActivityTheme"
activity_main.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:background="#FFFBF0">
<com.google.android.material.appbar.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="250dp" android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsingToolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar" android:fitsSystemWindows="true" app:contentScrim="@color/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView android:id="@+id/toolbarImageView" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:fitsSystemWindows="true" app:layout_collapseMode="parallax" android:adjustViewBounds="true" android:contentDescription="This is picture of title" tools:ignore="HardcodedText"/>
<androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:layout_gravity="bottom|end" android:src="@drawable/pencil_48px" app:elevation="8dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
4.2 新增界面
功能简单,就是创建新的笔记,整个界面只有一个Button和一个EditText。
圆角的卡片效果是使用CardView实现的。点击保存按钮以后,会生成一个随机数,并将笔记的内容、当前时间和这个随机数一起保存进数据库中。
(为什么要生成随机数:详见4.5)
NestedScrollView
可以滚动查看屏幕以外的数据,可以嵌套响应滚动事件,用在CoordinatorLayout内部,因为后者本身可以响应滚动事件。
CardView
实现卡片式布局效果的控件。
其他:
1.使用toolbar导入包的时候选
import androidx.appcompat.widget.Toolbar;
2.加上这段代码可以防止NestedScrollView和EditText嵌套使用时出现滑动冲突:
editText.setOnTouchListener(new View.OnTouchListener(){
@Override
public boolean onTouch(View view, MotionEvent motionEvent){
if(motionEvent.getAction()==MotionEvent.ACTION_DOWN){
view.getParent().requestDisallowInterceptTouchEvent(true);
}
if(motionEvent.getAction()==MotionEvent.ACTION_MOVE){
view.getParent().requestDisallowInterceptTouchEvent(true);
}
if(motionEvent.getAction()==MotionEvent.ACTION_UP){
view.getParent().requestDisallowInterceptTouchEvent(false);
}
return false;
}
});
3.获取当前时间:
private String getTime(){
@SuppressLint("SimpleDateFormat") SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");
Date curDate = new Date(System.currentTimeMillis());
return date.format(curDate);// 不需要赋值给中间变量再返回
}
4.生成随机数:
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static int getRandom(){ // 获取随机数
return ThreadLocalRandom.current().nextInt();
}
activity_add.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize">
<androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="#FFDAB9" android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar" tools:ignore="MissingConstraints"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="15dp" android:layout_marginBottom="20dp" android:layout_marginLeft="15dp" android:layout_marginStart="15dp" android:layout_marginRight="15dp" android:layout_marginEnd="15dp" app:cardBackgroundColor="#FFFBF0" app:cardCornerRadius="4dp" app:layout_constraintTop_toBottomOf="@+id/toolbar">
<EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="top" android:layout_margin="10dp" android:inputType="textMultiLine" android:lines="27" android:importantForAutofill="no" android:background="@null" android:scrollbars="vertical" android:hint="@string/add_hint"/>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
4.3 编辑界面
风格和新增界面保持一致,除了保存功能,其他的小功能都放入了左上角的滑动菜单中(NavigationView)。小功能包括快速新建笔记、获取灵感、定时提醒和删除笔记。
-
快速新建笔记:
免除回到主界面再新建的繁琐,直接在编辑界面进入新增界面。 -
获取灵感:
凑数的功能,主要是菜单里只有三个功能不好看加上去的。实际是弹出一个dialog,里面随机显示一条金句,金句是事先放在一个数组里的。 -
定时提醒:
系统在设定的时间发送一条通知。具体实现是在弹出的TimerPickerDialog里用AlarmManager设置提醒时间,将设好的时间传递给Service,到了设定的时间以后Service会启动Notification进行通知。 -
删除笔记:
将当前的笔记从数据库删除。
NavigationView
实现滑动菜单的控件,分为上半部分的headerLayout和下半部分menu。
TimePickerDialog
时间对话框。
AlarmManager
Android中用来实现定时任务的其中一种方式,另一种是使用Timer类。
Service
服务,四大组件之一,一般负责一些后台功能,不依赖任何用户界面,适合一些不需要和用户交互的任务。
Notification
通知,应用会在上方的状态栏显示一个通知的图标,可以设置通知图标、是否有响声。
activity_edit.xml
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".EditActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize">
<androidx.appcompat.widget.Toolbar android:id="@+id/toolBar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="#FFDAB9" style="@style/ThemeOverlay.AppCompat.DayNight.ActionBar" tools:ignore="MissingConstraints"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="15dp" android:layout_marginStart="15dp" android:layout_marginLeft="15dp" android:layout_marginEnd="15dp" android:layout_marginRight="15dp" android:layout_marginBottom="20dp" app:cardBackgroundColor="#FFFBF0" app:cardCornerRadius="4dp" app:layout_constraintTop_toBottomOf="@id/toolBar">
<EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="top" android:layout_margin="10dp" android:inputType="textMultiLine" android:lines="27" android:importantForAutofill="no" android:background="@null" android:scrollbars="vertical" android:hint="@null" android:textColor="#232323"/>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView android:id="@+id/navView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header"/>
</androidx.drawerlayout.widget.DrawerLayout>
4.4 搜索界面
点击搜索按钮以后进入的界面,根据关键字搜索已创建的笔记,点击搜索显示符合条件的笔记(RecyclerView),点击取消回到主界面。界面包含一个EditText、两个Button和一个RecyclerView。文本框的提示信息在布局文件里用hint属性来实现。
activity_search.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:background="#989795">
<EditText android:id="@+id/editText" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:inputType="text" android:importantForAutofill="no" android:hint="@string/add_hint"/>
<Button android:id="@+id/buttonSearch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/search"/>
<Button android:id="@+id/buttonBack" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/cancel"/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent"/>
</LinearLayout>
4.5 数据库
选择了Android自带的Sqlite数据库,使用了LitePal来进行数据库操作。
字段 | 类型 | 含义 |
---|---|---|
id | int | 自带的 |
text | String | 笔记内容 |
time | String | 笔记创建时间 |
tag | int | 笔记标识 |
说明:
id字段好像是使用LitePal以后会自动生成的,我不会用
tag字段用来代替id来唯一标识每一篇笔记,具体是在新建笔记的时候生成一个随机数给它。使用一个准确的时间(比如精确到秒)来作标识可能会更好,可以防止翻车。
打开在右下角边栏的Device File Explorer,在data - data - 包名下可以找到生成的数据库文件(.db)。
LitePal
开源的Android数据库框架,将一些数据库常用的功能进行了封装。
4.6 黑夜模式
Android 10以后才能使用的功能。
colors.xml(res目录New - Directory新建values-night,在values-night里New - Values resource file)
<resources>
<color name="colorPrimary">#303030</color>
<color name="colorPrimaryDark">#232323</color>
<color name="colorAccent">#008577</color>
</resources>
剩下的我自己也没整明白,就不提了。
五、文件列表
主界面
MainActivity.java:活动
activity_main.xml:主界面布局
toolbar.xml:菜单
新建界面
AddActivity.java:活动
activity_add.xml:新建界面布局
toolbar_add.xml:标题栏菜单
编辑界面
EditActivity.java:活动
NoticeService.java:服务
activity_edit.xml:编辑界面布局
toolbar_add.xml:标题栏菜单
nav_header.xml:滑动菜单布局
nav_menu.xml:滑动菜单
搜索界面
SearchActivity.java:活动
activity_search.xml:搜索界面布局
其他
(RecyclerView用的)
Note.java:自定义的泛型
NoteAdapter.java:自定义的适配器
note_item.xml:子项的布局
(数据库用的)
NoteBook.java:数据库表
litepal.xml:Litepal配置
六、可能出现的问题
报错:
Casued by:java.lang.reflect.InvocationTargetException
Casued by:java.lang.IllegalArgumentException:The style on this compoent requires your app theme to Theme
说明:
需要更新控件的主题到Theme.MaterialComponent
解决:
在styles.xml中,将
<style name="AppTheme"parent="Theme.AppCompat.Light.NoActionBar">
改成
<style name="AppTheme"parent="Theme.MaterialComponents.Light.NoActionBar.Bridge">
报错:
java.lang.IllegalArgumentException:Unsupported class file major version 57
说明:
version 57对应的JDK版本是13,如果是version 56对应的是12
解决:
安装更低版本的JDK
(这个问题我忘了是什么时候遇到了,最后我没有降低版本,可能是改用了别的控件,或者把其他什么地方的版本也升级了)
报错:
Casued by:org.codehaus.groovy.control.MultipleCompilationErrorsException:startup failed:
说明:
原因不清楚
解决:
升级Gradle到6.2.2版本可以解决,存在的缺陷是每次打开项目都要手动升级一次
报错:
You need to use a Theme.Appcompat.theme:
说明:
无
解决:
让活动从继承ActionBarActivity改成继承Activity
七、关键功能
7.1 RecyclerView的使用
在build.gradle中添加依赖(根据版本不同选择其中一个,上面是旧的,下面是新的)
implementation 'com.android.support:recyclerview-v7:29.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
(忘了用不用这步了)
在File - project structure - dependencies - app - library dependency中搜索recyclerview,选择“androidx.”的最新版本
在布局文件中引用(根据版本不同选择,上面是旧的,下面是新的)
<android.support.v7.widget.RecyclerView android:id="" android:layout_width="" android:layout_height=""/>
<androidx.recyclerview.widget.RecyclerView android:id="" android:layout_width="" android:layout_height=""/>
Note.java(泛型)
public class Note(){
private String noteContent;// 笔记内容
private String noteTime;// 笔记创建时间
private int noteTag;// 笔记标识
public Note(String noteContent, String noteTime, int noteTag){
this.noteContent = noteContent;
this.noteTime = noteTime;
this.noteTag = noteTag;
}
public String getNoteContent(){ return noteContent; }
public String getNoteTime(){ return noteTime; }
public int getNoteTag(){ return noteTag; }
}
note_item.xml(子项的布局)
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" app:cardCornerRadius="4dp">
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:id="@+id/noteContent" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginLeft="10dp" android:lines="2" android:background="#FFFEF0" android:textSize="24sp" android:textColor="#232323"/>
<TextView android:id="@+id/noteTime" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginLeft="10dp" android:background="#FFDAB9" android:textColor="#008577"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
NoteAdapter.java(适配器)
让适配器继承自RecyclerView.Adapter,将泛型指定为NoteAdapter.ViewHolder,内部类ViewHolder用于对控件的实例进行缓存。
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.ViewHolder>{
private List<Note> mNoteList;
static class ViewHolder extends RecyclerView.ViewHolder{ // 内部类ViewHolder
View noteView;
TextView noteContent;
TextView noteTime;
public ViewHolder(View view){ // 传入子项的最外层布局
super(view);
noteView = view;
noteContent = (TextView) view.findViewById(R.id.noteContent);// 获取实例
noteTime = (TextView) view.findViewById(R.id.noteTime);// 获取实例
}
}
public NoteAdapter(List<Note> noteList){ // 传入需要展示的数据
mNoteList = noteList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){ // 创建ViewHolder实例
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.note_item, parent, false);// 加载布局
final ViewHolder holder = new ViewHolder(view);
holder.noteView.setOnClickListener(new View.OnClickListener()){ // 子项点击事件
@Override
public void onClick(View v){
int position = holder.getAdapterPosition();
Note note = mNoteList.get(position);
Intent intent = new Intent(v.getContext(), EditActivity.class);
String contentData = note.getNoteContent();
int tagData = note.getNoteTag();
// intent.putExtra("键",数据)
intent.putExtra("content_data", contentData);
intent.putExtra("tag_data", String.valueOf(tagData));
v.getContext.startActivity(intent);
}
});
holder.noteTime.setOnClickListener(new View.OnClickListener(){ // 子项点击事件
@Override
public void onClick(View v){
int position = holder.getAdapterPosition();
Note note = mNoteList.get(position);
String time = note.getNoteTime();
Toast.makeText(v.getContext(), "笔记创建于" + time, Toast.LENGTH_LONG).show();
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position){ // 为子项赋值,子项被滚动到屏幕就执行
Note note = mNoteList.get(position);
holder.noteContent.setText(note.getNoteContent());
holder.noteTime.setText(note.getNoteTime());
}
@Override
public int getItemCount(){ // 告诉RecyclerView一共有多少子项
return mNoteList.size();
}
}
更新:
如果获取点击位置的接口被划线不推荐使用了,可能是如下原因(翻译摘自郭霖老师的文章)
这个方法当多个adapter嵌套时会存在歧义。如果你是在一个adapter的上下文中调用这个方法,你可能想要调用的是getBindingAdapterPosition()方法。如果你想获得的position是如同在RecyclerView中看到的那样,你应该调用getAbsoluteAdapterPosition()方法。
这是我这几天写这篇文章的时候看到的,具体文章→ 什么?RecyclerView中获取点击位置的接口被废弃了?
实例化
public class MainActivity extends AppCompatActivity{
private List<Note> noteList = new ArrayList<>();//
private RecyclerView recyclerView;
@Override
protected void onCreate(bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);// 获取RecyclerView实例
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);// 设置垂直式排列
NoteAdapter adapter = new NoteAdapter(noteList);// 创建NoteAdapter实例
recyclerView.setAdapter(adapter);// 完成适配器设置
}
}
从数据库获取数据
List<NoteBook> notes = DataSupport.findAll(NoteBook.class);
for(NoteBook note : notes){
Note temp = new Note(note.getContent(), note.getTime(), note.getTag());
noteList.add(temp);
}
7.2 Material库控件的使用
build.gradle中添加依赖
implementation 'com.google.android.material:material:1.1.0'
7.3 FloatingActionButton的使用
布局文件中引用
app:elevation属性指定的是高度值
<com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:layout_gravity="bottom|end" android:src="@drawable/图片名称" app:elevation="8dp"/>
设置点击事件
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener()){
@Override
public void onClick(View v){
Intent intent = new Intent(MainActivity.this, AddActivity.class);
startActivity(intent);
}
});
7.4 滑动菜单
android.support:desgin库已弃用,如需使用在build.gradle中添加
compile 'com.android.support:design:24.2.1'
nav_header.xml(Layout文件夹New - Layout resource file)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="180dp">
<ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:contentDescription="@string/headerText" android:scaleType="centerCrop" android:src="@mipmap/图片名称"/>
</RelativeLayout>
nav_menu.xml(menu文件夹New - Menu resource file)
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item android:id="@+id/nav_add" android:icon="@drawable/图片名称" android:title="@string/add"/>
<item android:id="@+id/nav_notice" android:icon="@drawable/图片名称" android:title="@string/notice"/>
<item android:id="@+id/nav_refresh" android:icon="@drawable/图片名称" android:title="@string/refresh"/>
<item android:id="@+id/nav_delete" android:icon="@drawable/图片名称" android:title="@string/delete"/>
</group>
</menu>
布局文件中引用
<com.google.android.material.navigation.NavigationView
android:id="@id/navView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/nav_menu"
app:headerLayout+"@layout/nav_header"/>
设置点击事件
NavigationView navView = (NavigationView) findViewById(R.id.navView);
navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item){
switch(item.getItemId()){
case R.id.nav_add:
...
case R.id.nav_notice:
...
case R.id.nav_refresh:
...
case R.id.nav_delete:
...
default:
}
return true;
}
});
7.5 TimePickerDialog的使用
RTC_WAKEUP表示让定时任务的触发时间从1970.1.1的0点开始算起,会唤醒CPU。AlertDialog.THEME_HOLO_LIGHT让TimePickerDialog显示滚动的效果
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Calendar calendar1 = Calendar.getInstance();// 获取实例
int hour = calendar1.get(Calendar.HOUR_OF_DAY);// 获取当前小时
int minute = calendar1.get(Calendar.MINUTE);// 获取当前分钟
// 设置时间对话框
TimePickerDialog timePickerDialog = new TimePickerDialog(EditActivity.this, android.app.AlertDialog.THEME_HOLO_LIGHT, new TimePickerDialog.OnTimeSetListener() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
Calendar calendar2 = Calendar.getInstance();
calendar2.set(Calendar.HOUR_OF_DAY, hourOfDay);// 获取设定小时
calendar2.set(Calendar.MINUTE, minute);// 获取设定分钟
Intent intent2 = new Intent(EditActivity.this, NoticeService.class);
PendingIntent pi = PendingIntent.getService(EditActivity.this, 0, intent2, 0);
// (工作类型, 触发时间, 意图)
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar2.getTimeInMillis(), pi);
}
}, hour, minute, true);// 初始显示时间
timePickerDialog.show();
7.6 通知的使用
这个通知是写在服务里的。PendingIntent可以理解为延迟执行的Intent。
@RequiresApi(api = Build.VERSION_CODES.O)// NotificationChannel&IMPORTANCE_HIGH的版本要求
private void initNotice(){
// getSystemService:确定获取系统的哪个服务
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// API 26(Android8.0)以后需要自己设置NotificationChannel
String channelId = "channel_01";
String channelName = "channelName";
String channelDescription = "this is default channel";
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(channelDescription);
assert notificationManager != null;
notificationManager.createNotificationChannel(channel);
Intent intentOut = new Intent(NoticeService.this, MainActivity.class);
// (Context, 0, Intent对象, PendingIntent的行为)
PendingIntent pi = PendingIntent.getActivity(NoticeService.this, 0, intentOut, 0);
// API 26(Android8.0)以后需要在Builder中添加id
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("记事本")
.setContentText("定时提醒")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.图片名称)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.图片名称))
.setContentIntent(pi)
.setAutoCancel(true)// 让通知可以点击以后自动取消
.build();
notificationManager.notify(1, notification);
}
这个通知最终实现的效果是,到了设定的时间后,会在系统的顶部弹出一个横幅通知,进行点击可以消除通知并进入记事本应用的主界面。
7.7 服务的使用
NoticeService.java(New - Service - Service)
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
public class NoticeService extends Service {
public NoticeService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
new Thread(new Runnable() { // 创建和启动子线程
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void run() { // 处理具体逻辑
initNotice();// 实现通知的函数
stopSelf();// 执行完毕后自动停止
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
private void initNotice(){ // 见上文
...
}
}
7.8 数据库的使用
build.gradle中添加依赖(我写文章的时候看到的最新版本好像是3.2.0)
implementation 'org.litepal.android:core:1.3.2'
在AndroidManifest.xml中
<application android:name="org.litepal.LitePalApplication" ...>
</application>
NoteBook.java(数据库表)
import org.litepal.crud.DataSupport;
public class NoteBook extends DataSupport(){
private int id;
private String content;
private String time;
private int tag;
public int getId(){ return id; }
public void setId(int id){ this.id = id; }
public String getContent(){ return content; }
public void setContent(String content){ this.content = content; }
public String getTime(){ return time; }
public void setTime(String time){ this.time = time; }
public int getTag(){ return tag; }
public void setTag(int tag){ this.tag = tag; }
}
litepal.xml(在包名 - app - src下创建assets文件夹,在assets文件夹下创建litepal.xml)
dbname是数据库的名字。version的值一开始是1,每次更新了数据库表(改了字段、新增了表等等)这个值就加1。list标签里是数据库的表,有几个表就写几个< mapping >< /mapping >。
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="NoteBook"></dbname>
<version value="1"></version>
<list>
<mapping class="com.example.notebook.NoteBook"></mapping>
</list>
</litepal>
连接数据库
SQLiteDataBase db = Connector.getDataBase();
增
NoteBook note = new NoteBook();
String inputContent = editText.getText().toString();// 笔记的内容
String inputTime = getTime();// 笔记的创建时间
int inputTag = getRandom();// 笔记的标识
note.setContent(inputContent);
note.setTime(inputTime);
note.setTag(inputTag);
删
DataSupport.deleteAll(NoteBook.class, "tag = ?", tag);// 根据标识删除对应的笔记
查
String orderContent = editText.getText().toString();// 从文本框获取想要查找的关键字
List<NoteBook> notes = DataSupport.where("content like ?", "%"+orderContent+"%").find(NoteBook.class);// 根据关键字在“内容”里查找
改
NoteBook note = new NoteBook();
String inputContent = editText.getText().toString();// 从文本框获取更改后的笔记内容
note.setContent(inputContent);
note.updateAll("tag = ?", tag);
八、后记
文章写得比较匆忙,可能会出现很多纰漏和错误,欢迎交流与学习,可能答不上来问题。后续如果想起了遗忘的东西可能会编辑。
程序是一边学一边写的,想到什么就加了什么进去,应该还有很多不合理的地方或者可以优化的地方。
Android的东西更新得比较快,经常过一两年又不一样了,而这个变化的东西恰好没有人发文章提到的时候,个人经验,这个时候去Android Developers查看一下参考文档(REFERENCE)会是个不错的选择。