绿色健康小清新

耐得住寂寞,守得住繁华

Android

准备工作和HelloWorld

下载Android Studio

https://developer.android.google.cn/studio/

创建项目


查看布局


运行


设置镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.0"

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
google()
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

创建APK

APK文件是Android系统的安装包文件,我们一般可以直接将其拷贝到手机储存中进行安装(打开)

第一次使用创建一个新的

在app下有一个release文件夹,有一个生成的apk文件


简单登录界面

效果


Activity生命周期

Activity作为Android的四大组件之一,非常的重要,也是最常用的四大组件,使用Activity必须要在AndroidManifest中进行注册,那么作为Android的基础,Activity的生命周期你是否完全掌握了呢?下面就让我们来一起回顾一下Activity的生命周期吧!

首先,你需要知道 Activity的生命周期分为两种。

  1. 典型情况下的生命周期(就是我们开发中经常用到的)
  2. 异常情况下的生命周期(虽说开发中也会用到,但是并不是所有的项目都会去(需要)回调异常生命周期的方法)

所以我们先来介绍前者,因为后者出现的情况比较特殊。

典型情况下的生命周期

说明

  1. onCreate:在首次创建 Activity 时调用。系统向此方法传递一个 Bundle 对象,其中包含 Activity 的上一状态,不过前提是捕获了该状态,而后会调用onStart方法。(可以在此方法中执行所有正常的静态设置 ,比如:创建视图、将数据绑定到列表等等。)
  2. onStart:在 Activity 即将对用户可见之前调用。而后如果Activity转入了前台就会调用onResume方法。 如果此时直接屏幕熄灭或者用户按下home键则会直接调用onStop方法,当然这种情况比较极端。
  3. onResume:在 Activity 即将开始与用户进行交互之前调用。 此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。当跳转另一个Activity,或者退出当前Activity后会调用onPause方法。
  4. onPause:在系统即将开始继续另一个 Activity 时调用。 此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行,所以不能执行耗时操作。而后正常情况下会调用onStop方法。但是有一种极端情况,就是如果这个时候快速让 当前Activity 返回前台,则会调用onResume方法。
  5. onStop:在 Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就会调用此方法。而后如果 Activity 恢复与用户的交互,则会调用 onRestart 方法,如果 Activity 被销毁,则会调用onDestroy方法。
  6. onRestart:在Activity被停止后再次启动时调用(即屏幕熄灭后再次回到app,按下home键后再次回到app),而后会调用onStart方法。
  7. onDestroy:在 Activity 被销毁前调用,这是 Activity 收到的最后调用。 当 Activity 结束(对 Activity 调用了 finish 方法),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 你可以通过 isFinishing 方法区分这两种情形。

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
String TAG = "myTag";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_main);
Log.d(TAG,"onCreate:");
}

@Override
protected void onStart() {
super.onStart();
Log.d(TAG,"onStart:");
}


@Override
protected void onResume() {
super.onResume();
Log.d(TAG,"onResume:");
}

@Override
protected void onPause() {
super.onPause();
Log.d(TAG,"onPause;");
}

@Override
protected void onStop() {
super.onStop();
Log.d(TAG,"onStop:");
}

@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG,"onRestart");
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy:");
}

启动后显示:

回到手机主界面:

再次回到应用:

退出应用/翻转虚拟机:


LifeCycles(让组件感知生命周期,Activity进一步解耦)

计时小案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MainActivity extends AppCompatActivity {

private Chronometer meter = null;
private long elapsedTime;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

meter = findViewById(R.id.meter);
//SystemClock.elapsedRealtime()就是从软件启动开始计时,退出结束,后台依然计时
meter.setBase(SystemClock.elapsedRealtime());
}

@Override
protected void onResume() {
super.onResume();
//将 当前运行时间-此前计时时间 设置为base时间;
//也就是说base时间与此时的已运行时间相差elapsedTime,也就是此前的已运行时间,然后接着计时
meter.setBase(SystemClock.elapsedRealtime() - elapsedTime);
meter.start();
}



@Override
protected void onPause() {
super.onPause();
//获取此时已经计时的时间
elapsedTime = SystemClock.elapsedRealtime() - meter.getBase();
//并不会真的停止计时,后台其实还在计时
meter.stop();
}
}

调用start(); 函数定时器开始计时,调用stop(); 函数当然就应该是停止(字面理解),但是stop() 的停止是停止视图计时(变动),但是后台其实还是在计时,毕竟依赖的是系统开机时间来计时的,stop() 方法是不可能把开机时间停止下来的

LifeCycles

在上面的例子中,有一个问题:如果我们相似的组件太多,就会使activity中过于耦合。这时使用LifeCycles,让组件自身去感知生命周期的变化,可以使组件的独立性更高。

创建一个我们自定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyChronometer extends Chronometer implements LifecycleObserver {

private Long elapsedTime;

public MyChronometer(Context context, AttributeSet attrs) {
super(context, attrs);
}

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private void pauseMeter(){
elapsedTime = SystemClock.elapsedRealtime() - getBase();
stop();
}

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private void resumeMeter(){
setBase(SystemClock.elapsedRealtime() - elapsedTime);
start();
}
}

添加观察者

1
2
3
4
5
6
7
8
9
10
private Chronometer meter = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

meter = findViewById(R.id.meter);
getLifecycle().addObserver((LifecycleObserver) meter);
}

使用该组件

效果与上面的效果一样,但是如果我们的屏幕翻转,就会重新计时。一个简单的方法,elapsedTime设置为静态的就可以了

1
private static Long elapsedTime = 0L;

控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_main);

//根据id获取当前的textView
this.textView = findViewById(R.id.textView);
}


@Override
protected void onPause() {
super.onPause();
//当触发pause时,将textView的文字改为pause
this.textView.setText("pause");
}


更多的查文档就行了,没什么难的


本地化及多语言设置

在手机的设置中设置语言就可以看到效果了


固定屏幕与旋转屏幕

固定屏幕

加了此条配置,屏幕就不会旋转了

  • 总以竖屏显示:android:screenOrientation=“portrait”
  • 总以横屏显示:android:screenOrientation=“landscape”

旋转屏幕

正如前面在声明周期里说到的,当屏幕旋转的时候,会destory一次,重新开始构建,也就是说原本我们的屏幕显示的一些非默认数值在旋转后就会恢复默认值,这显然是不行的,此时就要借助onSaveInstanceState函数(下面还会使用ViewModel来进行管理数据);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Button button2;
TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_main);

this.button2 = findViewById(R.id.button2);
this.textView = findViewById(R.id.textView3);


if(savedInstanceState!=null){
//获取旋转前的数据进行显示
String msg = savedInstanceState.getString("KEY");
textView.setText(msg);
}

this.button2.setOnClickListener(v -> {
this.textView.setText(R.string.button2);
});

}

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
//保存旋转前的数据
outState.putString("KEY",textView.getText().toString());
}

不管如何旋转,TextView中的字符串不会改变。


组件(MVVM)

当我们不适用ViewModel时,是直接对view进行操作,并且数据是直接存储在controller中

ViewModel

