原创-Android Studio篇章1

Android Studio

1 目录说明

所有以drawable 开头的文件夹都是用来放图片的,所有以mipmap开头的文件夹都是用来放应用图标的,所有以 values开头的文件夹都是用来放字符串、样式、颜色等配置的,layout文件夹是用来放布局文件 的。
HelioWorld项目的应用图标就是通过android:icon属性来指定的,应用的名称则是 通过android: label属性指定的

2 主活动

// 主活动,点击图标,首先启动这个活动
<intent-filter>
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

3 日志

logv/logd/logi/logw/loge 输出日志
logt 输出常量
Log.v()用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android 日志里面级别最低的一种。
Log.d()用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。 对应级另【J debug,比verbose高一级。
Log.i()用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分 析用户行为数据。对应级别info,比debug高一级。
Log.w()用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修 复一下这些出现警告的地方。对应级别warn,比info高一级。
Log.e()用于打印程序中的错误信息,比如程序进入到了 catch语句当中。当有错误信 息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别error, 比warn高一级。
其中,你不仅可以看到打印日志的内容和tag名,就连程序的包名、打印的时间以及应用程 序的进程号都可以看到。

在这里插入图片描述

4 加载布局

setContentView()方法来给当前的活动加载一个布局,而在 setContentView()方法中,我们一般都会传入一个布局文件的id.项目中添加的任何资源都会在R文件中生成一个相应的资源id,只需要调用R.layout. first layout就可以得到first layout. xml布局的id,然后将这个值传入setContentView()方法即可。

5 注册活动(在 AndroidManifest 文件中注册)

所有的活动都要在AndroidManifest.xml中进行注册才能生效
由于在最外层的<manifest>标签中已经通过package属性指定了程序的包名是 com.yxj.helloworld,因此在注册活动时这一部分就可以省略了,直接使用.MainActivity就足够
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
//可以使用android:label指定活动中标题栏的内容,标题栏是显示在活 动最顶部的。需要注意的是,给主活动指定的label不仅会成为 标题栏中的内容,还会成为启动器(Launcher)中应用程序显示的名称。
<activity android:name=".MainActivity" android:label="This is  MainActivity">

在这里插入图片描述

6 在活动中使用Toast

Toast是Android系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息 通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间
Toast的用法非常简单,通过静态方法makeText ()创建出一个Toast对象,然后调用show() 将Toast显示出来就可以了。这里需要注意的是,makeText ()方法需要传入3个参数。第一个参 数是Context,也就是Toast要求的上下文,由于活动本身就是一个Context对象,因此这里直 接传入FirstActivity.this即可。第二个参数是Toast显示的文本内容,第三个参数是Toast 显示的时长,有两个内置常量可以选择Toast. LENGTH SHORT和Toast. LENGTH_LONG
public void onClick(View v) {
    
    
           Toast.makeText(FirstActivity.this,"woshiyaoxinjiajiayou1111",Toast.LENGTH_SHORT).show();

}

7 在活动中使用Menu

Android给我 们提供了一种方式,可以让菜单都能得到展示的同时,还能不占用任何屏幕空间。
通过getMenuInflater()方法能够得到Menulnflater对象,再调用它的inflate()方法 就可以给当前活动创建菜单了。inflate()方法接收两个参数,第一个参数用于指定我们通过哪 一个资源文件来创建菜单,这里当然传入R.menu.main.第二个参数用于指定我们的菜单项将 添加到哪一个Menu对象当中,这里直接使用onCreateOptionsMenu()方法中传入的menu参数。 然后给这个方法返回true,表示允许创建的菜单显示出来,如果返回了 false,创建的菜单将 无法显示。
当然,仅仅让菜单显示出来是不够的,我们定义菜单不仅是为了看的,关键是要菜单真正可用才行,因此还要再定义菜单响应事件。在FirstActivity中重写onOptionsItemSelected()方法:
package com.yxj.helloworld;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.DialogTitle;

import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    
    
        getMenuInflater().inflate(R.menu.main,menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
    
    
        switch (item.getItemId()){
    
    
            case R.id.add_item:
                Toast.makeText(this,"You clicked Add", Toast.LENGTH_SHORT).show();
                break;
            case R.id.remove_item:
                Toast.makeText(this,"You clicked Remove",Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Toast toast = Toast.makeText(MainActivity.this, "You clicked Button 1", Toast.LENGTH_SHORT);
                toast.show();
                finish();  // 结束活动
            }
        });
    }
}

8 销毁活动

finish()  // 销毁当前活动

9 Intent

9.1 显示Intent

我们首先构建出了一个Intent,传入MainActivity.this作为上下文,传入Second- Activity.class作为目标活动,这样我们的“意图”就非常明显了,即在MainActivity这个活 动的基础上打开SecondActivity这个活动。然后通过startActivity。方法来执行这个Intent
显式Intent
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class SecondActivity extends AppCompatActivity {
    
    

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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".SecondActivity">

</androidx.constraintlayout.widget.ConstraintLayout>



package com.yxj.helloworld;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.DialogTitle;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}

在这里插入图片描述

9.2 隐式Intent

相比于显式Intent,隐式Intent则含蓄了许多,它并不明确指出我们想要启动哪一个活动, 而是指定了一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent, 并帮我们找岀合适的活动去启动。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".SecondActivity" android:label="secondActivity">
            <intent-filter>
            // 只有<action>和〈category〉中的内容同时能够匹配上Intent中指定的action和category时,这个活动才能响应该Intent
                <action android:name="com.yxj.helloworld.ACTION_START"></action>
                <category android:name="android.intent.category.DEFAULT"></category>
                <category android:name="com.yxj.helloworld.MY_CATEGORY"></category>
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="This is  MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


package com.yxj.helloworld;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.DialogTitle;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent("com.yxj.helloworld.ACTION_START");
 // 不是说要<action>和〈category〉同时匹配上才能响应的吗?怎么没看到哪里有指定 category 呢?这是因为 android.intent. category.DEFAULT 是一种默认的 category,在调 用startActivity()方法的时候会自动将这个category添加到Intent中.每个Intent中只能指定一个action,但却能指定多个category
              intent.addCategory("com.yxj.helloworld.MY_CATEGORY");
                startActivity(intent);
            }
        });
    }
}

9.3 更多隐式Intent的用法

使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使 得Android多个应用程序之间的功能共享成为了可能

9.3.1 使用内置浏览器

package com.yxj.helloworld;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.DialogTitle;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.net.URL;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
            // 这里我们首先指定了 Intent的action是Intent .ACTION_VIEW,这是一个Android系统内 置的动作,其常量值为android .intent .action. VIEW。然后通过Uri.parse()方法,将一个 网址字符串解析成一个Url对象,再调用Intent的setData ()方法将这个Uri对象传递进去。
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse("http://www.baidu.com")); startActivity(intent);
            }
        });
    }
}

在这里插入图片描述

9.3.2 使用内置电话

Intent .ACTION_DIAL,这又是一个Android系统的内置电话动 作

在这里插入图片描述

9.4 传递数据

9.4.1 向下一个活动传递数据

在启动活动时传递数据的思路很简单,Intent中提供了一系列putExtra()方法的重载,可 以把我们想要传递的数据暂存在Intent中,启动了另一个活动后,只需要把这些数据再从Intent 中取岀就可以了。比如说MainActivity中有一个字符串,现在想把这个字符串传递到SecondActivity 中,你就可以这样编写:
MainActivity.java
package com.yxj.helloworld;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.DialogTitle;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.net.URL;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                String data = "Hello SecondActivity";
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                // 这里我们还是使用显式Intent的方式来启动SecondActivity,并通过putExtra()方法传递了 一个字符串。注意这里putExtra()方法接收两个参数,第一个参数是键,用于后面从Intent中 取值,第二个参数才是真正要传递的数据
                intent.putExtra("extra_data",data);
                startActivity(intent);

            }
        });
    }
}
SecondActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

public class SecondActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        // 首先可以通过getlntent()方法获取到用于启动SecondActivity的Intent,然后调用 getString Extra()方法,传入相应的键值,就可以得到传递的数据了。这里由于我们传递的 是字符串,所以使用getStringExtra()方法来获取传递的数据。如果传递的是整型数据,则 使用getIntExtra()方法;如果传递的是布尔型数据,则使用getBooleanExtra()方法,以此类推。
        Intent intent = getIntent();
        String data = intent.getStringExtra("extra_data");
        Log.d("SecondActivity", data);
    }
}

在这里插入图片描述

9.4.2 返回数据给上一个活动

MainActivity.java
package com.yxj.helloworld;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.DialogTitle;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.net.URL;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                // Activity中还有一个startActivityForResult ()方法也 是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动.startActivityForResult ()方法接收两个参数,第一个参数还是Intent,第二个参数是请 求码,用于在之后的回调中判断数据的来源
                startActivityForResult(intent,1);

            }
        });
    }

// 由于我们是使用 startActivityForResult ()方法来启动 SecondActivity 的,在 SecondActivity 被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在FirstActivity中重 写这个方法来得到返回的数据.onActivityResult()方法带有三个参数,第一个参数requestcode,即我们在启动活动时 传入的请求码。第二个参数resultCode,即我们在返回数据时传入的处理结果。第三个参数 data,即携带着返回数据的Intent。由于在一个活动中有可能调用startActivityForResult() 方法去启动很多不同的活动,每一个活动返回的数据都会回调到onActivityResult()这个方法 中,因此我们首先要做的就是通过检查requestcode的值来判断数据来源。确定数据是从 SecondActivity返回的之后,我们再通过resultCode的值来判断处理结果是否成功。最后从data 中取值并打印出来,这样就完成了向上一个活动返回数据的工作
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    
    
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
    
    
            case 1:
                if (resultCode == RESULT_OK){
    
    
                    String data_return = data.getStringExtra("data_return");
                    Log.d("MainActivity",data_return);
                }
                break;

            default:
        }
    }
}
SecondActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class SecondActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent();
                intent.putExtra("data_return", "Hello FisrstActivity");
                // 这个方法非常重要,是专门用于向上一个活动返回数据的。setResultO方法接收两个参数, 第一个参数用于向上一个活动返回处理结果,一般只使用RESULT_0K或RESULT_CANCELED这 两个值,第二个参数则把带有数据的Intent传递回去,然后调用了 finish()方法来销毁当前活动。
                setResult(RESULT_OK,intent);
                finish();
            }
        });
    }
}

