Android记事本

一、前言

上学期做了个简单的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,这样从其他界面回到主界面的时候就可以保证数据更新。

扫描二维码关注公众号,回复: 12088617 查看本文章

笔记显示方式可以选用默认的垂直式(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)会是个不错的选择。

猜你喜欢

转载自blog.csdn.net/m0_46854117/article/details/109197510