ViewModel 将视图的数据和逻辑从具有生命周期特性的实体(如 Activity 和 Fragment)中剥离开来。直到关联的 Activity 或 Fragment 完全销毁时,ViewModel 才会随之消失,也就是说,即使在旋转屏幕导致 Fragment 被重新创建等事件中,视图数据依旧会被保留。ViewModels 不仅消除了常见的生命周期问题,而且可以帮助构建更为模块化、更方便测试的用户界面。

ViewModel的优点也很明显,为Activity 、Fragment存储数据,直到完全销毁。尤其是屏幕旋转的场景,常用的方法都是通过onSaveInstanceState()保存数据,再在onCreate()中恢复,真的是很麻烦。

其次因为ViewModel存储了数据,所以ViewModel可以在当前ActivityFragment中实现数据共享。

  1. ViewModel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MyViewModel extends ViewModel {

    private int number;

    public int getNumber() {
    return number;
    }

    public void setNumber(int number) {
    this.number = number;
    }
    }
  2. MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class MainActivity extends AppCompatActivity {

    private MyViewModel myViewModel;
    private TextView textView;
    private Button button1,button2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.myViewModel = new ViewModelProvider(this).get(MyViewModel.class);

    this.textView = findViewById(R.id.textView);
    this.button1 = findViewById(R.id.button1);
    this.button2 = findViewById(R.id.button2);

    this.textView.setText(String.valueOf(this.myViewModel.getNumber()));

    this.button1.setOnClickListener(v -> {
    this.myViewModel.setNumber(this.myViewModel.getNumber()+1);
    this.textView.setText(String.valueOf(this.myViewModel.getNumber()));
    });

    this.button2.setOnClickListener(v -> {
    this.myViewModel.setNumber(this.myViewModel.getNumber()+2);
    this.textView.setText(String.valueOf(this.myViewModel.getNumber()));
    });
    }
    }

    注意:这里 **this.myViewModel = new ViewModelProvider(this).get(MyViewModel.class);**可能会报错,这是因为依赖版本不对的原因

    解决方法:在build.grade中添加一条依赖

    1
    2
    3
    dependencies {
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    }
  3. 效果

    此时就算旋转ViewText中的数值也不会改变了。


LiveData

LiveData 是一个可以感知 Activity 、Fragment生命周期的数据容器。当 LiveData 所持有的数据改变时,它会通知相应的界面代码进行更新。同时,LiveData 持有界面代码 Lifecycle 的引用,这意味着它会在界面代码(LifecycleOwner)的生命周期处于 started 或 resumed 时作出相应更新,而在 LifecycleOwner 被销毁时停止更新。

主要作用在两点:

  • 数据存储器类。也就是一个用来存储数据的类。
  • 可观察。这个数据存储类是可以观察的,也就是比一般的数据存储类多了这么一个功能,对于数据的变动能进行响应。

主要思想就是用到了观察者模式思想,让观察者和被观察者解耦,同时还能感知到数据的变化(其实就是通过setValue()进行感知),所以一般被用到ViewModel中,ViewModel负责触发数据的更新,更新会通知到LiveData,然后LiveData再通知活跃状态的观察者

  1. ViewModelLiveData

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class ViewModelLiveData extends ViewModel {

    private MutableLiveData<Integer> likeNumber;

    public MutableLiveData<Integer> getLikeNumber() {
    if(likeNumber == null) {
    likeNumber = new MutableLiveData<>();
    likeNumber.setValue(0);
    }
    return likeNumber;
    }

    public void addLikeNumber(Integer number){
    likeNumber.setValue(likeNumber.getValue() + number);
    }
    }
  2. MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class MainActivity extends AppCompatActivity {

    TextView textView;
    ImageButton imageButton1;
    ImageButton imageButton2;
    ViewModelLiveData viewModelLiveData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    textView = findViewById(R.id.textView);
    imageButton1 = findViewById(R.id.imageButton1);
    imageButton2 = findViewById(R.id.imageButton2);

    viewModelLiveData = new ViewModelProvider(this).get(ViewModelLiveData.class);

    //相当于一个观察者,当数据发生改变时,会自动的对textView进行一次刷新
    //其他地方就不会出现对textView的修改了,进一步增加了代码的独立性77
    viewModelLiveData.getLikeNumber().observe(this,integer -> {
    textView.setText(String.valueOf(integer));
    });

    //+1
    imageButton1.setOnClickListener(view->{
    viewModelLiveData.addLikeNumber(1);
    });

    //-1
    imageButton2.setOnClickListener(view->{
    viewModelLiveData.addLikeNumber(-1);
    });
    }
    }
    1. 效果


DataBinding

什么是 DataBinding 呢,简单说来就是帮我们实现 view 和 data 绑定的工具,把数据映射到 view 的 xml中,可以在 xml 布局文件中实现 view 的赋值,方法调用。使用 DataBinding 后,我们不同再写 findViewById,不用再获取控件对象,不用再设置监听,可以节省我们 activity 中的很多获取控件,赋值,添加监听所需要的代码。

DataBinding 是个好东西,15年 google IO 大会就开始推了,最直接的变化就是催生了 android 中 MVVM 的出现,MVVM = MVP + DataBinding 。

DataBinding可以帮我们减少很多没必要的代码,大大提高我们的开发效率。比如大量减少使用findViewById()、setText(),setVisibility(),setEnabled()等代码的几率。

DataBinding主要解决了两个问题:

  • 需要多次使用findViewById等无营养的代码,损害了应用性能且令人厌烦
  • 更新UI数据需切换至UI线程,将数据分解映射到各个view比较麻烦

  1. 修改配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
    dataBinding {
    enabled true
    }
    }
    }

    上面写法已经过时,写下面这种写法

    1
    2
    3
    4
    5
    android {
    buildFeatures {
    dataBinding = true
    }
    }
  2. 转换为databing形式的layout

    然后会自动帮我们创建一个ActivityMainBinding

  3. 创建ViewModel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class MyViewModel extends ViewModel {

    private MutableLiveData<Integer> likeNumber;

    public MutableLiveData<Integer> getLikeNumber() {
    if(likeNumber == null) {
    likeNumber = new MutableLiveData<>();
    likeNumber.setValue(0);
    }
    return likeNumber;
    }

    public void add(){
    likeNumber.setValue(likeNumber.getValue() + 1);
    }
    }
  4. MainActivity(进行一个绑定)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class MainActivity extends AppCompatActivity {

    MyViewModel myViewModel;
    ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //setContentView(R.layout.activity_main);
    binding = DataBindingUtil.setContentView(this,R.layout.activity_main);

    myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
    binding.setData(myViewModel);
    binding.setLifecycleOwner(this);
    }
    }
  5. 在view中设置好变量,并进行属性和函数调用,我们调用属性其实本质上调用的是get()方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <data>
    <variable
    name="data"
    type="com.example.databinding.MyViewModel" />
    </data>

    <TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{String.valueOf(data.likeNumber)}"
    android:textSize="36sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.251" />

    <Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="384dp"
    android:text="@string/button"
    android:onClick="@{()->data.add()}"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.498"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