在这里插入图片描述

如果用户在SecondActivity中并不是通过点击按钮,而是通过按下Back 键回到FirstActivity,这样数据不就没法返回了吗?没错,不过这种情况还是很好处理的,我们 可以通过在SecondActivity中重写onBackPressed ()方法来解决这个问题
这样的话,当用户按下Back键,就会去执行onBackPressedO方法中的代码,我们在这里 添加返回数据的逻辑就行了。
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class SecondActivity extends AppCompatActivity {
    
    

    @Override
    public void onBackPressed() {
    
    
        Intent intent = new Intent();
        intent.putExtra("data_return", "Hello FisrstActivity");
        setResult(RESULT_OK,intent);
        finish();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent();
                intent.putExtra("data_return", "Hello FisrstActivity");
                setResult(RESULT_OK,intent);
                finish();
            }
        });

    }
}

在这里插入图片描述

10 活动的生命周期

10.1 返回栈

Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集 合,这个栈也被称作返回栈(Back Stack )o栈是一种后进先出的数据结构,在默认情况下,每当 我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键 或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会 重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。

在这里插入图片描述

10.2 活动状态

1.	运行状态
当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于 运行状态的活动,因为这会带来非常差的用户体验。
2.	暂停状态
当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得 既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的, 比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂 停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可 见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收 这种活动。
3.	停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为 这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于 停止状态的活动有可能会被系统回收。
4.	销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活 动,从而保证手机的内存充足。

10.3 活动的生存期

Activity类中定义了 7个回调方法,覆盖了活动生命周期的每一个环节,下面就来一一介绍 这7个方法。
# onCreate()0这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它 会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如 说加载布局、绑定事件等。
# onStart()这个方法在活动由不可见变为可见的时候调用。
# onResume()这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于 返回栈的栈顶,并且处于运行状态。
# onPause()这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在 这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行 速度一定要快,不然会影响到新的栈顶活动的使用。
onStop()这个方法在活动完全不可见的时候调用。它和0nPauseO方法的主要区别在 于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而 onStopO方法并不会执行。
onDestroy()0这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
onRestart()0这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了


以上7个方法中除了 onRestart()方法,其他都是两两相对的,从而又可以将活动分为3 种生存期。
完整生存期。活动在onCreate()方法和onDestroy()方法之间所经历的,就是完整生存 期。一般情况下,一个活动会在onCreate()方法中完成各种初始化操作,而在 onDestroy()方法中完成释放内存的操作。

可见生存期。活动在onStart()方法和onStop()方法之间所经历的,就是可见生存期。 在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可 以通过这两个方法,合理地管理那些对用户可见的资源。比如在onStart()方法中对资 源进行加载,而在onStopO方法中对资源进行释放,从而保证处于停止状态的活动不会 占用过多内存。
前台生存期。活动在onResume()方法和onPause()方法之间所经历的就是前台生存期。 在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行交互的,我 们平时看到和接触最多的也就是这个状态下的活动。

在这里插入图片描述

activity_nomal.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".NomalActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is a normal activity"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".DialogActivity">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="318dp"
        android:text="This is a dialog activity"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button1"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.467"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="397dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button1"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.467"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="397dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
DialogActivity.java
package com.yxj.activitylifecycletest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class DialogActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dialog);
    }
}
NomalActivity.java
package com.yxj.activitylifecycletest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class NomalActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_nomal);
    }
}
MainActivity.java
package com.yxj.activitylifecycletest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: ");
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button1);
        Button button2 = (Button) findViewById(R.id.button2);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this, NomalActivity.class);
                startActivity(intent);
            }
        });
        
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this, DialogActivity.class);
                startActivity(intent);
            }
        });
        
    }

    @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 onDestroy() {
    
    
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

    @Override
    protected void onRestart() {
    
    
        super.onRestart();
        Log.d(TAG, "onRestart: ");
    }
}
当MainActivity第一次被创建时会依次执行onCreate()、onStart()和 onResume()方法。

在这里插入图片描述

由于NormalActivity已经把MainActivity完全遮挡住,因此onPause ()和onStop ()方法都 会得到执行

在这里插入图片描述

按下Back键返回MainActivity,打印信息如图
由于之前MainActivity已经进入了停止状态,所以onRestart()方法会得到执行,之后又会 依次执行onStart ()和onResume()方法。注意此时onC reate ()方法不会执行,因为MainActivity 并没有重新创建

在这里插入图片描述

再点击第二个按钮,启动DialogActivity
可以看到,只有onPause()方法得到了执行,onStopO方法并没有执行,这是因为 DialogActivity并没有完全遮挡住Main Activity,此时MainActivity只是进入了暂停状态,并没有 进入停止状态。

在这里插入图片描述
在这里插入图片描述

相应地,按下Back键返回MainActivity也应该只有onResume()方法会得到执行, 如图

在这里插入图片描述

最后在MainActivity按下Back键退出程序,打印信息如图
依次会执行 onPause(). onStop()和 onDestroy()方法,最终销毁 MainActivityo

在这里插入图片描述

10.4 活动被回收了怎么办

一个活动进入到了停止状态,是有可能被系统回收的。那么想象以下 场景:应用中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了停止状态, 这个时候由于系统内存不足,将活动A回收掉了,然后用户按下Back键返回活动A,会出现什 么情况呢?其实还是会正常显示活动A的,只不过这时并不会执行。nRestartO方法,而是会 执行活动A的onCreate()方法,因为活动A在这种情况下会被重新创建一次。
活动A中是可能存在临时数据和 状态的。打个比方,MainActivity中有一个文本输入框,现在你输入了一段文字,然后启动 NormalActivity,这时MainActivity由于系统内存不足被回收掉,过了一会你又点击了 Back键回 到MainActivity,你会发现刚刚输入的文字全部都没了,因为MainActivity被重新创建了。
查阅文档可以看出,Activity中还提供了一个onSaveInstanceState()回调方法,这个方 法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临 时数据得不到保存的问题。
onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方 法用于保存数据,比如可以使用putStringO方法保存字符串,使用putlnt()方法保存整型数 据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值, 第二个参数是真正要保存的内容。

package com.yxj.activitylifecycletest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: ");
        setContentView(R.layout.activity_main);

        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this, NomalActivity.class);
                startActivity(intent);
            }
        });

        if (savedInstanceState != null){
    
    
            String tempData = savedInstanceState.getString("data_key");
            Log.d(TAG, tempData);
        }


    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
    
    
        super.onSaveInstanceState(outState, outPersistentState);
        String tempData = "Something you just typed";
        outState.putString("data_key", tempData);

    }
}

10.5 活动的启动模式

10.5.1 standard

standard是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种 启动模式
standard模式(即默认情况)下,每当启 动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动, 系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity" android:label="third">
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="secondActivity">
            <intent-filter>
                <action android:name="com.yxj.helloworld.ACTION_START" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.yxj.helloworld.MY_CATEGORY" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="This is  MainActivity" android:launchMode="standard">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MainActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;


import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this,MainActivity.class);
               startActivity(intent);

            }
        });
    }


}
输出结果
2020-11-12 14:16:18.879 22276-22276/com.yxj.helloworld D/MainActivity: com.yxj.helloworld.MainActivity@2e59483
2020-11-12 14:16:55.332 22276-22276/com.yxj.helloworld D/MainActivity: com.yxj.helloworld.MainActivity@dce02c0
2020-11-12 14:18:41.069 22276-22276/com.yxj.helloworld D/MainActivity: com.yxj.helloworld.MainActivity@c951013
从打印信息中我们就可以看出,每点击一次按钮就会创建岀一个新的FirstActivity实例。此 时返回栈中也会存在3个FirstActivity的实例,因此你需要连按3次Back键才能退出程序。
standard模式的原理示意图

在这里插入图片描述

10.5.2 singleTop

使用singleTop模式。当活动的启动模式指定为 singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再 创建新的活动实例。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity" android:label="third">
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="secondActivity">
            <intent-filter>
                <action android:name="com.yxj.helloworld.ACTION_START" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.yxj.helloworld.MY_CATEGORY" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="This is  MainActivity" android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MainActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;


import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this,MainActivity.class);
                startActivity(intent);

            }
        });
    }


}
每当想要再启动一个MainActivity时都会直接使用栈顶的活动,因此MainActivity也只会有一个实例,仅按一次Back键就可以退岀程序
输出结果
2020-11-12 14:44:23.034 23380-23380/com.yxj.helloworld D/MainActivity: com.yxj.helloworld.MainActivity@2e59483
不过当FirstActivity并未处于栈顶位置时,这时再启动FirstActivity,还是会创建新的实例的。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity" android:label="third">
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="secondActivity">
            <intent-filter>
                <action android:name="com.yxj.helloworld.ACTION_START" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.yxj.helloworld.MY_CATEGORY" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="This is  MainActivity" android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MainActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;


import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);

            }
        });
    }


}
SecondActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class SecondActivity extends AppCompatActivity {
    
    
    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());
        setContentView(R.layout.activity_second);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(SecondActivity.this,MainActivity.class);
                startActivity(intent);

            }
        });

    }
}
可以看到系统创建了两个不同的FirstActivity实例,这是由于在SecondActivity中再次启动 FirstActivity时,栈顶活动已经变成了 SecondActivity,因此会创建一个新的FirstActivity实例。 现在按下Back键会返回到SecondActivity,再次按下Back键又会回到FirstActivity,再按一次 
Back键才会退出程序。
输出结果
2020-11-12 14:58:18.075 24211-24211/com.yxj.helloworld D/MainActivity: com.yxj.helloworld.MainActivity@2e59483
2020-11-12 14:58:29.321 24211-24211/com.yxj.helloworld D/SecondActivity: com.yxj.helloworld.SecondActivity@60ddbf9
2020-11-12 14:58:46.951 24211-24211/com.yxj.helloworld D/MainActivity: com.yxj.helloworld.MainActivity@e4edbe4
singleTop模式的原理示意图

在这里插入图片描述

