上学期做了个简单的Android的记事本APP(功能基本就是书里的杂糅),也是我第一次接触Android,一时兴起,趁还没忘光先记录下来,一直以来都在看别人的自己没有分享过东西,如果能提供到一点点的帮助也是极好的。
教材选的是郭霖老师的《第一行代码》,买了第二版回来以后,没想到没过几周,在逛csdn的时候就看到第三版也出了,果断也买了。第三版里根据Android版本的更新更新了很多控件的使用,但是全书使用的是Kotlin语言,我的做法是将第二、三版结合起来一起看,仅供参考。
Android Studio3.4 JDK13.0.2
版本: Android Q(10.0) API 29 Gradle 6.2.2
在项目中的一些控件的引用与方法的使用都是基于上述版本的,可能会出现和其他版本中不一致的情况。
功能包括笔记的增删查改和定时提醒之类,总体上将最主要的功能放在最显眼的地方展示出来,而次要的功能放在菜单里。应用总共有四个界面,每个界面都是一个Activity。
进入应用看到的界面,应用的所有界面都隐藏了原先自带的标题栏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>功能简单,就是创建新的笔记,整个界面只有一个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>风格和新增界面保持一致,除了保存功能,其他的小功能都放入了左上角的滑动菜单中(NavigationView)。小功能包括快速新建笔记、获取灵感、定时提醒和删除笔记。
快速新建笔记: 免除回到主界面再新建的繁琐,直接在编辑界面进入新增界面。
获取灵感: 凑数的功能,主要是菜单里只有三个功能不好看加上去的。实际是弹出一个dialog,里面随机显示一条金句,金句是事先放在一个数组里的。
定时提醒: 系统在设定的时间发送一条通知。具体实现是在弹出的TimerPickerDialog里用AlarmManager设置提醒时间,将设好的时间传递给Service,到了设定的时间以后Service会启动Notification进行通知。
删除笔记: 将当前的笔记从数据库删除。
NavigationView 实现滑动菜单的控件,分为上半部分的headerLayout和下半部分的menu。
TimePickerDialog 可以选择时间的对话框。
AlarmManager Android中用来实现定时任务的其中一种方式,另一种是使用Timer类。
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>点击搜索按钮以后进入的界面,根据关键字搜索已创建的笔记,点击搜索显示符合条件的笔记,点击取消回到主界面。界面包含一个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>选择了Android自带的Sqlite数据库,使用了LitePal来进行数据库操作。
字段类型含义idint自带的textString笔记内容timeString笔记创建时间tagint笔记标识说明: id字段好像是使用LitePal以后会自动生成的,我不会使用 tag字段用来代替id来唯一标识每一篇笔记,具体是在新建笔记的时候生成一个随机数给它。使用一个准确的时间(比如精确到秒)来作标识可能会更好,可以防止翻车。
打开在右下角边栏的Device File Explorer,在data - data - 包名下可以找到生成的数据库文件(.db)。
LitePal 开源的Android数据库框架,将一些数据库常用的功能进行了封装。
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
在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); }在build.gradle中添加依赖 implementation ‘com.google.android.material:material:1.1.0’
在布局文件中引用 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); } });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; } });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();这个通知是写在服务里的。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); }这个通知最终实现的效果是,到了设定的时间后,会在系统的顶部弹出一个横幅通知,进行点击可以消除通知并进入记事本应用的主界面。
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(){// 见上文 ... } }在build.gradle中添加依赖 implementation ‘org.litepal.android:core:1.3.2’
在AndroidManifest.xml的application标签中添加 android:name=“org.litepal.LitePalApplication”
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)会是个不错的选择。