小案例(篮球积分器)

  1. 创建一个项目,并修改配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    plugins {
    id 'com.android.application'
    }

    android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
    applicationId "com.example.score"
    minSdkVersion 30
    targetSdkVersion 30
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildFeatures {
    dataBinding = true
    }

    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    }
    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
    }
    }

    dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    }
  2. 创建视图

  3. 创建ViewModel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public class MyViewModel extends ViewModel {

    private MutableLiveData<Integer> aTeamScore;
    private MutableLiveData<Integer> bTeamScore;
    /**
    /* 存储回退数值
    */
    private Integer aBack,bBack;

    public MutableLiveData<Integer> getATeamScore() {
    if(aTeamScore == null){
    aTeamScore = new MutableLiveData<>();
    aTeamScore.setValue(0);
    }
    return aTeamScore;
    }

    public MutableLiveData<Integer> getBTeamScore() {
    if(bTeamScore == null){
    bTeamScore = new MutableLiveData<>();
    bTeamScore.setValue(0);
    }
    return bTeamScore;
    }

    public void aTeamAdd(int p){
    aBack = aTeamScore.getValue();
    bBack = bTeamScore.getValue();
    aTeamScore.setValue(aTeamScore.getValue()+p);
    }

    public void bTeamAdd(int p){
    aBack = aTeamScore.getValue();
    bBack = bTeamScore.getValue();
    bTeamScore.setValue(bTeamScore.getValue()+p);
    }

    /**
    * @Description 重置
    * @date 2020/11/12 11:10
    * @return
    */
    public void reset(){
    aTeamScore.setValue(0);
    bTeamScore.setValue(0);
    }

    /**
    * @Description 撤销操作
    * @date 2020/11/12 11:09
    * @return
    */
    public void undo(){
    aTeamScore.setValue(aBack);
    bTeamScore.setValue(bBack);
    }
    }
  4. 使用DataBinding,并调用ViewModel中的属性和方法

  5. MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class MainActivity extends AppCompatActivity {

    MyViewModel myViewModel;
    ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //setContentView(R.layout.activity_main);
    binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
    myViewModel = new ViewModelProvider(this).get(MyViewModel.class);

    binding.setData(myViewModel);
    binding.setLifecycleOwner(this);
    }
    }
  6. 之后创建一个横版的,并修改样式

  7. 演示


ViewModelSavedState

在上面的例子中,我们的数据存在ViewModel中,理论上是不会被清除的,相比于onSaveInstanceState确实方便了不少,但如果我们的进程被后台系统杀死,数据就会丢失

我们这里来检验一下(因为之前的本地化多语言,我将虚拟机切换成立了中文)。

  1. 打开开发者模式:设置->关于模拟设备->版本号(连续点击进入开发者模式)

  2. 设置不保留活动:**设置->系统->开发者选项->下滑到 ‘应用’ 栏->打开 ‘不保留活动’ 选项 或者 选择后台进程限制的不允许后台进程 **

  3. 进行测试

解决办法

  • onSaveInstanceState(太旧了)
  • viewModelSavedState

onSaveInstanceState方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MainActivity extends AppCompatActivity {

MyViewModel myViewModel;
ActivityMainBinding binding;
private static final String KEY_A_NUMBER = "A_number";
private static final String KEY_B_NUMBER = "B_number";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
myViewModel = new ViewModelProvider(this).get(MyViewModel.class);

if(savedInstanceState!=null){
myViewModel.getATeamScore().setValue((Integer) savedInstanceState.get(KEY_A_NUMBER));
myViewModel.getBTeamScore().setValue((Integer) savedInstanceState.get(KEY_B_NUMBER));
}

binding.setData(myViewModel);
binding.setLifecycleOwner(this);

}

@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_A_NUMBER, myViewModel.getATeamScore().getValue());
outState.putInt(KEY_B_NUMBER, myViewModel.getBTeamScore().getValue());
}
}

viewModelSavedState方法

  1. 添加依赖

    1
    implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
  2. 修改ViewModel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    public class MyViewModel extends ViewModel {

    //private MutableLiveData<Integer> aTeamScore;
    //private MutableLiveData<Integer> bTeamScore;
    private Integer aBack,bBack;

    private SavedStateHandle handle;

    private static final String KEY_A_NUMBER = "A_number";
    private static final String KEY_B_NUMBER = "B_number";

    public MyViewModel(SavedStateHandle handle) {
    this.handle = handle;
    }

    public MutableLiveData<Integer> getATeamScore() {
    if(!handle.contains(KEY_A_NUMBER)){
    handle.set(KEY_A_NUMBER,0);
    }
    return handle.getLiveData(KEY_A_NUMBER);
    }

    public MutableLiveData<Integer> getBTeamScore() {
    if(!handle.contains(KEY_B_NUMBER)){
    handle.set(KEY_B_NUMBER,0);
    }
    return handle.getLiveData(KEY_B_NUMBER);
    }

    public void aTeamAdd(int p){
    aBack = (Integer) handle.getLiveData(KEY_A_NUMBER).getValue();
    bBack = (Integer) handle.getLiveData(KEY_B_NUMBER).getValue();
    handle.set(KEY_A_NUMBER,aBack+p);
    }

    public void bTeamAdd(int p){
    aBack = (Integer) handle.getLiveData(KEY_A_NUMBER).getValue();
    bBack = (Integer) handle.getLiveData(KEY_B_NUMBER).getValue();
    handle.set(KEY_B_NUMBER,bBack+p);
    }

    /**
    * @Description 重置
    * @date 2020/11/12 11:10
    * @return
    */
    public void reset(){
    handle.set(KEY_A_NUMBER,0);
    handle.set(KEY_B_NUMBER,0);
    }

    /**
    * @Description 撤销操作
    * @date 2020/11/12 11:09
    * @return
    */
    public void undo(){
    handle.set(KEY_A_NUMBER,aBack);
    handle.set(KEY_B_NUMBER,bBack);
    }
    }
  3. 修改MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class MainActivity extends AppCompatActivity {

    MyViewModel myViewModel;
    ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //setContentView(R.layout.activity_main);
    binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
    myViewModel = new ViewModelProvider(this,new SavedStateViewModelFactory(getApplication(),this)).get(MyViewModel.class);

    binding.setData(myViewModel);
    binding.setLifecycleOwner(this);

    }
    }

SharedPreferences

通过上面的ViewModelSavedState,我们进入后台后数据也不会丢失了,但是当我们点击返回时,数据还会丢失。

解决办法:SharedPreferences,永久保存数据

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

SharedPreferences shp = getPreferences(Context.MODE_PRIVATE);

//存储数据
SharedPreferences.Editor edit = shp.edit();
edit.putInt("NUMBER", 100);
edit.commit();

//读取
int x = shp.getInt("NUMBER", 0);
Log.d("myLog", "onCreate:"+x);
}
}

运行代码,控制台日志打印如下

然后找到右下角的

点击,会看到一个目录,进入到data/data/,你会看到很多的包名,然后找到你当前MainActivity所在的包名,单击右键,进行一次刷新