10.5.3 singleTask

当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否 存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统 出栈,如果没有发现就会创建一个新的活动实例。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity" android:label="third">
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="secondActivity">
            <intent-filter>
                <action android:name="com.yxj.helloworld.ACTION_START" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.yxj.helloworld.MY_CATEGORY" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="This is  MainActivity" android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MainActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;


import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);

            }
        });
    }


    @Override
    protected void onRestart() {
    
    
        super.onRestart();
        Log.d(TAG, "onRestart: ");
    }
}
SecondActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class SecondActivity extends AppCompatActivity {
    
    
    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, this.toString());
        setContentView(R.layout.activity_second);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(SecondActivity.this,MainActivity.class);
                startActivity(intent);

            }
        });

    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }
}
输出结果
2020-11-12 15:10:45.788 24445-24445/com.yxj.helloworld D/MainActivity: com.yxj.helloworld.MainActivity@2e59483
2020-11-12 15:11:35.879 24445-24445/com.yxj.helloworld D/SecondActivity: com.yxj.helloworld.SecondActivity@60ddbf9
2020-11-12 15:12:07.632 24445-24445/com.yxj.helloworld D/MainActivity: onRestart: 
2020-11-12 15:12:08.810 24445-24445/com.yxj.helloworld D/SecondActivity: onDestroy: 
其实从打印信息中就可以明显看出了,在SecondActivity中启动FirstActivity时,会发现返 回栈中已经存在一个FirstActivity的实例,并且是在SecondActivity的下面,于是SecondActivity 会从返回栈中出栈,而FirstActivity重新成为了栈顶活动,因此FirstActivity的onRestart ()方 法和SecondActivity的onDestroy()方法会得到执行。现在返回栈中应该只剩下一个FirstActivity 的实例了,按一下Back键就可以退出程序。
singleTask模式的原理示意图

在这里插入图片描述

10.5.4 singlelnstance

指定为singlelnstance模式的活动会启用一个新的返 回栈来管理这个活动.
想象以下场景,假设我们的程序中有一个活动是允许其他程 序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢? 使用前面3种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在 不同的返回栈中入栈时必然是创建了新的实例。而使用singlelnstance模式就可以解决这个问题, 在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都 共用的同一个返回栈,也就解决了共享活动实例的问题。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity" android:label="third">
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="secondActivity"
            android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="com.yxj.helloworld.ACTION_START" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.yxj.helloworld.MY_CATEGORY" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="This is  MainActivity" android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MainActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;


import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class MainActivity extends AppCompatActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);

            }
        });
    }


    @Override
    protected void onRestart() {
    
    
        super.onRestart();
        Log.d(TAG, "onRestart: ");
    }
}
SecondActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class SecondActivity extends AppCompatActivity {
    
    
    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_second);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
                startActivity(intent);

            }
        });

    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }
}
ThirdActivity.java
package com.yxj.helloworld;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;

public class ThirdActivity extends AppCompatActivity {
    
    
    private static final String TAG = "ThirdActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_third);
    }
}
输出结果
2020-11-12 15:25:08.322 24718-24718/com.yxj.helloworld D/MainActivity: Task id is 100
2020-11-12 15:25:51.485 24718-24718/com.yxj.helloworld D/SecondActivity: Task id is 101
2020-11-12 15:25:54.987 24718-24718/com.yxj.helloworld D/ThirdActivity: Task id is 100
可以看到,SecondActivity 的 Task id 不同于 MainActivity 和 ThirdActivity,这说明 SecondActivity确实是存放在一个单独的返回栈里的,而且这个栈中只有SecondActivity这一个 活动。
然后我们按下Back键进行返回,你会发现ThirdActivity竟然直接返回到了 MainActivity, 再按下Back键又会返回到SecondActivity,再按下Back键才会退出程序,这是为什么呢?其实 原理很简单,由于MainActivity和ThirdActivity是存放在同一个返回栈里的,当在ThirdActivity 的界面按下Back键,MainActivity会从返回栈中岀栈,那么MainActivity就成为了栈顶活动显 示在界面上,因此也就出现了从ThirdActivity直接返回到MainActivity的情况。然后在MainActivity界面再次按下Back键,这时当前的返回栈已经空了,于是就显示了另一个返回栈 的栈顶活动,即SecondActivityo最后再次按下Back键,这时所有返回栈都已经空了,也就自 然退出了程序。
singlelnstance模式的原理示意图

在这里插入图片描述

10.5.5 知晓当前是在哪一个活动

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.helloworld">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity" android:label="third">
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="secondActivity"
            android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="com.yxj.helloworld.ACTION_START" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="com.yxj.helloworld.MY_CATEGORY" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="This is  MainActivity" android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
BaseActivity
package com.yxj.helloworld;


import android.os.Bundle;
import android.os.PersistableBundle;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    
    
    private static final String TAG = "BaseActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, getClass().getSimpleName());
    }
}

MainActivity.java
package com.yxj.helloworld;



import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class MainActivity extends BaseActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);

            }
        });
    }


    @Override
    protected void onRestart() {
    
    
        super.onRestart();
        Log.d(TAG, "onRestart: ");
    }
}
  • SecondActivity.java
package com.yxj.helloworld;


import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class SecondActivity extends BaseActivity{
    
    
    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_second);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
                startActivity(intent);

            }
        });

    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }
}
ThirdActivity.java
package com.yxj.helloworld;


import android.os.Bundle;
import android.util.Log;

public class ThirdActivity extends BaseActivity {
    
    
    private static final String TAG = "ThirdActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_third);
    }
}
输出结果
2020-11-12 15:49:06.610 25471-25471/com.yxj.helloworld D/BaseActivity: MainActivity
2020-11-12 15:49:06.622 25471-25471/com.yxj.helloworld D/MainActivity: Task id is 105
2020-11-12 15:49:40.505 25471-25471/com.yxj.helloworld D/BaseActivity: SecondActivity
2020-11-12 15:49:40.505 25471-25471/com.yxj.helloworld D/SecondActivity: Task id is 106
2020-11-12 15:49:43.736 25471-25471/com.yxj.helloworld D/BaseActivity: ThirdActivity
2020-11-12 15:49:43.742 25471-25471/com.yxj.helloworld D/ThirdActivity: Task id is 105

10.5.6 随时随地退出程序

如果目前你手机的界面还停留在ThirdActivity,你会发现当前想退出程序是非常不方便的, 需要连按3次Back键才行。按Home键只是把程序挂起,并没有退出程序。其实这个问题就足 以引起你的思考,如果我们的程序需要一个注销或者退出的功能该怎么办呢?必须要有一个随时 随地都能退出程序的方案才行。
其实解决思路也很简单,只需要用一个专门的集合类对所有的活动进行管理就可以了,下面 我们就来实现一下。
ActivityCollector.java
package com.yxj.helloworld;

import android.app.Activity;

import java.util.ArrayList;
import java.util.List;

public class ActivityCollector {
    
    

    public  static  List<Activity> activities = new ArrayList<>();
    public static void addActivity(Activity activity){
    
    
        activities.add(activity);
    }

    public static void removeActivity(Activity activity){
    
    
        activities.remove(activity);
    }

    public static void  finishAll(){
    
    
        for (Activity activity : activities) {
    
    
            if (!activity.isFinishing()) {
    
    
                activity.finish();
            }
        }

        activities.clear();
    }
}

BaseActivity.java
package com.yxj.helloworld;


        import android.app.Activity;
        import android.os.Bundle;
        import android.os.PersistableBundle;
        import android.util.Log;

        import androidx.annotation.Nullable;
        import androidx.appcompat.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    
    
    private static final String TAG = "BaseActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, getClass().getSimpleName());
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        ActivityCollector.removeActivity(this);

    }
}

ThirdActivity.java
package com.yxj.helloworld;


import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class ThirdActivity extends BaseActivity {
    
    
    private static final String TAG = "ThirdActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_third);
        Button button3 = (Button) findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                ActivityCollector.finishAll();
// 可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退 出,杀掉进程的代码如下所示:
其中,killProcessO方法用于杀掉一个进程,它接收一个进程id参数,我们可以通过 myPid()方法来获得当前程序的进程ido需要注意的是,killProcessO方法只能用于杀掉当前 程序的进程,我们不能使用这个方法去杀掉其他程序。
                android.os.Process.killProcess(android.os.Process.myPid());
            }
        });

    }
}
在 BaseActivity 的 onCreate()方法中调用了 ActivityCollector 的 addActivityO方 法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity中重写onDestroyO 方法,并调用了 ActivityCollector的removeActivity()方法,表明将一个马上要销毁的活 动从活动管理器里移除。
从此以后,不管你想在什么地方退出程序,只需要调用ActivityCollector. finishAll () 方法就可以了。

10.5.7 启动活动的最佳写法

,SecondActivity所需要的数据在方法 参数中全部体现出来了,这样即使不用阅读SecondActivity中的代码,不去询问负责编写 SecondActivity的同事,你也可以非常清晰地知道启动SecondActivity需要传递哪些数据。另外, 这样写还简化了启动活动的代码,现在只需要一行代码就可以启动SecondActivity,如下所示:
SecondActivity.java
package com.yxj.helloworld;


import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class SecondActivity extends BaseActivity{
    
    
    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_second);
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent(SecondActivity.this,ThirdActivity.class);
                startActivity(intent);

            }
        });

    }

    public static void actionStart(Context context, String data1, String data2){
    
    
        Intent intent = new Intent(context, SecondActivity.class);
        intent.putExtra("param1", data1);
        intent.putExtra("param2", data2);
        context.startActivity(intent);

    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }
}
MainActivity
 
package com.yxj.helloworld;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class MainActivity extends BaseActivity {
    
    
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        Log.d(TAG, "Task id is " + getTaskId());
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_1);
        button1.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                SecondActivity.actionStart(MainActivity.this, "data1", "data2");

            }
        });
    }


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

11.常用控件

11.1 TextView