其中的数据如下

1
2
3
4
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="NUMBER" value="100" />
</map>

自定义文件名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//SharedPreferences shp = getPreferences(Context.MODE_PRIVATE);
SharedPreferences shp = getSharedPreferences("My_DATA", Context.MODE_PRIVATE);

//存储数据
SharedPreferences.Editor edit = shp.edit();
edit.putInt("NUMBER", 600);
edit.commit();

//读取
int x = shp.getInt("NUMBER", 0);
Log.d("myLog", "onCreate:"+x);
}
}

运行


类结构

我们当前的类继承自ContextWrapper类,getSharedPreferences方法是在ContextWrapper类中定义的。


定义Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class MyData {

private int data;

private Context context;
private Resources resources;

private SharedPreferences shp;

/**
* @Description 初始化基本信息,必须传入context对象
* @date 2020/11/14 14:56
* @param context
* @return
*/
public MyData(Context context) {
this.context = context;
this.resources = context.getResources();
shp = context.getSharedPreferences(resources.getString(R.string.MY_DATA),Context.MODE_PRIVATE);
}

/**
* @Description 存储数据
* @date 2020/11/14 14:55
* @return
*/
public void save(){
SharedPreferences.Editor edit = shp.edit();
edit.putInt(resources.getString(R.string.MY_KEY), data);
edit.commit();
}

/**
* @Description 读取数据
* @date 2020/11/14 14:55
* @return
*/
public int load(){
data = shp.getInt(resources.getString(R.string.MY_KEY), resources.getInteger(R.integer.defValue));
return data;
}

public int getData() {
return data;
}

public void setData(int data) {
this.data = data;
}
}

其中使用到的常量放在了资源中,自己创建一个int资源

1
2
3
4
5
6
7
8
9
10
11
-----string.xml-----
<resources>
<string name="app_name">SharedPreferences</string>
<string name="MY_DATA">my_data</string>
<string name="MY_KEY">my_key</string>
</resources>

-----int.xml-----
<resources>
<integer name="defValue">0</integer>
</resources>

在MainActivity中可以定义一个MyData,并传入context

1
MyData myData = new MyData(this);

这样子传可以,但不好,因为可能会引起内存的泄露。因为我们的Activity在翻转,切换等情况会进行重新创建,而我们的myData中又有一个引用指向Activity,就会导致当前Activity对象并不会被垃圾回收器回收。

应该传递Application,只要应用还存在,就不会被重新创建。

1
MyData myData = new MyData(getApplication());

MainActivity代码

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

MyData myData = new MyData(getApplication());
myData.setData(800);
myData.save();

Log.d("myLog", "onCreate:"+myData.load());
}

之后自己可以查看结果


AndroidViewModel和小案例

首先,修改配置build.gradle,导入依赖

1
2
3
4
5
6
buildFeatures {
dataBinding = true
}

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'

做个界面

编写viewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.example.viewmodelshp;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.SavedStateHandle;

/**
* @author codekiller
* @date 2020/11/14 15:19
* @Description ViewModel
*/
public class MyViewModel extends AndroidViewModel {

private SavedStateHandle handle;

private String key = getApplication().getString(R.string.data_key);
private String shpName = getApplication().getString(R.string.shp_name);

public MyViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {
super(application);
this.handle = savedStateHandle;

if (!this.handle.contains(key)) {
load();
}
}

public LiveData<Integer> getNumber() {
return handle.getLiveData(key);
}

/**
* @return
* @Description 加载数据
* @date 2020/11/14 15:41
*/
public void load() {
SharedPreferences shp = getApplication().getSharedPreferences(shpName, Context.MODE_PRIVATE);
handle.set(key, shp.getInt(key, 0));
}

/**
* @return
* @Description 保存数据
* @date 2020/11/14 15:41
*/
public void save() {
SharedPreferences shp = getApplication().getSharedPreferences(shpName, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = shp.edit();
edit.putInt(key, getNumber().getValue());
edit.commit();
}

/**
* @param x
* @return
* @Description 进行+和-运算
* @date 2020/11/14 15:39
*/
public void add(int x) {
handle.set(key, getNumber().getValue() + x);
//save(); 耗时间
}

}

常量资源

1
2
3
4
5
6
7
8
<resources>
<string name="app_name">ViewModelSHP</string>
<string name="button_plus">+</string>
<string name="button_minus">-</string>
<string name="textview">HelloWorld</string>
<string name="data_key">DATA_KEY</string>
<string name="shp_name">shp_name</string>
</resources>

修改activity_main.xml,先转化为data binding layout,再修改代码(这里给出了修改的部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<data>
<variable
name="data"
type="com.example.viewmodelshp.MyViewModel" />
</data>

<TextView
android:text="@{String.valueOf(data.getNumber())}"/>

<Button
android:onClick="@{()->data.add(1)}"/>

<Button
android:onClick="@{()->data.add(-1)}"/>

修改MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainActivity extends AppCompatActivity {

MyViewModel myViewModel;
ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

myViewModel = new ViewModelProvider(this, new SavedStateViewModelFactory(getApplication(), this)).get(MyViewModel.class);
binding.setData(myViewModel);
binding.setLifecycleOwner(this);
}

@Override
protected void onPause() {
super.onPause();
//保存数据
myViewModel.save();
}
}

之后,不论是进程杀死,还是退出应用,关机,数据都不会丢失。但是如果你手机没电了突然关机,onPause()调用不了,数据依然会丢失。


简单案例

创建

创建一个fragment

创建好之后,会帮我们生成四个文件

然后,创建Navigation资源

结果生成一个目录和文件


编写界面

配置导航

右边可以修改名称

效果

进入到activity_main.xml文件中

选择一个navigation


配置导航逻辑

有两种方式

  1. HomeFragment

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    Button button = getView().findViewById(R.id.button);

    button.setOnClickListener(v -> {
    NavController controller = Navigation.findNavController(v);
    controller.navigate(R.id.action_homeFragment_to_detailFragment);
    });
    }
  2. DetailFragment

    1
    2
    3
    4
    5
    6
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    getView().findViewById(R.id.button2).setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_detailFragment_to_homeFragment));
    }

添加动画


添加一个界面返回图标

这个东西是要定义到我们的main_avtivity中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

NavController controller = Navigation.findNavController(this, R.id.fragment);
NavigationUI.setupActionBarWithNavController(this,controller);

}

@Override
public boolean onSupportNavigateUp() {
NavController controller = Navigation.findNavController(this, R.id.fragment);
return controller.navigateUp();

//return super.onSupportNavigateUp();
}
}


效果演示


传递参数

静态传递

获取数据并显示

1
2
3
4
5
6
7
8
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

String name = getArguments().getString("name");
TextView textView = getView().findViewById(R.id.textView);
textView.setText(name);
}

很显然,这种方式只能传递静态的数据


动态传递

样例

传递方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

getView().findViewById(R.id.button).setOnClickListener(v->{
EditText editText = getView().findViewById(R.id.editText);
String str = editText.getText().toString();

if(TextUtils.isEmpty(str)){
Toast.makeText(getActivity(),"请输入名字!",Toast.LENGTH_SHORT).show();;
return;
}
//将数据放在Bundle中
Bundle bundle = new Bundle();
bundle.putString("my_name", str);

NavController controller = Navigation.findNavController(v);
controller.navigate(R.id.action_homeFragment2_to_detailFragment2,bundle);
});
}

接收方:

1
2
3
4
5
6
7
8
9
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);

String myName = getArguments().getString("my_name");

TextView textView = getView().findViewById(R.id.textView);
textView.setText(myName);
}

演示:


基于ViewModel(※)

  1. 修改配置build.gradle,为了简单,就不导入savedstate的依赖了

    1
    2
    3
    4
    5
    6
    7
    8
    dependencies {
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    //implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
    }

    buildFeatures {
    dataBinding = true
    }
  2. 创建两个Fragment,并绘制样式

  3. 创建导航,进行导航配置,然后在activity_main.xml中配置NavHostFragment

  4. 创建ViewModel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class MyViewModel extends ViewModel {

    private MutableLiveData<Integer> number;

    public MutableLiveData<Integer> getNumber() {
    if(number == null){
    number = new MutableLiveData<>();
    number.setValue(0);
    }
    return number;
    }

    public void add(int x) {
    number.setValue(number.getValue()+x);
    if(number.getValue()<0){
    number.setValue(0);
    }
    }
    }
  5. 修改fragment_home.xml,转为data binding layout,再修改代码(显示修改的部分)

    1
    2
    3
    4
    5
    6
    7
    8
    <data>
    <variable
    name="data"
    type="com.example.navigation3.MyViewModel" />
    </data>

    <TextView
    android:text="@{String.valueOf(data.number)}"/>
  6. 修改fragment_detail.xml,转为data binding layout,再修改代码(显示修改的部分)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <data>
    <variable
    name="data"
    type="com.example.navigation3.MyViewModel" />
    </data>

    <TextView
    android:text="@{String.valueOf(data.number)}"/>

    <Button
    android:onClick="@{()->data.add(1)}"/>

    <Button
    android:onClick="@{()->data.add(-1)}"/>

    注意:我们的界面xml文件中只用于显示数据和进行数据相关的操作,导航跳转等与数据无关的操作放在我们的具体代码逻辑中,比如下方

  7. 修改HomeFragment

    LayoutInflater主要是用于加载布局的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    MyViewModel myViewModel = new ViewModelProvider(getActivity()).get(MyViewModel.class);
    FragmentHomeBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_home,container,false);

    binding.setData(myViewModel);
    binding.setLifecycleOwner(getActivity());

    //监听点击事件,进行导航
    binding.button.setOnClickListener(v->{
    NavController controller = Navigation.findNavController(v);
    controller.navigate(R.id.action_homeFragment_to_detailFragment);
    });

    binding.seekBar.setProgress(myViewModel.getNumber().getValue());

    //监听seekBar的改变
    binding.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    myViewModel.getNumber().setValue(progress);
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
    });

    return binding.getRoot();

    }
  8. 修改DetailFragment

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    MyViewModel viewModel = new ViewModelProvider(getActivity()).get(MyViewModel.class);
    FragmentDetailBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_detail,container,false);

    binding.setData(viewModel);
    binding.setLifecycleOwner(getActivity());

    //监听点击事件,进行导航
    binding.button4.setOnClickListener(v->{
    NavController controller = Navigation.findNavController(v);
    controller.navigate(R.id.action_detailFragment_to_homeFragment);
    });

    return binding.getRoot();
    }
  9. 演示


自定义动画

创建Animation资源

编写代码

滑动效果

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="-100%"
android:toXDelta="0%">

</translate>
</set>

复制一下文件slide_from_left.xml,创建slide_to_right.xml文件

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0%"
android:toXDelta="100%">
</translate>
</set>

添加动画

演示

缩放和旋转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="1000"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0" />

<rotate
android:duration="1000"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" />
</set>

效果


Room

介绍和依赖

官网地址: https://developer.android.google.cn/jetpack/androidx/releases/room

1
2
3
4
5
6
7
8
9
10
11
12
13
def room_version = "2.2.5"

implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"

// // optional - RxJava support for Room
// implementation "androidx.room:room-rxjava2:$room_version"
//
// // optional - Guava support for Room, including Optional and ListenableFuture
// implementation "androidx.room:room-guava:$room_version"

// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"

简单案列

  1. 导入依赖

    1
    2
    3
    4
    5
    def room_version = "2.2.5"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    testImplementation "androidx.room:room-testing:$room_version"
  2. 编写实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package com.example.roombasic;

    import androidx.room.ColumnInfo;
    import androidx.room.Entity;
    import androidx.room.PrimaryKey;

    /**
    * @author codekiller
    * @date 2020/11/16 17:42
    * @Description TODO
    */
    @Entity
    public class Word {
    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "english_word")
    private String word;

    @ColumnInfo(name = "chinese_meaning")
    private String chineseMeaning;

    public Word(){}

    public Word(String word, String chineseMeaning) {
    this.word = word;
    this.chineseMeaning = chineseMeaning;
    }

    public int getId() {
    return id;
    }

    public void setId(int id) {
    this.id = id;
    }

    public Word setIdc(int id){
    this.id = id;
    return this;
    }

    public String getWord() {
    return word;
    }

    public void setWord(String word) {
    this.word = word;
    }

    public String getChineseMeaning() {
    return chineseMeaning;
    }

    public void setChineseMeaning(String chineseMeaning) {
    this.chineseMeaning = chineseMeaning;
    }
    }
  3. 编写数据库操作Dao类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package com.example.roombasic;

    import androidx.room.Dao;
    import androidx.room.Delete;
    import androidx.room.Insert;
    import androidx.room.Query;
    import androidx.room.Update;

    import java.util.List;

    /**
    * @author codekiller
    * @date 2020/11/16 17:44
    * @Description Dao: Database Access Object
    */
    @Dao
    public interface WordDao {

    @Insert
    void insertWords(Word... words);

    @Update
    void updateWords(Word... words);

    @Delete
    void deleteWords(Word... words);

    @Query("DELETE FROM WORD")
    void deleteAllWords();

    @Query("SELECT * FROM WORD ORDER BY ID DESC")
    List<Word> getAllWords();
    }
  4. 数据库类

    1
    2
    3
    4
    5
    @Database(entities = {Word.class}, version = 1, exportSchema = false)
    public abstract class WordDatabase extends RoomDatabase {

    public abstract WordDao getWordDao();
    }
  5. 界面

  6. 具体的数据库操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    package com.example.roombasic;

    import androidx.appcompat.app.AppCompatActivity;
    import androidx.room.Room;
    import androidx.room.util.StringUtil;

    import android.os.Bundle;
    import android.widget.Button;
    import android.widget.TextView;

    import java.util.List;

    public class MainActivity extends AppCompatActivity {

    WordDatabase wordDatabase;
    WordDao wordDao;

    TextView textView;
    Button insertButton,updateButton,clearButton,deleteButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    wordDatabase = Room.databaseBuilder(this,WordDatabase.class,"word_database")
    .allowMainThreadQueries()
    .build();

    textView = findViewById(R.id.textView2);
    insertButton = findViewById(R.id.insertButton);
    updateButton = findViewById(R.id.updateButton);
    clearButton = findViewById(R.id.clearButton);
    deleteButton = findViewById(R.id.deleteButton);

    wordDao = wordDatabase.getWordDao();

    updateView();

    //插入操作
    insertButton.setOnClickListener(v->{
    Word word1 = new Word("Hello","你好");
    Word word2 = new Word("World","世界");
    wordDao.insertWords(word1,word2);
    updateView();
    });

    //更新操作
    updateButton.setOnClickListener(v->{
    Word word = new Word("spring","春天");
    word.setId(1);
    wordDao.updateWords(word);
    updateView();
    });

    //清除操作
    clearButton.setOnClickListener(v->{
    wordDao.deleteAllWords();
    updateView();
    });

    //删除操作
    deleteButton.setOnClickListener(v->{
    wordDao.deleteWords(new Word().setIdc(4));
    updateView();
    });



    }

    void updateView(){
    List<Word> words = wordDao.getAllWords();
    String text = "";
    for (int i=words.size()-1;i>=0;--i) {
    text += words.get(i).getId()+":"+words.get(i).getWord()+" = "+words.get(i).getChineseMeaning()+";\n";
    }
    textView.setText(text);

    }

    }
  7. 运行相关操作可以在我们的文件中看到