用android:layout width和 android: layout height指定了控件的宽度和高度。Android中所有的控件都具有这两个属性, 可选值有 3 种:match parentA fill parent 和 wrap contento 其中 match parent 和 fill parent的意义相同,现在官方更加推荐使用match parento match parent表示让当前 控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小。wrap_content表示 让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。所以上 面的代码就表示让TextView的宽度和父布局一样宽,也就是手机屏幕的宽度,让TextView的高 度足够包含住里面的内容就行。当然除了使用上述值,你也可以对控件的宽和高指定一个固定的 大小,但是这样做有时会在不同手机屏幕的适配方面岀现问题。
TextView中的文字默认是居左上角对齐的
android:gravity来指定文字的对齐方式,可选值有top、bottom、left、right、 center等,可以用来同时指定多个值,这里我们指定的center,效果等同于center_ vertical | center horizontal,表示文字在垂直和水平方向都居中对齐
and roid: textsize属性可以指定文字的大小,通过and roid: textColo r属性可以指
定文字的颜色,在Android中字体大小使用sp作为单位。

11.2 Button

统会对Button中的所有英文字母自动进行大写转换,如果这不是 你想要的效果,可以使用如下配置来禁用这一默认特性:android:textAllCaps="false"

11.3 EditText

android:hint="Type something here" 显示一些提示性的文 字,然后一旦用户输入了任何内容,这些提示性的文字就会消失。
android:maxLines属性控制行数
MainActivity.java

package com.yxj.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    
    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        button.setOnClickListener(this);

    }


    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()){
    
    
            case R.id.button:
                String inputText = editText.getText().toString();
                Toast.makeText(MainActivity.this,inputText, Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }
}
首先通过findViewById()方法得到EditText的实例,然后在按钮的点击事件里调用EditText 的getText()方法获取到输入的内容,再调用toString()方法转换成字符串,最后还是老方法, 使用Toast将输入的内容显示出来。

在这里插入图片描述

11.4 ImageView

ImageView是用于在界面上展示图片的一个控件,它可以让我们的程序界面变得更加丰富多 彩。学习这个控件需要提前准备好一些图片,图片通常都是放在以“drawable”开头的目录下的。 目前我们的项目中有一个空的drawable目录,不过由于这个目录没有指定具体的分辨率,所以一 般不使用它来放置图片。这里我们在res目录下新建一个drawable-xhdpi目录,然后将事先准备 好的两张图片img l.png和img_2.png复制到该目录当中
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:textAllCaps="false"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.21" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.34" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.14637482" />

    <EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type something here"
        android:maxLines="2"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2"
        app:layout_constraintVertical_bias="0.506"></EditText>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.6128591" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/img_1"
        tools:layout_editor_absoluteX="121dp"
        tools:layout_editor_absoluteY="488dp" />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java

package com.yxj.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    
    private EditText editText;
    private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        imageView = (ImageView)findViewById(R.id.imageView);
        button.setOnClickListener(this);

    }


    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()){
    
    
            case R.id.button:
                imageView.setImageResource(R.drawable.img_2);
                break;
            default:
                break;
        }
    }
}

11.5 ProgressBar

 Android控件的可见属性。所有的Android控件都具有这个属性,可以通过android: visibility 进行指定,可选值有3种:visible, invisible和gone。visible表示控件是可见的,这个 值是默认值,不指定android:visibility时,控件都是可见的。invisible表示控件不可见, 但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。gone则表示控件不仅 不可见,而且不再占用任何屏幕空间。我们还可以通过代码来设置控件的可见性,使用的是 setvisibility()方法,可以传入 View. VISIBLE. View. INVISIBLE 和 View.GONE 这 3 种值。
 android:max属性给进度条设置一个最大值
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:textAllCaps="false"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.21" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.34" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.14637482" />

    <EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type something here"
        android:maxLines="2"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2"
        app:layout_constraintVertical_bias="0.506"></EditText>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.6128591" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/guideline6"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline4"
        app:srcCompat="@drawable/img_1" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.73" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView"
        android:max="100"/>

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.yxj.uiwidgettest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    
    private EditText editText;
    private ImageView imageView;
    private ProgressBar progressBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        imageView = (ImageView)findViewById(R.id.imageView);
         progressBar = (ProgressBar) findViewById(R.id.progressBar);
        button.setOnClickListener(this);

    }


    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()){
    
    
            case R.id.button:
                int progress = progressBar.getProgress();
                progress += 10;
                progressBar.setProgress(progress);
                break;
            default:
                break;
        }
    }
}

11.6 AlertDialog

AlertDialog 一般都是用于提示一些非常重要的内容或者 警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:textAllCaps="false"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.21" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.34" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.14637482" />

    <EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type something here"
        android:maxLines="2"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2"
        app:layout_constraintVertical_bias="0.506"></EditText>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.6128591" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/guideline6"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline4"
        app:srcCompat="@drawable/img_1" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.73" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        app:layout_constraintBottom_toTopOf="@+id/guideline7"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView"
        app:layout_constraintVertical_bias="0.653" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="611dp" />




</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.yxj.uiwidgettest;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    
    private EditText editText;
    private ImageView imageView;
    private ProgressBar progressBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        imageView = (ImageView)findViewById(R.id.imageView);
         progressBar = (ProgressBar) findViewById(R.id.progressBar);
        button.setOnClickListener(this);

    }


    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()){
    
    
            case R.id.button:
            // 首先通过AlertDialog.Builder创建一个AlertDialog的实例,然后可以为这个对话框设置标题、 内容、可否取消等属性,接下来调用setPositiveButton()方法为对话框设置确定按钮的点击 事件,调用setNegativeButton ()方法设置取消按钮的点击事件,最后调用show()方法将对话 框显示出来
                AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
                dialog.setTitle("This is a Dialog");
                dialog.setMessage("Something important");
                dialog.setCancelable(true);
                dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
    
    
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
    
    

                    }
                });

                dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
    
    
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
    
    

                    }
                });
                dialog.show();
                break;
            default:
                break;

        }
    }
}

在这里插入图片描述

11.7 ProgressDialog

rogressDialog和AlertDialog有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其 他控件的交互能力。不同的是,ProgressDialog会在对话框中显示一个进度条,一般用于表示当 前操作比较耗时,让用户耐心地等待。它的用法和AlertDialog也比较相似
MainActivity.java
package com.yxj.uiwidgettest;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    
    private EditText editText;
    private ImageView imageView;
    private ProgressBar progressBar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        editText = (EditText) findViewById(R.id.edit_text);
        imageView = (ImageView)findViewById(R.id.imageView);
         progressBar = (ProgressBar) findViewById(R.id.progressBar);
        button.setOnClickListener(this);

    }


    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()){
    
    
            case R.id.button:
                ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
                progressDialog.setTitle("This is ProgressDialog");
                progressDialog.setMessage("Loading...");
                progressDialog.setCancelable(true);
                progressDialog.show();
                break;
            default:
                break;

        }
    }
}

在这里插入图片描述

12 4种基本布局

12.1 线性布局

LinearLayout又称作线性布局,是一种非常常用的布局。正如它的名字所描述的一样,这个 布局会将它所包含的控件在线性方向上依次排列.
android:orientation属性指定了排列方向是vertical,如果指定的是 horizontal,控件就会在水平方向上排列了
如果不指定android:orientation属性的值,默认的排列方向 就是horizontal
这里需要注意,如果LinearLayout的排列方向是horizontal,内部的控件就绝对不能将宽度 指定为match_parent,因为这样的话,单独一个控件就会将整个水平方向占满,其他的控件就 没有可放置的位置了。同样的道理,如果LinearLayout的排列方向是vertical,内部的控件就不能 将高度指定为match_parent

android:gravity 用于指定文字在控件中的对齐方式,而android: layout_gravity用于指定控件在布局中的对 齐方式。android:layout_gravity的可选值和android:gravity差不多,但是需要注意,当 LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方 向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上 的对齐方式。同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方 式才会生效。
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button1"
            android:layout_gravity="top"/>

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button2"
            android:layout_gravity="center_vertical"/>

        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button3"
            android:layout_gravity="bottom"/>




    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

android:layout_weight
这个属性允许我们使用比例的方式来指定控件的大小,它在手机屏幕的适配性方面可以起到非常重要 的作用。比如我们正在编写一个消息发送界面,需要一个文本编辑框和一个发送按钮

dp是Android中用于指定控件大小、间距等属性的单位

这里我们仅指定了 EditText的android:layout_weight属性,并将Button的宽度改回 wrap_content()这表示Button的宽度仍然按照wrap_content来计算,而EditText则会占满屏 幕所有的剩余空间。使用这种方式编写的界面,不仅在各种屏幕的适配方面会非常好,而且看起 来也更加舒服。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <EditText
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:id="@+id/input_message"
            android:layout_weight="1"
            android:hint="Type something"></EditText>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Send">

        </Button>


    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

12.2 相对布局

每个控件都是相对于父布局进行定位的
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:text="Button1" />


        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
           android:layout_alignParentRight="true"
            android:layout_alignParentTop="true"
            android:text="Button2" />


        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
           android:layout_centerInParent="true"
            android:text="Button" />

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentLeft="true"
            android:text="Button4" />

        <Button
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:text="Button" />


    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

控件可以相对于控件进行定位
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="Button 3" />


        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/button3"
            android:layout_toLeftOf="@+id/button3"
            android:text="Button 1" />



        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
           android:layout_above="@+id/button3"
            android:layout_toRightOf="@+id/button3"

            android:text="Button2" />

        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/button3"
            android:layout_toLeftOf="@+id/button3"
            android:text="Button 4" />

        <Button
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
           android:layout_below="@+id/button3"
            android:layout_toRightOf="@+id/button3"
            android:text="Button" />
        

    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

12.3 帧布局

FrameLayout又称作帧布局,它相比于前面两种布局就简单太多了,因此它的应用场景也少 了很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角。

FrameLayout又称作帧布局,它相比于前面两种布局就简单太多了,因此它的应用场景也少 了很多。这种布局没有方便的定位方式,所有的控件都会默认摆放在布局的左上角。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <TextView
            android:id="@+id/text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="This is TextView"></TextView>
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image_view"
            android:src="@mipmap/ic_launcher"></ImageView>
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

我们指定TextView在FrameLayout中居左对齐,指定ImageView在FrameLayout中居右对齐
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <TextView
            android:id="@+id/text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="This is TextView"
            android:layout_gravity="left"></TextView>
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image_view"
            android:src="@mipmap/ic_launcher"
            android:layout_gravity="right"></ImageView>
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