代码优化

在上面的例子中,我们可以看到,数据库的操作都是放在主线程里面进行执行的,这显然不是太好。而且我们的mainActivity里面代码太多了,现在对代码进行一些修改。

看下文件结构

  1. Word不变

  2. WordDao不变

  3. WordDatabase,采用单例模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Database(entities = {Word.class}, version = 1, exportSchema = false)
    public abstract class WordDatabase extends RoomDatabase {

    private static WordDatabase INSTANCE;


    /**
    * @Description 单例获取
    * @date 2020/11/17 8:14
    * @param context
    * @return
    */
    public static synchronized WordDatabase getDatabase(Context context){
    if(INSTANCE == null){
    INSTANCE = Room.databaseBuilder(context, WordDatabase.class, "word_database")
    .allowMainThreadQueries()
    .build();
    }
    return INSTANCE;
    }

    public abstract WordDao getWordDao();
    }
  4. WordRepository,封装数据库的操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    public class WordRepository {

    private LiveData<List<Word>> allWordsLive;

    private WordDao wordDao;

    public WordRepository(Context context) {
    WordDatabase wordDatabase = WordDatabase.getDatabase(context);
    wordDao = wordDatabase.getWordDao();
    allWordsLive = wordDao.getAllWords();
    }

    public LiveData<List<Word>> getAllWordsLive() {
    return allWordsLive;
    }

    public void insertWords(Word... words) {
    new InsertAsyncTask(wordDao).doInBackground(words);
    }

    public void updateWords(Word... words) {
    new UpdateAsyncTask(wordDao).doInBackground(words);
    }

    public void deleteWords(Word... words) {
    new DeleteAsyncTask(wordDao).doInBackground(words);
    }

    public void deleteAllWords() {
    new DeleteAllAsyncTask(wordDao).doInBackground();
    }

    static class InsertAsyncTask extends AsyncTask<Word, Void, Void> {

    private WordDao wordDao;

    public InsertAsyncTask(WordDao wordDao) {
    this.wordDao = wordDao;
    }

    @Override
    protected Void doInBackground(Word... words) {
    wordDao.insertWords(words);
    return null;
    }
    }

    static class UpdateAsyncTask extends AsyncTask<Word, Void, Void> {

    private WordDao wordDao;

    public UpdateAsyncTask(WordDao wordDao) {
    this.wordDao = wordDao;
    }

    @Override
    protected Void doInBackground(Word... words) {
    wordDao.updateWords(words);
    return null;
    }
    }

    static class DeleteAsyncTask extends AsyncTask<Word, Void, Void> {

    private WordDao wordDao;

    public DeleteAsyncTask(WordDao wordDao) {
    this.wordDao = wordDao;
    }

    @Override
    protected Void doInBackground(Word... words) {
    wordDao.deleteWords(words);
    return null;
    }
    }

    static class DeleteAllAsyncTask extends AsyncTask<Void, Void, Void> {

    private WordDao wordDao;

    public DeleteAllAsyncTask(WordDao wordDao) {
    this.wordDao = wordDao;
    }

    @Override
    protecteds Void doInBackground(Void... voids) {
    this.wordDao.deleteAllWords();
    return null;
    }
    }
    }

    注意一点:AsyncTask已经被弃用了

  5. WordViewModel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class WordVIewModel extends AndroidViewModel {

    private WordRepository wordRepository;

    public WordVIewModel(@NonNull Application application) {
    super(application);
    wordRepository = new WordRepository(application);

    }

    public LiveData<List<Word>> getAllWordsLive() {
    return wordRepository.getAllWordsLive();
    }


    public void insertWords(Word... words) {
    wordRepository.insertWords(words);
    }

    public void updateWords(Word... words) {
    wordRepository.updateWords(words);
    }

    public void deleteWords(Word... words) {
    wordRepository.deleteWords(words);
    }

    public void deleteAllWords() {
    wordRepository.deleteAllWords();
    }
    }
  6. MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    public class MainActivity extends AppCompatActivity {


    TextView textView;
    Button insertButton,updateButton,clearButton,deleteButton;

    private WordVIewModel wordVIewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    wordVIewModel = new ViewModelProvider(this).get(WordVIewModel.class);

    textView = findViewById(R.id.textView2);
    insertButton = findViewById(R.id.insertButton);
    updateButton = findViewById(R.id.updateButton);
    clearButton = findViewById(R.id.clearButton);
    deleteButton = findViewById(R.id.deleteButton);



    wordVIewModel.getAllWordsLive().observe(this,(words)->{
    StringBuilder text = new StringBuilder();
    for (int i=words.size()-1;i>=0;--i) {
    text.append(words.get(i).getId()).append(":").append(words.get(i).getWord()).append(" = ").append(words.get(i).getChineseMeaning()).append(";\n");
    }
    textView.setText(text.toString());
    });

    //插入操作
    insertButton.setOnClickListener(v->{
    Word word1 = new Word("Hello","你好");
    Word word2 = new Word("World","世界");
    wordVIewModel.insertWords(word1,word2);
    });

    //更新操作
    updateButton.setOnClickListener(v->{
    Word word = new Word("spring","春天");
    word.setId(1);
    wordVIewModel.updateWords(word);
    });

    //清除操作
    clearButton.setOnClickListener(v->{
    wordVIewModel.deleteAllWords();
    });

    //删除操作
    deleteButton.setOnClickListener(v->{
    wordVIewModel.deleteWords(new Word().setIdc(10));
    });
    }
    }

RecyclerView

RecyclerView的优点

  1. 进行item回收复用
  2. RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
  3. 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等),也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。你想控制Item的分隔线,可以通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去抒写代码。
  4. 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。