12.4 百分比布局

由于LinearLayout本身已经支持按比例指定控件的大小了,因此百分比布局只为FrameLayout 和 RelativeLayout 进行了功能扩展,提供了 PercentFrameLayout 和 PercentRelativeLayout 这两个全新的布局
不同于前3种布局,百分比布局属于新增布局,那么怎么才能做到让新增布局在所有Android 版本上都能使用呢?为此,Android团队将百分比布局定义在了 support库当中,我们只需要在项 目的build.gradle中添加百分比布局库的依赖,就能保证百分比布局在Android所有系统版本上的 兼容性了。
打开app/build.gradle文件,在dependencies闭包中添加如下内容:
build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.yxj.uilayouttest"
        minSdkVersion 14
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

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

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.percentlayout:percentlayout:1.0.0'   // 需要加入这段代码支持百分比布局
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}
activity main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <androidx.percentlayout.widget.PercentFrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/button1"
            android:text="Button 1"
            android:layout_gravity="left|top"
            app:layout_widthPercent="50%"
            app:layout_heightPercent="50%">

        </Button>


        <Button
            android:id="@+id/button2"
            android:text="Button 2"
            android:layout_gravity="right|top"
            app:layout_widthPercent="50%"
            app:layout_heightPercent="50%">

        </Button>

        <Button
            android:id="@+id/button3"
            android:text="Button 3"
            android:layout_gravity="left|bottom"
            app:layout_widthPercent="50%"
            app:layout_heightPercent="50%">

        </Button>


        <Button
            android:id="@+id/button4"
            android:text="Button 4"
            android:layout_gravity="right|bottom"
            app:layout_widthPercent="50%"
            app:layout_heightPercent="50%">

        </Button>


    </androidx.percentlayout.widget.PercentFrameLayout>


</androidx.constraintlayout.widget.ConstraintLayout>
最外层我们使用了 PercentFrameLayout,由于百分比布局并不是内置在系统SDK当中的,所 以需要把完整的包路径写出来。然后还必须定义一个app的命名空间,这样才能使用百分比布局 的自定义属性。
在PercentFrameLayout中我们定义了 4个按钮,使用app: layout_widthPercent属性将各按钮的宽度指定为布局的50%,使用app:layout_heightPercent属性将各按钮的高度指定为 布局的50%。这里之所以能使用app前缀的属性就是因为刚才定义 app的命名空间,当然我们 一直能使用android前缀的属性也是同样的道理。
不过PercentFrameLayout还是会继承FrameLayout的特性,即所有的控件默认都是摆放在布 局的左上角。那么为了让这4个按钮不会重叠,这里还是借助了 layout_gravity来分别将这4 个按钮放置在布局的左上、右上、左下、右下4个位置。

PercentFrameLayout的用法就介绍到这里,另外一个PercentRelativeLayout的用法也是非 常相似的,它继承了 RelativeLayout中的所有属性,并且可以使用app: layout_widthPercent 和app:layout_heightPercent来按百分比指定控件的宽高.

在这里插入图片描述

12.5.引入布局

一般我们的程序中可能有很多个活动都需要这样的标题栏,如果在每个活动的布局中都编 写一遍同样的标题栏代码,明显就会导致代码的大量重复。这个时候我们就可以使用引入布局的 方式来解决这个问题

只需要通过一行include语句将标题栏布局引入进来就可以了
title.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/title_bg">


    <Button
        android:id="@+id/title_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff"/>

    <TextView
        android:id="@+id/title_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp"/>


    <Button
        android:id="@+id/title_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff"></Button>
</LinearLayout>

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <include layout="@layout/title"></include>

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.yxj.uicustomviews;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
     // 这里我们调用了 getSupportActionBar()方法来获得ActionBar的实例,然后再调用 ActionBar的hide()方法将标题栏隐藏起来
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
    
    
            actionBar.hide();
        }
    }
}

在这里插入图片描述

12.6 创建自定义控件

引入布局的技巧确实解决了重复编写布局代码的问题,但是如果布局中有一些控件要求能够 响应事件,我们还是需要在每个活动中为这些控件单独编写一次事件注册的代码。比如说标题栏 中的返回按钮,其实不管是在哪一个活动中,这个按钮的功能都是相同的,即销毁当前活动。而 如果在每一个活动中都需要重新注册一遍返回按钮的点击事件,无疑会增加很多重复代码,这种 情况最好是使用自定义控件的方式来解决。
TitleLayout.java
package com.yxj.uicustomviews;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;

import androidx.annotation.Nullable;


public class TitleLayout extends LinearLayout {
    
    


    public TitleLayout(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
// 首先我们重写了 LinearLayout中带有两个参数的构造函数,在布局中引入TitleLayout控件就 会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助 Layoutlnflater来实现了。通过 Layoutlnflater 的 from()方法可以构建出一个 Layoutlnflater 对 象,然后调用inflate()方法就可以动态加载一个布局文件,inflate方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layout.title,第二个参数是给加载好的布局 再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this.
        LayoutInflater.from(context).inflate(R.layout.title,this);
        Button titleBack = (Button) findViewById(R.id.title_back);
        Button titleEdit = (Button) findViewById(R.id.title_edit);
        titleBack.setOnClickListener(new OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                ((Activity)getContext()).finish();
            }
        });

        titleEdit.setOnClickListener(new OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Toast.makeText(getContext(),"You clicked Edit button",Toast.LENGTH_SHORT).show();
            }
        });

    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <com.yxj.uicustomviews.TitleLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </com.yxj.uicustomviews.TitleLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
title.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/title_bg">


    <Button
        android:id="@+id/title_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/back_bg"
        android:text="Back"
        android:textColor="#fff"/>

    <TextView
        android:id="@+id/title_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="Title Text"
        android:textColor="#fff"
        android:textSize="24sp"/>


    <Button
        android:id="@+id/title_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="5dp"
        android:background="@drawable/edit_bg"
        android:text="Edit"
        android:textColor="#fff"></Button>
</LinearLayout>

在这里插入图片描述

12.7 ListView

12.7.1 ListView的简单用法

ListView允许用户通过手指上下滑动的方 式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">


    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.yxj.listviewtest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {
    
    

    private String[] data = {
    
    "Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango",
    "Apple","Banana","Orange","Watermelon","Pear","Grape","Pineapple","Strawberry","Cherry","Mango"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 这里由于我们提供的数据都是字符串,因此将 ArrayAdapter的泛型指定为String,然后在ArrayAdaptei■的构造函数中依次传入当前上下文、 ListView子项布局的id,以及要适配的数据。注意,我们使用了 android.R. layout. simple_ list item l作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个 TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。最后,还需要调用ListView的setAdapter()方法,将构建好的适配器对象传递进去,这样 ListView和数据之间的关联就建立完成了
        ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, data);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}

在这里插入图片描述

12.7.2 定制ListView的界面

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />

</androidx.constraintlayout.widget.ConstraintLayout>
定义一个实体类,作为ListView适配器的适配类型
Fruit
package com.yxj.listviewtest;

public class Fruit {
    
    
    private String name;
    private int imageId;

    public Fruit(String name, int imageId) {
    
    
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public int getImageId() {
    
    
        return imageId;
    }

    public void setImageId(int imageId) {
    
    
        this.imageId = imageId;
    }
}

需要为ListView的子项指定一个我们自定义的布局,在layout目录下新建fruitJtem.xml
在这个布局中,我们定义了一个ImageView用于显示水果的图片,又定义了一个TextView 用于显示水果的名称,并让TextView在垂直方向上居中显示。
fruitItem.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"></ImageView>

        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="10dp"></TextView>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为 Fruit类。新建类FruitAdapter
FruitAdapter.jav
package com.yxj.listviewtest;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;

// FruitAdapter重写了父类的一组构造函数,用于将上下文、ListView子项布局的id和数据 都传递进来。另外又重写了 getViewO方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()方法中,首先通过getltem()方法得到当前项的Fruit实例,然后使用 Layoutlnflater来为这个子项加载我们传入的布局。
//接下来调用View的findViewById()方法分别获取到ImageView和 TextView的实例,并分别调用它们的setImageResource()和setText ()方法来设置显示的图 片和文字,最后将布局返回,这样我们自定义的适配器就完成了。
//这里Layoutlnflater的inflate。方法接收3个参数,前两个参数我们已经知道是什么 意思了,第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不为 这个View添加父布局,因为一旦View有了父布局之后,它就不能再添加到ListView中了。
public class FruitAdapter extends ArrayAdapter<Fruit> {
    
    
    private int resourceId;
    public FruitAdapter(Context context, int textViewResouceId,List<Fruit> objects){
    
    
        super(context,textViewResouceId,objects);
        resourceId = textViewResouceId;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    
    
        Fruit fruit = getItem(position);
        View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
        ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
        TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
        fruitImage.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());
        return view;
    }
}

修改MainActivity中的代码
MainActivity.java
package com.yxj.listviewtest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    
    

    private List<Fruit> fruitList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruites();
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }

// 可以看到,这里添加了一个initFruits()方法,用于初始化所有的水果数据。在Fruit类 的构造函数中将水果的名字和对应的图片id传入,然后把创建好的对象添加到水果列表中。另 外我们使用了一个for循环将所有的水果数据添加了两遍,这是因为如果只添加一遍的话,数据量还不足以充满整个屏幕。接着在onCreate()方法中创建了 FruitAdapter对象,并将FruitAdapter 作为适配器传递给ListView,这样定制ListView界面的任务就完成了。
    private void initFruites(){
    
    
        for (int i = 0; i < 2; i++) {
    
    
            Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange= new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);

        }
    }
}

12.7.3 提升ListView的运行效率

之所以说ListView这个控件很难用,就是因为它有很多细节可以优化,其中运行效率就是很 重要的一点。目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方 法中,每次都将布局重新加载了一遍,当ListView快速滚动的时候,这就会成为性能的瓶颈。
仔细观察会发现,getView()方法中还有一个convertView参数,这个参数用于将之前加 载好的布局进行缓存,以便之后可以进行重用。修改FruitAdapter中的代码