首先修改一下插入操作代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
insertButton.setOnClickListener(v->{
String[] english = {
"Hello",
"World",
"Android",
"Google",
"Studio",
"Project",
"Database",
"Recycler",
"View",
"String",
"Value",
"Integer"
};
String[] chinese = {
"你好",
"世界",
"安卓系统",
"谷歌公司",
"工作室",
"项目",
"数据库",
"回收站",
"视图",
"字符串",
"价值",
"整数类型"
};
for(int i=0;i<chinese.length;i++){
wordVIewModel.insertWords(new Word(english[i], chinese[i]));
}
});

使用RecyclerView

创建layout

创建元素的layout

创建一个Adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.example.roombasic;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;

/**
* @author codekiller
* @date 2020/11/17 9:20
* @Description TODO
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

List<Word> allWords = new ArrayList<>();

public void setAllWords(List<Word> allWords){
this.allWords = allWords;
}


@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View itemView = layoutInflater.inflate(R.layout.cell_normal,parent,false);
return new MyViewHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Word word = allWords.get(position);
holder.textViewNumber.setText(String.valueOf(position+1));
holder.textViewEnglish.setText(word.getWord());
holder.textViewChinese.setText(word.getChineseMeaning());
}

@Override
public int getItemCount() {
return allWords.size();
}

static class MyViewHolder extends RecyclerView.ViewHolder{

TextView textViewNumber, textViewEnglish, textViewChinese;

public MyViewHolder(@NonNull View itemView) {
super(itemView);
textViewNumber = itemView.findViewById(R.id.textViewNumber);
textViewChinese = itemView.findViewById(R.id.textViewChinese);
textViewEnglish = itemView.findViewById(R.id.textViewEnglish);

}
}

}

修改MainAdapter代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package com.example.roombasic;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.room.Room;
import androidx.room.util.StringUtil;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;

import java.util.List;

public class MainActivity extends AppCompatActivity {


RecyclerView recyclerView;
Button insertButton,updateButton,clearButton,deleteButton;
MyAdapter myAdapter;

private WordVIewModel wordVIewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
wordVIewModel = new ViewModelProvider(this).get(WordVIewModel.class);


insertButton = findViewById(R.id.insertButton);
updateButton = findViewById(R.id.updateButton);
clearButton = findViewById(R.id.clearButton);
deleteButton = findViewById(R.id.deleteButton);

recyclerView = findViewById(R.id.recyclerView);
myAdapter = new MyAdapter();

recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(myAdapter);

wordVIewModel.getAllWordsLive().observe(this,(words)->{
myAdapter.setAllWords(words);
myAdapter.notifyDataSetChanged();
});

//插入操作
insertButton.setOnClickListener(v->{
String[] english = {
"Hello",
"World",
"Android",
"Google",
"Studio",
"Project",
"Database",
"Recycler",
"View",
"String",
"Value",
"Integer"
};
String[] chinese = {
"你好",
"世界",
"安卓系统",
"谷歌公司",
"工作室",
"项目",
"数据库",
"回收站",
"视图",
"字符串",
"价值",
"整数类型"
};
for(int i=0;i<chinese.length;i++){
wordVIewModel.insertWords(new Word(english[i], chinese[i]));
}
});

//更新操作
updateButton.setOnClickListener(v->{
Word word = new Word("spring","春天");
word.setId(1);
wordVIewModel.updateWords(word);
});

//清除操作
clearButton.setOnClickListener(v->{
wordVIewModel.deleteAllWords();
});

//删除操作
deleteButton.setOnClickListener(v->{
wordVIewModel.deleteWords(new Word().setIdc(10));
});
}
}

CardView,波纹效果和网页跳转

创建一个新的layout,记得三个textView的id,和cell_normal中相同的就行

设置一下margin,CardView下修改

修改Adapter

1
2
3
4
5
6
7
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View itemView = layoutInflater.inflate(R.layout.cell_card,parent,false);
return new MyViewHolder(itemView);
}

波纹效果

相当于

再修改背景样式

运行之后发现,可点击,但是波纹效果是在后面的,此时要修改一下前景


效果图演示

这里有一个小功能,就是跳转到有道词典了。修改adapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Word word = allWords.get(position);
holder.textViewNumber.setText(String.valueOf(position+1));
holder.textViewEnglish.setText(word.getWord());
holder.textViewChinese.setText(word.getChineseMeaning());

holder.itemView.setOnClickListener(v->{
Uri uri = Uri.parse("https://m.youdao.com/dict?le=eng&q=" + holder.textViewEnglish.getText());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
holder.itemView.getContext().startActivity(intent);
});
}

增加隐藏

  1. 增加一个switch

  2. 增加一个字段

    1
    2
    @ColumnInfo(name = "chinese_invisible")
    private boolean chineseInvisible;
  3. 修改adapter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private List<Word> allWords = new ArrayList<>();
    private WordVIewModel wordVIewModel;

    public MyAdapter(WordVIewModel wordVIewModel) {
    this.wordVIewModel = wordVIewModel;
    }

    public void setAllWords(List<Word> allWords){
    this.allWords = allWords;
    }


    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
    View itemView = layoutInflater.inflate(R.layout.cell_card2,parent,false);
    return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
    Word word = allWords.get(position);
    holder.textViewNumber.setText(String.valueOf(position+1));
    holder.textViewEnglish.setText(word.getWord());
    holder.textViewChinese.setText(word.getChineseMeaning());

    holder.aSwitchChineseInvisible.setOnCheckedChangeListener(null);

    //控制显示和隐藏中文
    if(word.isChineseInvisible()){
    //GONE 会隐藏,而且会释放空间(改变位置),而invisible仅仅是不显示。
    holder.textViewChinese.setVisibility(View.GONE);
    holder.aSwitchChineseInvisible.setChecked(true);
    } else {
    holder.textViewChinese.setVisibility(View.VISIBLE);
    holder.aSwitchChineseInvisible.setChecked(false);
    }

    //进行有道词典搜索
    holder.itemView.setOnClickListener(v->{
    Uri uri = Uri.parse("https://m.youdao.com/dict?le=eng&q=" + holder.textViewEnglish.getText());
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(uri);
    holder.itemView.getContext().startActivity(intent);
    });

    //switch监听
    holder.aSwitchChineseInvisible.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if(isChecked){
    holder.textViewChinese.setVisibility(View.GONE);
    word.setChineseInvisible(true);
    wordVIewModel.updateWords(word);
    }else{
    holder.textViewChinese.setVisibility(View.VISIBLE);
    word.setChineseInvisible(false);
    wordVIewModel.updateWords(word);
    }

    }
    });
    }

    @Override
    public int getItemCount() {
    return allWords.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder{

    TextView textViewNumber, textViewEnglish, textViewChinese;

    Switch aSwitchChineseInvisible;

    public MyViewHolder(@NonNull View itemView) {
    super(itemView);
    textViewNumber = itemView.findViewById(R.id.textViewNumber);
    textViewChinese = itemView.findViewById(R.id.textViewChinese);
    textViewEnglish = itemView.findViewById(R.id.textViewEnglish);
    aSwitchChineseInvisible = itemView.findViewById(R.id.chineseInvisible);
    }
    }

    }

这一点尤为重要

holder.aSwitchChineseInvisible.setOnCheckedChangeListener(null);

  1. 修改MainActvity

    一定要避免两次刷新,不然后卡顿

    1
    2
    3
    4
    5
    6
    7
    8
    9
    wordVIewModel.getAllWordsLive().observe(this,(words)->{
    int temp = myAdapter.getItemCount();
    myAdapter.setAllWords(words);

    //避免两种刷新冲突
    if(temp != words.size()) {
    myAdapter.notifyDataSetChanged();
    }
    });

数据迁移

我们此时有一个需求,在原有的word表中增加一个字段,应该怎么做?

1
2
3
4
5
6
7
8
9
10
11
@PrimaryKey(autoGenerate = true)
private int id;

@ColumnInfo(name = "english_word")
private String word;

@ColumnInfo(name = "chinese_meaning")
private String chineseMeaning;

@ColumnInfo(name = "bar_data")
private boolean bar;

破坏式迁移

修改数据库的version和增加.fallbackToDestructiveMigration(),这种方式就是将现有结构清空,然后创建新的数据库,是一种破坏式的创建。不推荐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Database(entities = {Word.class}, version = 2, exportSchema = false)
public abstract class WordDatabase extends RoomDatabase {

private static WordDatabase INSTANCE;


/**
* @Description 单例获取
* @date 2020/11/17 8:14
* @param context
* @return
*/
public static synchronized WordDatabase getDatabase(Context context){
if(INSTANCE == null){
INSTANCE = Room.databaseBuilder(context, WordDatabase.class, "word_database")
//.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
return INSTANCE;
}

public abstract WordDao getWordDao();
}