可以看到,现在我们在getView()方法中进行了判断,如果convertView==null,则使用 Layoutlnflater去加载布局,如果不为null则直接对convertView进行重用。这样就大大提 高了 ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。
不过,目前我们的这份代码还是可以继续优化的,虽然现在已经不会再重复去加载布局,但 是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例。 我们可以借助一个ViewHolder来对这部分性能进行优化

我们新增了一个内部类ViewHolder,用于对控件的实例进行缓存。当convertView == null 的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View 的setTag()方法,将ViewHolder对象存储在View中。当convertView不为null的时候, 则调用View的getTagO方法,把ViewHolder重新取出。这样所有控件的实例都缓存在了 ViewHolder里,就没有必要每次都通过findViewByld()方法来获取控件实例了。
通过这两步优化之后,我们ListView的运行效率就已经非常不错了。
FruitAdapter.java
package com.yxj.listviewtest;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;

public class FruitAdapter extends ArrayAdapter<Fruit> {
    
    
    private int resourceId;
    public FruitAdapter(Context context, int textViewResouceId,List<Fruit> objects){
    
    
        super(context,textViewResouceId,objects);
        resourceId = textViewResouceId;
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    
    
        Fruit fruit = getItem(position);
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
    
    
            view =  LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
            viewHolder = new ViewHolder();
            viewHolder.fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            viewHolder.fruitName = (TextView)view.findViewById(R.id.fruit_name);
            view.setTag(viewHolder);  // 将ViewHolder存储在View中
        }else {
    
    
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();  // 重新获取ViewHolder
        }
        viewHolder.fruitImage.setImageResource(fruit.getImageId());
        viewHolder.fruitName.setText(fruit.getName());
        return view;
    }

    class ViewHolder {
    
    
        ImageView fruitImage;
        TextView fruitName;
    }
}

12.7.4 ListView的点击事件

ListView的滚动毕竟只是满足了我们视觉上的效果,可是如果ListView中的子项 不能点击的话,这个控件就没有什么实际的用途了。因此,本小节我们就来学习一下ListView如 何才能响应用户的点击事件。

可以看到,我们使用setOnItemClickListener()方法为ListView注册了一个监听器,当 用户点击了 ListView中的任何一个子项时,就会回调onItemClick()方法。在这个方法中可以 通过position参数判断出用户点击的是哪一个子项,然后获取到相应的水果,并通过Toast将 水果的名字显示出来
MainActivity.java
package com.yxj.listviewtest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    
    

    private List<Fruit> fruitList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruites();
        FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    
    
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    
    
                Fruit fruit = fruitList.get(position);
                Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void initFruites(){
    
    
        for (int i = 0; i < 2; i++) {
    
    
            Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange= new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);

        }
    }
}

在这里插入图片描述

12.8 RecyclerView

在布局中加入RecyclerView控件也是非常简单的,先为RecyclerView指定一个id,然后将 宽度和高度都设置为match_parent,这样RecyclerView也就占满了整个布局的空间。需要注意 的是,由于RecyclerView并不是内置在系统SDK当中的,所以需要把完整的包路径写岀来。 
这里我们想要使用RecyclerView来实现和ListView相同的效果,因此就需要准备一份同样 的水果图片。简单起见,我们就直接从ListViewTest项目中把图片复制过来就可以了,另外顺便 将Fruit类和fruit_item.xml也复制过来,省得将同样的代码再写一遍。
FruitAdapter.java
package com.yxj.recyclerview;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

// 虽然这段代码看上去好像有点长,但其实它比ListView的适配器要更容易理解。这里我们首 先定义了一个内部类 ViewHolder, ViewHolder 要继承自 RecyclerView.ViewHolder.然后 ViewHolder的构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewByld()方法来获取到布局中的ImageView和TextView 的实例了。
//接着往下看,FruitAdapter中也有一个构造函数,这个方法用于把要展示的数据源传进来, 并赋值给一个全局变量mFruitList,我们后续的操作都将在这个数据源的基础上进行。
//继续往下看,由于FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写 onCreateViewHolder()、onBindViewHolder()和 getItemCount()这 3 个方法。onCreateViewHolder()方法是用于创建ViewHolder实例的,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载出的布局传入到构造函数当中,最后将 ViewHolder的实例返回。onBindViewHolder()方法是用于对RecyclerView子项的数据进行赋值 的,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit 实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。getltemCount () 方法就非常简单了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    
    
    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {
    
    
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(View view) {
    
    
            super(view);
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView)view.findViewById(R.id.fruit_name);
        }
    }

    public FruitAdapter(List<Fruit> fruitList) {
    
    
        mFruitList = fruitList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return  holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    
    
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());

    }

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

可以看到,这里使用了一个同样的initFruitsO方法,用于初始化所有的水果数据。接着在onCreate()方法中我们先获取到RecyclerView的实例,然后创建了一个LinearLayout- Manager对象,并将它设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局 方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。 接下来我们创建了 FruitAdapter的实例,并将水果数据传入到FruitAdapter的构造函数中, 最后调用RecyclerView的setAdapter()方法来完成适配器设置,这样RecyclerView和数据之间 的关联就建立完成了

在这里插入图片描述

12.8.1 实现横向滚动和瀑布流布局

12.8.1.1 实现横向滚动
fruit_item.xml
// 可以看到,我们将LinearLayout改成垂直方向排列,并把宽度设为1OOdp。这里将宽度指定 为固定值是因为每种水果的文字长度不一致,如果用wrap_content的话,RecyclerView的子项 就会有长有短,非常不美观;而如果用match_parent的话,就会导致宽度过长,一个子项占满 整个屏幕。然后我们将ImageView和TextView都设置成了在布局中水平居中,并且使用layout.marginTop属性让文字和图片之间保持一些距离。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="100dp"
        android:layout_height="wrap_content">
        

        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"></ImageView>

        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="10dp"></TextView>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
接下来修改MainActivity中的代码
package com.yxj.recyclerview;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    
    

    private List<Fruit> fruitList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruites();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
 // MainActivity 中只加入了一行代码,调用 LinearLayoutManager 的 setOrientation ()方法来 设置布局的排列方向,默认是纵向排列的,我们传入LinearLayoutManager .HORIZONTAL表示 让布局横行排列,这样RecyclerView就可以横向滚动了。     layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruites(){
    
    
        for (int i = 0; i < 2; i++) {
    
    
            Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange= new Fruit("Orange", R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }
}

在这里插入图片描述

12.8.1.2 实现横向滚动和瀑布流布局
fruit_item.xml
// 这里做了几处小的调整,首先将LinearLayout的宽度由1OOdp改成了 match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值。另外我们使用了 layout_margin属性来让子项之间互留一点间距,这样就不至于所有子项都紧贴在一些。还有 就是将TextView的对齐属性改成了居左对齐,因为待会我们会将文字的长度变长,如果还是居中显示就会感觉怪怪的。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        >

        <ImageView
            android:id="@+id/fruit_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"></ImageView>

        <TextView
            android:id="@+id/fruit_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"
            android:layout_marginTop="10dp"></TextView>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package com.yxj.recyclerview;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import android.os.Bundle;
import android.provider.FontsContract;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {
    
    

    private List<Fruit> fruitList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFruites();
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    }

    private void initFruites(){
    
    
        for (int i = 0; i < 2; i++) {
    
    
            Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange= new Fruit(getRandomLengthName("Orange"), R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(getRandomLengthName("Pear"), R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(getRandomLengthName("Grape"), R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(getRandomLengthName("Mango"), R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

    private String getRandomLengthName(String name){
    
    
        Random random = new Random();
        int length = random.nextInt(20)+1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
    
    
            builder.append(name);
        }

        return builder.toString();

    }
}
首先,在onCreate()方法中,我们创建了一个StaggeredGridLayoutManager的实例。 StaggeredGridLayoutManager的构造函数接收两个参数,第一个参数用于指定布局的列数, 传入3表示会把布局分为3列;第二个参数用于指定布局的排列方向,传入StaggeredGridLayoutManager. VERTICAL表示会让布局纵向排列,最后再把创建好的实例设置到RecyclerView 当中就可以了,就是这么简单!
没错,仅仅修改了一行代码,我们就已经成功实现瀑布流布局的效果了。不过由于瀑布流布 局需要各个子项的高度不一致才能看出明显的效果,为此我又使用了一个小技巧。这里我们把眼 光聚焦在getRandomLengthName()这个方法上,这个方法使用了 Random对象来创造一个1到 20之间的随机数,然后将参数中传入的字符串重复随机遍。在initFruitsO方法中,每个水果 的名字都改成调用getRandomLengthName ()这个方法来生成,这样就能保证各水果名字的长短
差距都比较大,子项的高度也就各不相同了。  

在这里插入图片描述

12.8.1.3 RecyclerView 的点击事件
FruitAdapter.java
package com.yxj.recyclerview;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

import java.util.List;

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    
    

    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {
    
    
        View fruitView;
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(View view) {
    
    
            super(view);
            fruitView = view;
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView)view.findViewById(R.id.fruit_name);
        }
    }

    public FruitAdapter(List<Fruit> fruitList) {
    
    
        mFruitList = fruitList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        final ViewHolder holder = new ViewHolder(view);
        holder.fruitView.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "you clicked view" + fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });

        holder.fruitImage.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                int position = holder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(),"you clicked image" +fruit.getName(),Toast.LENGTH_SHORT).show();

            }
        });
        return  holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    
    
        Fruit fruit = mFruitList.get(position);
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());

    }

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

我们先是修改了 ViewHolder,在ViewHolder中添加了 fruitView变量来保存子项最外层 布局的实例,然后在onCreateViewHolder()方法中注册点击事件就可以了。这里分别为最外层 布局和ImageView都注册了点击事件,RecyclerView的强大之处也在这里,它可以轻松实现子项 中任意控件或布局的点击事件。我们在两个点击事件中先获取了用户点击的position,然后通过 position拿到相应的Fruit实例,再使用Toast分别弹出两种不同的内容以示区别。 
然后再点击菠萝的文字部分,由于TextView并没有注册点击事件,因此点击文字这个事件 会然后再点击菠萝的文字部分,由于TextView并没有注册点击事件,因此点击文字这个事件 会被子项的最外层布局捕获到

在这里插入图片描述

12.8.1.4 编写界面的最佳实践
编写主界面,修改activity main.xml中的代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#f00">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/msg_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="#0f0"
            />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">\

        <EditText
            android:id="@+id/input_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Type something here"
            android:maxLines="2">

        </EditText>

            <Button
                android:id="@+id/send"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Send">

            </Button>
        </LinearLayout>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
定义消息的实体类,新建Msg
Msg.java
package com.yxj.chat;

public class Msg {
    
    
    public static final int TYPE_RECEIVED = 0;
    public static final int TYPE_SENT = 1;
    private String content;
    private int type;

    public Msg(String content, int type) {
    
    
        this.content = content;
        this.type = type;
    }

    public String getContent() {
    
    
        return content;
    }

    public void setContent(String content) {
    
    
        this.content = content;
    }

    public int getType() {
    
    
        return type;
    }

    public void setType(int type) {
    
    
        this.type = type;
    }
}

编写RecyclerView子项的布局,新建msg_item.xml
msg_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="wrap_content">


    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
       >
        <LinearLayout
            android:id="@+id/left_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="left"
            android:background="@drawable/message_left">

            <TextView
                android:id="@+id/left_msg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_margin="10dp"
                android:textColor="#fff">

            </TextView>

        </LinearLayout>

        <LinearLayout
            android:id="@+id/right_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:background="@drawable/message_right">

            <TextView
                android:id="@+id/right_msg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_margin="10dp">

            </TextView>
        </LinearLayout>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
MsgAdapter.java
package com.yxj.chat;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder> {
    
    

    private List<Msg> mMsgList;


    static class ViewHolder extends  RecyclerView.ViewHolder{
    
    
        LinearLayout leftLayout;
        LinearLayout rightLaout;
        TextView leftMsg;
        TextView rightMsg;


        public ViewHolder(@NonNull View view) {
    
    
            super(view);
            leftLayout = (LinearLayout) view.findViewById(R.id.left_layout);
            rightLaout = (LinearLayout)view.findViewById(R.id.right_layout);
            leftMsg = (TextView) view.findViewById(R.id.left_msg);
            rightMsg = (TextView) view.findViewById(R.id.right_msg);
        }
    }

    public MsgAdapter(List<Msg> msgList){
    
    
        mMsgList = msgList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    
    
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item, parent, false);
        return new ViewHolder(view);
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
    
    
         Msg msg = mMsgList.get(position);
         if (msg.getType() == Msg.TYPE_RECEIVED){
    
    
             holder.leftLayout.setVisibility(View.VISIBLE);
             holder.rightLaout.setVisibility(View.GONE);
             holder.leftMsg.setText(msg.getContent());
         } else if (msg.getType() == Msg.TYPE_SENT) {
    
    
             holder.rightLaout.setVisibility(View.VISIBLE);
             holder.leftLayout.setVisibility(View.GONE);
             holder.rightMsg.setText(msg.getContent());
         }
    }

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

}

MainActivity.java
在initMsgs()方法中我们先初始化了几条数据用于在RecyclerView中显示。然后在发送按 钮的点击事件里获取了 EditText中的内容,如果内容不为null则创建出一个新的Msg对象,并 把它添加到msgList列表中去。之后又调用了适配器的notifyltemlnserted()方法,用于通知 列表有新的数据插入,这样新增的一条消息才能够在RecyclerView中显示。接着调用 RecyclerView的scrollToPosition()方法将显示的数据定位到最后一行,以保证一定可以看得 到最后发出的一条消息。最后调用EditText的setText ()方法将输入的内容清空。
package com.yxj.chat;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    
    
     private List<Msg> msgList = new ArrayList<>();
     private EditText inputText;
     private Button send;
     private RecyclerView msgRecyclerView;
     private MsgAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initMsgs();
        inputText = (EditText) findViewById(R.id.input_text);
        send = (Button) findViewById(R.id.send);
        msgRecyclerView = (RecyclerView)findViewById(R.id.msg_recycler_view);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        msgRecyclerView.setLayoutManager(linearLayoutManager);
        adapter = new MsgAdapter(msgList);
        msgRecyclerView.setAdapter(adapter);
        send.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                String content = inputText.getText().toString();
                if (!"".equals(content)) {
    
    
                    Msg msg = new Msg(content,Msg.TYPE_SENT);
                    msgList.add(msg);
                    adapter.notifyItemInserted(msgList.size() - 1);  // 当有新消息时,刷新RecyclerView中的显示
                    msgRecyclerView.scrollToPosition(msgList.size() - 1); // 将RecyclerView定位到最后一行
                    inputText.setText(""); //清空输入框中的内容
                }
            }
        });


    }
    
    private void initMsgs(){
    
    
        Msg msg1 = new Msg("Hello guy", Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("Hello,Who is that", Msg.TYPE_SENT);
        msgList.add(msg2);
        Msg msg3 = new Msg("This is Tom. Nice talking to you.", Msg.TYPE_RECEIVED);
        msgList.add(msg3);

    }
}

在这里插入图片描述

13 广播机制

13.1 标准广播

标准广播(Normal broadcasts )是一种完全异步执行的广播,在广播发出之后,所有的广 播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可 言。这种广播的效率会比较高,但同时也意味着它是无法被截断的

在这里插入图片描述

13.2 有序广播

有序广播(Ordered broadcasts )则是一种同步执行的广播,在广播发出之后,同一时刻只 会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后, 广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就 可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的 广播接收器就无法收到广播消息了

在这里插入图片描述

13.3 接收系统广播

Android内置了很多系统级别的广播,我们可以在应用程序中通过监听这些广播来得到各种 系统的状态信息。比如手机开机完成后会发出一条广播,电池的电量发生变化会发出一条广播, 时间或时区发生改变也会发出一条广播,等等。如果想要接收到这些广播,就需要使用广播接收 器,下面我们就来看一下它的具体用法

13.3.1 动态注册监听网络变化

广播接收器可以自由地对自己感兴趣的广播进行注册,这样当有相应的广播发岀时,广播接 收器就能够收到该广播,并在内部处理相应的逻辑。注册广播的方式一般有两种,在代码中注册 和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册。
那么该如何创建一个广播接收器呢?其实只需要新建一个类,让它继承自BroadcastReceiver, 并重写父类的onReceiveO方法就行了。这样当有广播到来时,onReceive( )方法 就会得到执行,具体的逻辑就可以在这个方法中处理。
MainActivity.java
package com.yxj.broadcasttest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.widget.Toast;

// 可以看到,我们在MainActivity中定义了一个内部类NetworkChangeReceiver,这个类是继承自BroadcastReceiver的,并重写了父类的onReceive()方法。这样每当网络状态发生变化时onReceive ()方法就会得到执行,这里只是简单地使用Toast提示了一段文本信息。然后观察onCreate()方法,首先我们创建了一个IntentFilter的实例,并给它添加了一 个值为android.net.conn.CONNECTIVITY_CHANGE的action,为什么要添加这个值呢?因为当 网络状态发生变化时,系统发出的正是一条值为android.net.conn.CONNECTIVITY_CHANGE的 广播,也就是说我们的广播接收器想要监听什么广播,就在这里添加相应的action接下来创建 了一个NetworkChangeReceiver的实例,然后调用registerReceiver()方法进行注册,将 NetworkChangeReceiver 的实例和 IntentFilter 的实例都传了进去,这样NetworkChangeReceiver 就会收到所有值为android.net.conn.CONNECTIVITY_CHANGE的广播,也就实现了监听网络变化的功能。最后要记得,动态注册的广播接收器一定都要取消注册才行,这里我们是在onDestroyO 方法中通过调用unregisterReceiver()方法来实现的。

//整体来说,代码还是非常简单的,现在运行一下程序。首先你会在注册完成的时候收到一条广播,然后按下Home键回到主界面(注意不能按Back键,否则。onDestroyO方法会执行),接 着打开Settings程序Data usage进入到数据使用详情界面,然后尝试着开关Cellular data按钮来 启动和禁用网络,你就会看到有Toast提醒你网络发生了变化。


// 在 onReceive()方法中,首先通过 getSystemService()方法得到了 ConnectivityManager 的实例,这是一个系统服务类,专门用于管理网络连接的。然后调用它的getActiveNetworkInfo()方法可以得到Networkinfo的实例,接着调用Networkinfo的isAvailable()方法, 就可以判断出当前是否有网络了,最后我们还是通过Toast的方式对用户进行提示。另外,这里有非常重要的一点需要说明,Android系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中声明权 限才可以,否则程序将会直接崩溃。比如这里访问系统的网络状态就是需要声明权限的。打开AndroidManifest.xml文件,在里面加入如下权限就可以访问系统网络状态了 :<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
//现在重新运行程序,然后按下Home >Settings->Data usage,进入到数据使用详情界面, 关闭Cellular data会弹出无网络可用的提示
public class MainActivity extends AppCompatActivity {
    
    
    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver = new NetworkChangeReceiver();
        registerReceiver(networkChangeReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
        System.out.println("destroy1111");
    }

    class NetworkChangeReceiver extends BroadcastReceiver {
    
    