SQL语句方式(※)

其实就是运行一个sql语句,对表结构进行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Database(entities = {Word.class}, version = 2, exportSchema = false)
public abstract class WordDatabase extends RoomDatabase {

private static WordDatabase INSTANCE;


/**
* @Description 单例获取
* @date 2020/11/17 8:14
* @param context
* @return
*/
public static synchronized WordDatabase getDatabase(Context context){
if(INSTANCE == null){
INSTANCE = Room.databaseBuilder(context, WordDatabase.class, "word_database")
//.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2)
.build();
}
return INSTANCE;
}

public abstract WordDao getWordDao();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("alter table word add column bar_data INTEGER not null default 1");
}
};
}

查看结果,将我们的数据库表信息存储到本地,存储前记得刷新!!!然后用DB Brower for SQLite打开word_database文件。

通过这种方式,我们也能执行任何sql语句


BottomNavigation

基本步骤

  1. 新建一个项目

  2. 选择三个矢量图(随意

  3. 创建三个Fragment和相应的ViewModel

  4. 创建一个Menu资源

    会生成一个文件夹和menu.xml文件

  5. 修改menu.xml

  6. 创建一个navigation资源,并进行修改

  7. 修改activity_main.xml

    首先引入我们的menu

    设置一个NavHostFragment

  8. 修改MainActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
    NavController controller = Navigation.findNavController(this, R.id.fragment);

    AppBarConfiguration configuration = new AppBarConfiguration.Builder(bottomNavigationView.getMenu()).build();

    NavigationUI.setupActionBarWithNavController(this,controller,configuration);
    NavigationUI.setupWithNavController(bottomNavigationView,controller);

    }
  9. 效果

以上基本就完成了


增加动画(ObjectAnimator)

  1. 添加图标

  2. 修改FirstFragment,添加旋转动画

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class FirstFragment extends Fragment {

    private FirstViewModel mViewModel;
    private ImageView imageView;

    public static FirstFragment newInstance() {
    return new FirstFragment();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
    @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.first_fragment,container,false);
    imageView = view.findViewById(R.id.imageView);


    return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mViewModel = new ViewModelProvider(this).get(FirstViewModel.class);

    imageView.setRotation(mViewModel.rotationPosition);

    //添加旋转效果
    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "rotation", 0, 0);
    objectAnimator.setDuration(500);

    imageView.setOnClickListener(v->{
    if(!objectAnimator.isRunning()) {
    objectAnimator.setFloatValues(imageView.getRotation(), imageView.getRotation() + 100);
    mViewModel.rotationPosition = imageView.getRotation() + 100;
    objectAnimator.start();
    }
    });

    }

    }
  3. 修改SecondFragment,添加缩放动画

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public class SecondFragment extends Fragment {

    private SecondViewModel mViewModel;
    private ImageView imageView;

    public static SecondFragment newInstance() {
    return new SecondFragment();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
    @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.second_fragment, container, false);
    imageView = view.findViewById(R.id.imageView);

    return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mViewModel = new ViewModelProvider(this).get(SecondViewModel.class);


    imageView.setScaleX(mViewModel.scaleFactor);
    imageView.setScaleY(mViewModel.scaleFactor);

    //缩放动画
    ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0, 0);
    ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0, 0);
    scaleX.setDuration(500);
    scaleY.setDuration(500);

    imageView.setOnClickListener(v->{
    if(!scaleX.isRunning()){
    scaleX.setFloatValues(imageView.getScaleX()+0.1f);
    scaleY.setFloatValues(imageView.getScaleY()+0.1f);

    //记录当前旋转值
    mViewModel.scaleFactor = imageView.getScaleX() + 0.1f;

    scaleX.start();
    scaleY.start();
    }
    });
    }

    }
  4. 修改ThirdFragment,添加移动动画

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class ThirdFragment extends Fragment {

    private ThirdViewModel mViewModel;
    private ImageView imageView;

    public static ThirdFragment newInstance() {
    return new ThirdFragment();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
    @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.third_fragment, container, false);
    imageView = view.findViewById(R.id.imageView);

    return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mViewModel = new ViewModelProvider(this).get(ThirdViewModel.class);

    imageView.setX(mViewModel.dx+imageView.getX());

    ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(imageView, "x", 0, 0);
    objectAnimatorX.setDuration(500);

    imageView.setOnClickListener(v->{
    if(!objectAnimatorX.isRunning()){
    float dx = new Random().nextBoolean() ? 100: -100;
    mViewModel.dx += dx;

    objectAnimatorX.setFloatValues(imageView.getX()+dx);
    objectAnimatorX.start();
    }
    });

    }
    }
  5. 效果



Paging

此处用到了Room和Page,先导入依赖

1
2
3
4
5
6
7
8
9
10
dependencies {
def paging_version = "2.1.2"

implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx

def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<com.example.mvvmnews.ui.view.FolderTextView
android:id="@+id/contentDetail"
android:layout_width="0dp"
android:layout_height="70dp"
android:layout_marginBottom="16dp"
android:hint="大祭司哦啊的横扫的哈"
android:text="降低欧莎度搜啊哈到时候"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/textView"
app:layout_constraintVertical_bias="1.0" />
-------------本文结束感谢您的阅读-------------
六经蕴籍胸中久,一剑十年磨在手

欢迎关注我的其它发布渠道