        @Override
        public void onReceive(Context context, Intent intent) {
    
    
            ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            if (networkInfo != null && networkInfo.isAvailable()){
    
    
                Toast.makeText(context,"network is available",Toast.LENGTH_SHORT).show();
            }else{
    
    
                Toast.makeText(context,"network is unavailable",Toast.LENGTH_SHORT).show();
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述

13.3.2 静态注册实现开机启动

动态注册的广播接收器可以自由地控制注册与注销,在灵活性方面有很大的优势,但是它也 存在着一个缺点,即必须要在程序启动之后才能接收到广播,因为注册的逻辑是写在onCreate() 方法中的。那么有没有什么办法可以让程序在未启动的情况下就能接收到广播呢?这就需要使用 静态注册的方式了。
这里我们准备让程序接收一条开机广播,当收到这条广播时就可以在onReceive()方法里 执行相应的逻辑,从而实现开机启动的功能。可以使用Android Studio 的快捷方式来创建一 个广播接收器,右击New Other Broadcast Receiver
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.broadcasttest">
// 由于Android系统启动完成后会发出一条值为android.permission.RECEIVE_BOOT_COMPLETED的广播,因此我们在<intent-filter>标签里添加了相应的action另外,监听系统开机广播也是需要声明权限的,可以看到,我们使用<uses-permission>标签又加入了一条android.permission.RECEIVE_BOOT_COMPLETED权限。
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

// Exported属性表示是否 允许这个广播接收器接收本程序以外的广播,Enabled属性表示是否启用这个广播接收器
需要注意的是, 不要在onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会报错。因此 广播接收器更多的是扮演一种打开程序其他组件的角色,比如创建一条状态栏通知,或者启动一个服务等
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>
    </application>

</manifest>

13.3.3 发送自定义广播

13.3.3.1 发送标准广播
MyBroadcastReceiver
package com.yxj.broadcasttest;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyBroadcastReceiver extends BroadcastReceiver {
    
    

    @Override
    public void onReceive(Context context, Intent intent) {
    
    
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
      Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
    }
}

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.broadcasttest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.yxj.broadcasttest.MY_BROADCAST"></action>
            </intent-filter>
        </receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
MainActivity.java
package com.yxj.broadcasttest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    
    
    private IntentFilter intentFilter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent("com.yxj.broadcasttest.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });
    }


}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send Broadcast">

    </Button>
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

13.3.3.2 发送有序广播
广播是一种可以跨进程的通信方式,这一点从前面接收系统广播的时候就可以看岀来了。因 此在我们应用程序内发出的广播,其他的应用程序应该也是可以收到的。为了验证这一点,我们 需要再新建一个 BroadcastTest2 项目,点击 Android Studio 导航栏一>File—>New—>New Project 进行 创建。
将项目创建好之后,还需要在这个项目下定义一个广播接收器,用于接收上一小节中的自定 义广播。新建AnotherBroadcastReceiver,代码如下所示:
AnotherBroadcastReceiver.java
package com.yxj.broadcasttest2;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class AnotherBroadcastReceiver extends BroadcastReceiver {
    
    

    @Override
    public void onReceive(Context context, Intent intent) {
    
    
        Toast.makeText(context,"received in AnotherBroadcastReceiver",Toast.LENGTH_SHORT).show();
    }
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.broadcasttest2">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.yxj.broadcasttest.MY_BROADCAST"></action>
            </intent-filter>
        </receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

在这里插入图片描述
在这里插入图片描述

不过到目前为止,程序里发出的都还是标准广播,现在我们来尝试一下发送有序广播
MainActivity.java
package com.yxj.broadcasttest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    
    
    private IntentFilter intentFilter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent("com.yxj.broadcasttest.MY_BROADCAST");
                sendOrderedBroadcast(intent,null);
            }
        });
    }
}
设定广播接收器的先后顺序
可以看到,我们通过android:priority属性给广播接收器设置了优先级,优先级比较高 的广播接收器就可以先收到广播。这里将MyBroadcastReceiver的优先级设成了 100,以保证它一 定会在AnotherBroadcastReceiver之前收到广播。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.broadcasttest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100">
                <action android:name="com.yxj.broadcasttest.MY_BROADCAST"></action>
            </intent-filter>
        </receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
既然已经获得了接收广播的优先权,那么MyBroadcastReceiver就可以选择是否允许广播继 续传递了。修改MyBroadcastReceiver中的代码,如下所示:
MyBroadcastReceiver.java
package com.yxj.broadcasttest;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyBroadcastReceiver extends BroadcastReceiver {
    
    

    @Override
    public void onReceive(Context context, Intent intent) {
    
    
     
      Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
      // 如果在onReceive()方法中调用了 abortBroadcast ()方法,就表示将这条广播截断,后面 
的广播接收器将无法再接收到这条广播。现在重新运行程序,并点击一下Send Broadcast按钮, 你会发现,只有MyBroadcastReceiver中的Toast信息能够弹出,说明这条广播经过MyBroadcast- Receiver之后确实是终止传递了。

      abortBroadcast();
    }
}

在这里插入图片描述

13.3.4 使用本地广播

面我们发送和接收的广播全部属于系统全局广播,即发出的广播可以被其他任何应用程序 接收到,并且我们也可以接收来自于其他任何应用程序的广播。这样就很容易引起安全性的问题, 比如说我们发送的一些携带关键性数据的广播有可能被其他的应用程序截获,或者其他的程序不 停地向我们的广播接收器里发送各种垃圾广播。
为了能够简单地解决广播的安全性问题,Android引入了一套本地广播机制,使用这个机制 发岀的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出 的广播,这样所有的安全性问题就都不存在了。
本地广播的用法并不复杂,主要就是使用了一个LocalBroadcastManager,来对广播进行管理, 并提供了发送广播和注册广播接收器的方法。
MainActivity.java
package com.yxj.broadcasttest;

import androidx.appcompat.app.AppCompatActivity;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;


// 首先是通过LocalBroadcastManager的getlnstance()方 法得到了它的一个实例,然后在注册广播接收器的时候调用的是LocalBroadcastManager的 registerReceiver ()方法,在发送广播的时候调用的是 LocalBroadcastManager 的 sendBroadcast () 方法,仅此而已。这里我们在按钮的点击事件里面发出了一条com. example .broadcasttest. LOCAL_BROADCAST广播,然后在LocalReceiver里去接收这条广播。重新运行程序,并点击Send Broadcast按钮
public class MainActivity extends AppCompatActivity {
    
    
    private IntentFilter intentFilter;

    private LocalReceiver localReceiver;

    private LocalBroadcastManager localBroadcastManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);  // 获取实例
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent("com.yxj.broadcasttest.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent); // 发送本地广播
            }
        });

        intentFilter = new IntentFilter();
        intentFilter.addAction("com.yxj.broadcasttest.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter);  // 注册本地广播监听器
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }

    class LocalReceiver extends BroadcastReceiver {
    
    

        @Override
        public void onReceive(Context context, Intent intent) {
    
    
            Toast.makeText(context,"received local broadcast",Toast.LENGTH_SHORT).show();
        }
    }


}

在这里插入图片描述

另外还有一点需要说明,本地广播是无法通过静态注册的方式来接收的。其实这也完全可以 理解,因为静态注册主要就是为了让程序在未启动的情况下也能收到广播,而发送本地广播时, 我们的程序肯定是已经启动了,因此也完全不需要使用静态注册的功能。
最后我们再来盘点一下使用本地广播的几点优势吧。
可以明确地知道正在发送的广播不会离开我们的程序,因此不必担心机密数据泄漏。
其他的程序无法将广播发送到我们程序的内部,因此不需要担心会有安全漏洞的隐患。
发送本地广播比发送系统全局广播将会更加高效。

13.3.5 使用本地广播广播的最佳实践一实现强制下线功能

强制下线功能需要先关闭掉所有的活动,然后回到登录界面。
先创建一个Activitycollector类用于管理所有的活动
ActivityCollector
package com.yxj.broadcastbestpractice;

import android.app.Activity;

import java.util.ArrayList;
import java.util.List;

public class ActivityCollector {
    
    
    public static List<Activity> activities = new ArrayList<>();

    public static void addActivity(Activity activity){
    
    
        activities.add(activity);
    }

    public static void removeActivity(Activity activity){
    
    
        activities.remove(activity);
    }

    public static void finishAll(){
    
    
        for (Activity activity : activities) {
    
    
            if (!activity.isFinishing()) {
    
    
                activity.finish();
            }
        }
        activities.clear();
    }
}

创建BaseActivity类作为所有活动的父类
BaseActivity
package com.yxj.broadcastbestpractice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    
    
    private ForceOfflineReceiver receiver;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onResume() {
    
    
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.yxj.broadcastbestpractice.FORCE_OFFLINE");
        receiver = new ForceOfflineReceiver();
        registerReceiver(receiver,intentFilter);
    }

    @Override
    protected void onPause() {
    
    
        super.onPause();
        if (receiver != null){
    
    
            unregisterReceiver(receiver);
            receiver = null;
        }
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

    class ForceOfflineReceiver extends BroadcastReceiver{
    
    

        @Override
        public void onReceive(final Context context, Intent intent) {
    
    
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("Warning");
            builder.setMessage("You are forced to be offline. Please try to login again");
            builder.setCancelable(false);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
    
    
                @Override
                public void onClick(DialogInterface dialog, int which) {
    
    
                    ActivityCollector.finishAll();
                    Intent i = new Intent(context, LoginActivity.class);
                    context.startActivity(i);

                }
            });
            builder.show();
        }
    }
}

LoginActivity
package com.yxj.broadcastbestpractice;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class LoginActivity extends BaseActivity {
    
    

    private EditText accountEdit;
    private EditText passwordEdit;
    private Button login;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        accountEdit = (EditText)findViewById(R.id.account);
        passwordEdit = (EditText)findViewById(R.id.password);
        login = (Button)findViewById(R.id.login);
        login.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();

                if (account.equals("admin") && password.equals("123456")){
    
    
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    startActivity(intent);
                    finish();
                }else {
    
    
                    Toast.makeText(LoginActivity.this,"account or password is invalid",Toast.LENGTH_SHORT).show();
                }


            }
        });
    }
}
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Account: ">

        </TextView>

        <EditText
            android:id="@+id/account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical">

        </EditText>
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Password: ">

        </TextView>

        <EditText
            android:id="@+id/password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword">

        </EditText>

    </LinearLayout>

    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="Login">

    </Button>
</LinearLayout>
这里我们并不需 要在主界面里提供什么花哨的功能,只需要加入强制下线功能就可以了,修改aCtivity_main.xml 中的代码
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/force_offline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send force offline broadcast">

    </Button>

</androidx.constraintlayout.widget.ConstraintLayout>
修改MainActivity中的代码
MainActivity
package com.yxj.broadcastbestpractice;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends BaseActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button forceOffline = (Button) findViewById(R.id.force_offline);
        forceOffline.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                Intent intent = new Intent("com.yxj.broadcastbestpractice.FORCE_OFFLINE");
                sendBroadcast(intent);
            }
        });

    }
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yxj.broadcastbestpractice">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"></activity>
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>

    </application>

</manifest>

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/unique_perfect/article/details/108923660
今日推荐