Android入门基础教程_android入门教程-程序员宅基地

技术标签: 学习  java  android  

第1章 Android Studio运行第一个程序

1.1 Android Studio下载:

1.1.1 Android开发者官网:

        https://developer.android.google.cn

1.1.2 下载Android Studio开发者工具:

  1. 进入Android开发者官网;
  2. 找到Android Studio工具下载页面;
  3. 点击“Download option”按钮选择与自己系统对应的版本(本人使用的是Windows版本);
  4. 同意“条款和条件”并点击下载;

1.2 Android Studio安装:

  1. 双击下载好的Android Studio工具;
  2. 直接点击“Next”到安装处点击“Install”安装;(中途有:欢迎安装界面-->选择上虚拟设备-->安装路径)
  3. 等待安装完成后点“Next”如下图所示表示安装完成;                                                               
  4. 运行Android Studio;(由于是第一次安装,没有配置文件,直接点击“OK”)
  5. 点击“Cancel”;                                                                                                                                                  
  6. 会弹出欢迎界面,提示会提醒是否将使用情况统计信息发送到“Google”;(单纯让谷歌知道多了一位Android开发者)
  7. 来到欢迎配置Android Studio开发环境,点击”Next“直接到License Agreement[许可协议];(【Install Type:安装类型】[Standard:标准]-->【Select Ul Theme:选择UI主题】-->【Verify Settings:验证配置】)
  8. 阅读并同意安装组件的许可协议,点击“Finish”;([License Agreement:许可协议]、[Accept:接受])
  9. 下载完成后,点击“Finish”;

1.3 Android Studio卸载:

  1. 卸载Android Studio软件;(默认安装路径:C:\Program Files\Android\Android Studio)
  2. 删除SDK文件夹;(默认路径:C:\用户\Administrator[用户名]\AppData\Local\Android\sdk)
  3. 删除相关文件夹;(默认路径:C:\用户\Administrator[用户名]\)
    删除.android 文件夹
    删除 .AndroidStudioX.X 文件夹
    删除.gradle 文件夹

具体详见:https://blog.csdn.net/weixin_45048331/article/details/111868109

1.4 第一个Android应用程序:

1.4.1 创建Android工程项目:

  1. 将Android Studio工具的快捷启动放到桌面;
  2. 点击“New Project”;
  3. 选择Phone and Table-->选择“Empty Activity”-->点击“Next”;
  4. 【Name:app的名字】-->【Package name:包名】-->【Save location:保存路径】-->【Language:语言】-->【点击“Finish”】;
  5. 然后等待下载完成Gradle配置工具;(某些用户可能会弹出防火墙的“安全中心警告”,点击“允许访问”)
  6. 打开“activity_main.xml”文件,视图效果和模拟器上运行的效果差不多

1.4.2 运行第一个Android应用程序:

  • 添加手机模拟器
  1. 点击下图右边第4个像手机一样的图标;
  2. 点击“Create virtual device”添加手机模拟器;
  3. 选上自己喜欢的设备,然后点击“Next”;(可点击“New Hardware Profile”自定义模拟设备)
  4. 点击“Download”下载模拟设备的系统镜像;
  5. 选择已下载的模拟设备系统镜像,点击“Next”;
  6. 给模拟设备命名,点击“”Finish;
  7. 点击“播放按钮”给模拟设备开机;                                                                                      
  8. 手机仿真器视图显示设置;                                                                                                             
  9. 点击运行按钮后的运行结果;                                                                                                     
  10. 编码方式与视图设计方式切换在编辑框的右上角,仿真器隐藏后在编辑框的右边栏最下方(【Code、Split、Design】、【Emulator:仿真器】);

1.4.3 仿真程序进程终止问题

1.5 Android工程结构介绍

修改工程结构显示方式,将Android修改成Project[以目录结构显示];为不影响显示效果此部分内容请使用PC端查看

├── .gradle                                                                                # AS自动生成的文件,AS会自动修改它的,项目打包时也会删除;
├── .idea                                                                                   # AS自动生成的文件,AS会自动修改它的,项目打包时也会删除;
├── app                                                                                     # 应用相关的东西都在里面,工作的核心目录 
│        ├── build                                                                         # 编译的产物。某些情况下,可以手动把它整个删掉。
│        ├── libs                                                                           # 依赖包可以放这里,比如一些jar文件。
│        ├── src                                                                            # 代码在这。非常重要。
│        │        ├── main
│        │        │        ├── java                                                      # 放Java代码的地方
│        │        │        ├── res 
│        │        │        │        ├── drawable                                    # 应用图标
│        │        │        │        ├── layout                                         # Android布局文件夹
│        │        │        │        ├── mipmap                                      # 桌面图标
│        │        │        │        ├── layout                                         # Android布局文件夹
│        │        │        │        └── values                                        # 颜色、样式、字符集配置文件夹
│        ├── .gitignore                                                                   # 版本控制
│        ├── build.gradle                                                               # 非常重要,app的构建配置。俗称“app的gradle文件”。
│        └── proguard-rules.pro                                                    # 先不管。这个是混淆配置。
├── gradle                                                                                  # 它是一个构建起配置文件
├── .gitignore                                                                             # 整个工程的版本控制
├── build.gradle                                                                         # 很重要。项目级的配置。俗称“项目gradle文件”。
├── gradle.properties                                                                 # 全局的gradle配置文件
├── gradlew                                                                                # Linux/mac上执行gradle命令
├── gradlew.bat                                                                          # Windows上执行gradle命令
├── local.properties                                                                    # 本地配置文件,一般不上传
└── settings.gradle                                                                     # gralde的项目级配置

第2章 Android的UI控件

注:控件的属性值可以【Ctrl+鼠标左键】进行查看

2.1 TextView(文本框)

本节前言:

dp(dip): device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGA和QVGA 推荐使用这个,不依赖像素。 

px: pixels(像素). 不同设备显示效果相同,一般我们HVGA代表320x480像素,这个用的比较多。 

pt: point,是一个标准的长度单位,1pt=1/72英寸,用于印刷业,非常简单易用; 

sp: scaled pixels(放大像素). 主要用于字体显示best for textsize。

2.1.1 基础属性详解: 

  • id:为TextView设置一个组件id,根据id,我们可以在Java代码中通过findViewById(R.id.tv_one)的方法获取到该对象,然后进行相关属性的设置,又或者使用RelativeLayout时,参考组件用的也是id!
  • layout_width:组件的宽度,一般写:**wrap_content**或者**match_parent(fill_parent)**,前者是控件显示的内容多大,控件就多大,而后者会填满该控件所在的父容器;当然也可以设置成特定的大小,比如我这里为了显示效果,设置成了200dp。
  • layout_height:组件的高度,内容同上。
  • gravity:设置控件中内容的对齐方向,TextView中是文字,ImageView中是图片等等。
  • text:设置显示的文本内容,一般我们是把字符串写到src/main/res/values/string.xml文件中,然后通过@String/xxx取得对应的字符串内容的,这里为了方便我直接就写到""里,不建议这样写!!!
  • textColor:设置字体颜色,同上,通过src/main/res/values/colors.xml资源来引用,然后通过@String/xxx进行引用,别直接这样写!
  • textStyle:设置字体风格,三个可选值:**normal**(无效果),**bold**(加粗),**italic**(斜体)
  • textSize:字体大小,单位一般是用sp!
  • background:控件的背景颜色,可以理解为填充整个控件的颜色,可以是图片哦!

XML代码:

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:gravity="center"
        android:text="Android学习"
        android:textColor="#FF000000"
        android:textSize="30sp"
        android:textStyle="italic"
        android:background="#FFFF0000" />

2.1.2 带阴影的TextView

  • android:shadowColor:设置阴影颜色,需要与shadowRadius一起使用哦!
  • android:shadowRadius:设置阴影的模糊程度,设为0.1就变成字体颜色了,建议使用3.0
  • android:shadowDx:设置阴影在水平方向的偏移,就是水平方向阴影开始的横坐标位置
  • android:shadowDy:设置阴影在竖直方向的偏移,就是竖直方向阴影开始的纵坐标位置

 XML代码:

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:gravity="center"

        android:shadowColor="@color/red"
        android:shadowRadius="3.0"
        android:shadowDx="10.0"
        android:shadowDy="10.0"

        android:text="@string/tv_one"
        android:textColor="#FF000000"
        android:textSize="30sp"
        android:textStyle="italic" />

2.1.3 实际开发例子——跑马灯效果

  • android:singleLine:内容单行显示
  • android:focusable:是否可以获取焦点
  • android:focusablelnTouchMode:用于控制视图在触摸模式下是否可以聚焦
  • android:ellipsize:在哪里省略文本
  • android:marqueeRepeatLimit:字幕动画重复的次数

更多详见:2.3.1 TextView(文本框)详解 | 菜鸟教程 (runoob.com)

XML代码: 

    <TextView
        android:id="@+id/tv_one"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:gravity="center"
        android:shadowColor="@color/red"
        android:shadowRadius="3.0"
        android:shadowDx="10.0"
        android:shadowDy="10.0"

        android:singleLine="true"
        android:ellipsize="marquee"
        android:marqueeRepeatLimit="marquee_forever"
        android:focusable="true"
        android:focusableInTouchMode="true"

        android:text="@string/tv_one"
        android:textColor="#FF000000"
        android:textSize="30sp"
        android:textStyle="italic" />

默认情况下没有效果:

  1. 开启可点击:android:clickable="true"
  2. 添加一个请求标签:
  3. 写一个类继承TextView实现其方法,并重写isFocused()方法返回true,然后将TextView标签给出自定义的全类名

2.2 Button(按钮)

本节前言:

Button是TextView的子类,所以TextView上很多属性也可以应用到Button 上!我们实际开发中对于Button的,无非是对按钮的几个状态做相应的操作,比如:按钮按下的时候 用一种颜色,弹起又一种颜色,或者按钮不可用的时候一种颜色这样!上述实现无非是通过 StateListDrawable这种Drawable资源来实现,即编写一个drawable的资源文件。

2.2.1 StateListDrawable介绍:

StateListDrawable是Drawable资源的一种,可以根据不同的状态,设置不同的图片效果,关键节点 < selector >,我们只需要将Button的background属性设置为该drawable资源即可轻松实现,按下 按钮时不同的按钮颜色或背景!

  • drawable:引用的Drawable位图,我们可以把他放到最前面,就表示组件的正常状态~
  • state_focused:是否获得焦点
  • state_window_focused:是否获得窗口焦点
  • state_enabled:控件是否可用
  • state_checkable:控件可否被勾选,eg:checkbox
  • state_checked:控件是否被勾选
  • state_selected:控件是否被选择,针对有滚轮的情况
  • state_pressed:控件是否被按下
  • state_active:控件是否处于活动状态,eg:slidingTab
  • state_single:控件包含多个子控件时,确定是否只显示一个子控件
  • state_first:控件包含多个子控件时,确定第一个子控件是否处于显示状态
  • state_middle:控件包含多个子控件时,确定中间一个子控件是否处于显示状态
  • state_last:控件包含多个子控件时,确定最后一个子控件是否处于显示状态

更多详见:3.3.3 Button(按钮)与ImageButton(图像按钮) | 菜鸟教程 (runoob.com)

  1.    在src/main/res/drawable中添加位矢图和new按钮选择器【btn_selecter.xml】
  2. 默认情况下新版本的AS设置背景颜色是无效果的,需要修改themes.xml文件
  3. 在src/main/res中new按钮的颜色选择器【color/btn_color_selecter.xml】

XML代码: 

    <Button
        android:text="点我有惊喜"
        android:textColor="#FFFF0000"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@drawable/btn_selecter"
        android:backgroundTint="@color/btn_color_selecter"
        android:layout_gravity="center" />

 运行效果图: 

 

2.2.2 常见的Button事件:

  • 单击事件:setOnClickListener()
  • 长按事件:setOnLongClickListener()
  • 触摸/移动事件:setOnTouchListener()

代码中TAG的值为“leo”

运行结果1(长按+移动):setOnLongClickListener()与setOnTouchListener()的返回值为false

 运行结果2(长按+移动):setOnLongClickListener()返回值为false,setOnTouchListener()的返回值为true,会将setOnLongClickListener()取代(即触摸/移动也会触发)

 运行结果3(长按+移动):setOnLongClickListener()返回值为true,setOnTouchListener()的返回值为false,setOnClickListener()不会执行

onClickListener()的另一种写法:在activity_main.xml的Button标签中写android:onClick="leoClick",再按住Alt+Enter创建leoClick方法,优先级低于setOnClickListener()

2.3 EditText(输入框)

2.3.1 主要属性

  • android:hint输入提示
  • android:textColorHint输入提示文字的颜色
  • android:inputType输入类型
  • android:drawableXxoxx在输入框的指定方位添加图片
  • android:drawablePadding设置图片与输入内容的间距
  • android:paddingXxxx设置内容与边框的间距
  • android:background背景色

更多详见:2.3.2 EditText(输入框)详解 | 菜鸟教程 (runoob.com)

 XML代码:

    <EditText
        android:id="@+id/username"
        android:hint="请输入用户名"
        android:textColorHint="#FF95A1AA"
        android:inputType="text"
        android:background="@drawable/edit_background"
        android:layout_margin="10dp"
        android:drawableLeft="@drawable/ic_baseline_person_24"
        android:drawablePadding="5dp"
        android:padding="5dp"
        android:layout_width="300dp"
        android:layout_height="50dp" />

    <EditText
        android:id="@+id/password"
        android:hint="请输入密码"
        android:textColorHint="#FF95A1AA"
        android:inputType="textPassword"
        android:background="@drawable/edit_background"
        android:layout_margin="10dp"
        android:drawableLeft="@drawable/ic_baseline_lock_24"
        android:drawablePadding="5dp"
        android:padding="5dp"
        android:layout_width="300dp"
        android:layout_height="50dp" />

    <EditText
        android:id="@+id/phone"
        android:hint="电话"
        android:textColorHint="#FF95A1AA"
        android:inputType="phone"
        android:background="@drawable/edit_background"
        android:layout_margin="10dp"
        android:drawableLeft="@drawable/ic_baseline_phone_24"
        android:drawablePadding="5dp"
        android:padding="5dp"
        android:layout_width="300dp"
        android:layout_height="50dp" />

    <Button
        android:id="@+id/submit_btn"
        android:text="提交"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

 运行效果图:

2.3.2 实际开发例子——用户登录

运行效果图:edit_background.xml是文本框

2.4 ImageView(图像视图)

2.4.1 主要属性

  • android:src设置图片资源
  • android:scaleType设置图片缩放类型
  • android:maxHeight最大高度
  • android:maxWidth最大宽度
  • android:adjustViewBounds是否调整View的界限

 XML代码:

    <ImageView
        android:src="@drawable/ceshi"
        android:layout_width="300dp"
        android:layout_height="300dp" />

    <ImageView
        android:src="@drawable/ceshi"
        android:maxHeight="300dp"
        android:maxWidth="300dp"
        android:adjustViewBounds="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

运行效果图:

2.4.2 scaleType的属性

  • fitStart:保持宽高比缩放图片,直到较长的边与lmage的边长相等缩放完成后将图片放在ImageView的左上角
  • fitCenter:默认值,同上,缩放后放于中间
  • fitEnd:同上,缩放后放于右下角
  • fitXY:对图像的横纵方向进行独立缩放,使得该图片完全适应lmageView,但是图片的宽高比可能会发生改变
  • center:保持原图的大小,显示在ImageView的中心。 当原图的size大于ImageView的size,超过部分裁剪处理。
  • centerCrop:保持宽高比缩放图片,直到完全覆盖ImageView,可能会出现图片的显示不完全
  • centerInside:保持宽高比缩放图片,直到ImageView能够完全地显示图片
  • matrix:不改变原图的大小,从ImageView的左上角开始绘制原图,原图超过ImageView的部分作裁剪处理

更多详见:2.3.4 ImageView(图像视图) | 菜鸟教程 (runoob.com)

2.5 ProgressBar(进度条)

 2.5.1 常用属性详解

  • android:max:进度条的最大值
  • android:progress:进度条已完成进度值
  • android:indeterminate:如果设置成true,则进度条不精确显示进度
  • style="?android:attr/progressBarStyleHorizontal"水平进度条

更多详见:ProgressBar(进度条) | 菜鸟教程 (runoob.com)

 XML代码:

    <ProgressBar
        android:id="@+id/pb1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:text="显示/隐藏进度条"
        android:onClick="leoClick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <!--水平进度条-->
    <ProgressBar
        android:id="@+id/pb2"
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"
        android:layout_width="350dp"
        android:layout_height="wrap_content" />
    <Button
        android:text="模拟下载按钮"
        android:onClick="loadClick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ProgressBar
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"
        android:indeterminate="true"
        android:layout_width="350dp"
        android:layout_height="wrap_content" />

 Java代码:

public class MainActivity extends AppCompatActivity {
    private ProgressBar pb1;
    private ProgressBar pb2;
    private Integer speed;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        pb1 = findViewById(R.id.pb1);
        pb2 = findViewById(R.id.pb2);
        speed = (int)(pb2.getMax() * 0.1);
    }

    public void leoClick(View view) {
        if (pb1.getVisibility() == View.GONE) {
            pb1.setVisibility(View.VISIBLE); //显示
        } else {
            pb1.setVisibility(View.GONE);
        }
    }

    public void loadClick(View view) {
        int progress = pb2.getProgress();
        pb2.setProgress(progress + speed);
    }
}

运行效果图:

2.6 Notification(状态栏通知)

2.6.1 Notification的基本使用流程

状态通知栏主要涉及到2个类:Notification 和NotificationManager

Notification:通知信息类,它里面对应了通知栏的各个属性

NotificationManager:是状态栏通知的管理类,负责发通知、清除通知等操作。

使用的基本流程:

  • Step 1. 获得NotificationManager对象: NotificationManager nManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  • Step 2. 创建一个通知栏的Builder构造类: Notification.Builder nBuilder = new Notification.Builder(this);
    在老的版本中是使用Notification()
    新的版本是使用Notification.Builder()
    为了兼容性现在使用NotificationCompat.Builder()
  • Step 3. 对Builder进行相关的设置,比如标题,内容,图标,动作等!
  • Step 4. 调用Builder的build()方法为notification赋值
  • Step 5. 调用NotificationManager的notify()方法发送通知!
  • PS:另外我们还可以调用NotificationManager的cancel()方法取消通知

2.6.2 NotificationChannel(通知渠道)

通知渠道,Android 8.0引入了通知渠道,其允许您为要显示的每种通知类型创建用户可自定义的渠道。
通知重要程度设置,NotificationManager类中

  • IMPORTANCE_NONE:关闭通知
  • IMPORTANCE_MIN:开启通知,不会弹出,但没有提示音,状态栏中无显示
  • IMPORTANCE_LOW:开启通知,不会弹出,不发出提示音,状态栏中显示
  • IMPORTANCE_DEFAULT(默认):开启通知,不会弹出,发出提示音,状态栏中显示
  • IMPORTANCE_HIGH:开启通知,会弹出,发出提示音,状态栏中显示

2.6.3 Notification的常见方法

  • setContentTitle(String string):设置标题
  • setContentText(String string):设置文本内容
  • setSmalllcon(int icon):设置小图标
  • setLargelcon(Bitmap icon):设置通知的大图标
  • setColor(int argb):设置小图标的颜色
  • setContentlntent(Pendinglntent intent):设置点击通知后的跳转意图
  • setAutoCancel(boolean boolean):设置点击通知后自动清除通知
  • setWhen(long when):设置通知被创建的时间

注意:Android从5.0系统开始,对于通知栏图标的设计进行了修改。现在Google要求,所有应用程序的通知栏图标,应该只使用alpha图层来进行绘制,而不应该包括RGB图层。

2.6.3 实际例子——模拟通知

 XML代码:

Java代码:

运行效果:

更多详见:2.5.8 Notification(状态栏通知)详解 | 菜鸟教程 (runoob.com)

2.7 Toolbar(工具栏目)

2.7.1 常用属性详解

取消系统默认的标题栏目:src/main/res/values/themes.xml --> style标签 --> parent="Theme.MaterialComponents.DayNight.NoActionBar"

  • android:layout_width="match_parent"
  • android:layout_height=""?attr/actionBarSize":使用的是ActionBar的高度
  • android:background="#FFFF00"
  • app:navigationlcon=" @drawable/ic_baseline_arrow_back_24""
  • app:title="主标题"
  • app:titleTextColor="#FF0000"
  • app:titleMarginStart="90dp":左间距
  • app:subtitle="子标题"
  • app:subtitleTextColor="#00FFFFF"
  • app:logo="@mipmap/ic_launcher"

 使用ToolBar替换系统默认的DarkActionBar:

XML代码:

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

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tb"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
        app:title="标题"
        app:titleTextColor="@color/red"
        app:titleMarginStart="90dp"
        android:background="@color/teal_200"/>

</LinearLayout>

Java代码: 

public class MainActivity extends AppCompatActivity {

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

        Toolbar toolbar = findViewById(R.id.tb); //androidx.appcompat.widget.Toolbar
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e("leo","ToolBar被点击了!");
            }
        });
    }
}

运行效果图: 

2.7.2 标题居中显示

代码及运行效果:

2.8 AlertDialog(对话框)

2.8.1 实现方式/使用流程

  • AlertDialog.Builder builder = new AlertDialog.Builder(context); 构建Dialog的各种参数
  • Builder.setlcon(int iconld); 添加ICON
  • Builder.setTitle(CharSequence title); 添加标题
  • Builder.setMessage(CharSequence message);添加消息
  • Builder.setView(View view); 设置自定义布局
  • setPositiveButton:确定按钮
  • setNegativeButton:取消按钮
  • setNeutralButton:中间按钮
  • Builder.create(); 创建Dialog
  • Builder.show(); 显示对话框

使用流程:

  • Step 1:创建AlertDialog.Builder对象;
  • Step 2:调用setIcon()设置图标,setTitle()setCustomTitle()设置标题;
  • Step 3:设置对话框的内容:setMessage()还有其他方法来指定显示的内容;
  • Step 4:调用setPositive/Negative/NeutralButton()设置:确定,取消,中立按钮;
  • Step 5:调用create()方法创建这个对象,再调用show()方法将对话框显示出来;

更多详见:2.5.9 AlertDialog(对话框)详解 | 菜鸟教程 (runoob.com)

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">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="leoClick"
        android:text="显示对话框"/>

</LinearLayout>

Java代码:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "leo";

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

    public void leoClick(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this); //androidx.appcompat.app.AlertDialog;
        builder.setIcon(R.mipmap.ic_launcher)
                .setTitle("对话框")
                .setMessage("今天天气怎么样?")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Log.e(TAG, "点击了确定按钮");
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Log.e(TAG, "点击了取消按钮");
                    }
                })
                .setNeutralButton("中间/其他", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Log.e(TAG, "点击了中间/其他按钮");
                    }
                })
                .create()
                .show(); //最后两个位置不能交换
    }
}

运行效果图1(无setView()):

 dialog_view.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:background="#66FFFF00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="今天天气很好!"
        android:textSize="18sp"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_baseline_tag_faces_24"
        app:tint="#00FF00" />
</LinearLayout>

 运行效果2:

2.9 PopupWindow(悬浮框)

2.9.1 常用方法

  • setContentView(View contentView):设置PopupWindow显示的view
  • showAsDropDown(View anchor):相对某个控件的位置〈正左下方),无偏移showAsDropDown(View anchor, int xoff, int yoff):相对某个控件的位置,有偏移
  • setFocusable(boolean focusable):设置是否获取焦点
  • setBackgroundDrawable(Drawable background):设置背景dismiss()关闭弹窗
  • setAnimationStyle(int animationStyle):设置加载动画
  • setTouchable(boolean touchable):设置触摸使能
  • setOutsideTouchable(boolean touchable):设置PopupWindow外面的触摸使能

更多详见:2.6.1 PopupWindow(悬浮框)的基本使用 | 菜鸟教程 (runoob.com)

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">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="弹出PopupWIndow"
        android:onClick="leoClick"/>

</LinearLayout>

popup_view.xml代码: 

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

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="上海"
        android:textSize="18sp"/>

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="北京"
        android:textSize="18sp"/>
</LinearLayout>

Java代码:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "leo";

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

    public void leoClick(View view) {
        View popupView = getLayoutInflater().inflate(R.layout.popup_view, null);
        //视图、宽、高、是否获取焦点(点击空白处取消)
        PopupWindow popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
        popupWindow.setBackgroundDrawable(getResources().getDrawable(R.drawable.ceshi)); //设置背景
        popupWindow.showAsDropDown(view, 50,0);
        Log.e(TAG, "leoClick: 111111");

        Button btn1 = popupView.findViewById(R.id.btn1);
        Button btn2 = popupView.findViewById(R.id.btn2);
        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e(TAG, "onClick: 按钮1被点击");
            }
        });
        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e(TAG, "onClick: 按钮2被点击");
            }
        });
    }
}

运行结果图:

ListView(列表条目)

使用步骤:

  • Step 1:在布局文件中添加一个LisetView
  • Step 2:新建一个条目布局文件,这里为list_item.xml,在此文件中写好每一个条目的布局内容
  • Step 3:创建item对应的JavaBean
  • Step 4:创建待填充的数据data集合并添加数据
  • Step 5:创建辅助类Adapter(适配器)需继承BaseAdapter并实现其方法,这里命名为MyAdapter,此类中包含适配的数据和Context。实现getView的时候,需要创建View并将其返回,然后需要在此方法中通过view的findById获取list_item中的组件并填充内容
  • Step 6: 创建并初始化MyAdpter适配器,然后获取布局中的ListView,通过set方法将创建好的MyAdpter适配器复值给ListView的Adapter方法就可以完成了

 inflate()方法解析:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
  • 第一个参数:要获取的布局文件,传入R.layout.xxx
  • 第二个参数:这个参数也是一个布局,是为第一个参数指定的父布局。
  • 第三个参数(如果第二个参数为null这个参数将失去作用)
    true:将第一个参数表示的布局添加到第二参数的布局中。
    false:不将第一个参数表示的布局添加到第二参数的布局中。
    既然不添加,那么为什么第二个参数不设置为null呢。
    不添加的话,这个函数就只剩下一个作用了,那就是获取布局,为了使第一个参数的宽高属性不失效,所以要为他指定一个父布局

更多详见:2.4.5 ListView简单实用 | 菜鸟教程 (runoob.com)

MyActivity.java:

public class MainActivity extends AppCompatActivity {

    private ArrayList<Bean> data = new ArrayList<>();

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

        for (int i = 0; i < 20; i++) {
            Bean bean = new Bean("享学"+i);
            data.add(bean);
        }

        ListView listView = findViewById(R.id.lv);
        listView.setAdapter(new MyAdapter(data, this));
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Log.e("TAG", "onItemClick: " + i);
            }
        });
    }
}

Bean.java:

package com.example.mylistview;

public class Bean {
    private String name;

    public Bean(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

MyAdapter.java:

public class MyAdapter extends BaseAdapter {
    private ArrayList<Bean> data;
    private Context context;

    public MyAdapter(ArrayList<Bean> data, Context context) {
        this.data = data;
        this.context = context;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int i) {
        return data.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        if (view == null) {
            view = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
        }
        TextView tv = view.findViewById(R.id.tv);
        tv.setText(data.get(i).getName());
        Log.e("TAG", "getView: " + i);
        return view;
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

list_item.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="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="享学0"
        android:textSize="30sp" />

</LinearLayout>

运行效果图: 

优化后的MyAdapter.java:即用viewHolder存储传过来的view,并为view绑定绑定上viewHolder对象

public class MyAdapter extends BaseAdapter {
    private ArrayList<Bean> data;
    private Context context;

    public MyAdapter(ArrayList<Bean> data, Context context) {
        this.data = data;
        this.context = context;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int i) {
        return data.get(i);
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if (view == null) {
            viewHolder = new ViewHolder();
            view = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
            viewHolder.textView = view.findViewById(R.id.tv);
            view.setTag(viewHolder);
        } else{
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.textView.setText(data.get(i).getName());
        Log.e("TAG", "getView: " + i);
        return view;
    }
    
    public final class ViewHolder{
        TextView textView;
    }
}

RecyclerView(大量数据集合的列表)

1 什么是RecycylerView:

RecycylerView是support-v7包中的新组件,是一个强大的滑动组件,是一个增强版的ListView,与经典的ListView相比,同样拥
不仅可以实现和ListView同样的效果,还优化了ListView中存在的各种不足之处,这一点从它的名字Recyclerview即回收view也可以看出。ResyslerView 能够实现横向滚动,这是ListView所不能实现的

2 如何使用:

Step 1:添加RecycylerView的依赖:

Step 2:创建布局:

  • activity_main.xml
  • item.xml

Step 3:创建适配器:

  1. 创建适配器类继承RecyclerView.Adapter<>类,传入泛型为RecyclerView.ViewHolder的子类(第2点)。
  2. 创建内部类即RecyclerView.ViewHolder类的子类,并为其绑定item控件。
  3. 实现RecyclerView.Adapter类的相关方法(快捷键为Alt+Enter)。
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position);//为MyViewHolder绑定上数据
    public int getItemCount();//返回数据的条目数

Step 4:在MainActivity.java中使用RecyclerView:

  1. 获取RecyclerView对象 。
  2. 准备适配的数据 。
  3. 适配器实例化 。
  4. 设置LayoutManager布局方式。
  5. 设置Adapter 。

 MyActivity.java:

public class MainActivity extends AppCompatActivity {

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

        ArrayList<Bean> data = new ArrayList<>();

        for (int i = 0; i < 20; i++) {
            Bean bean = new Bean("享学"+i);
            data.add(bean);
        }

        RecyclerView recyclerView = findViewById(R.id.rv);
        MyAdapter myAdapter = new MyAdapter(data, this);

        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(manager);

        recyclerView.setAdapter(myAdapter);
    }
}

Bean.java:

public class Bean {
    private String name;

    public Bean(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

MyAdapter.java:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private ArrayList<Bean> data;
    private Context context;

    public MyAdapter(ArrayList<Bean> data, Context context) {
        this.data = data;
        this.context = context;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = View.inflate(context, R.layout.recycler_item, null);
        MyViewHolder myViewHolder = new MyViewHolder(view);
        return myViewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.textView.setText(data.get(position).getName());
    }

    @Override
    public int getItemCount() {
        return data==null?0:data.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder{
        TextView textView;
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv);
        }
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

recycler_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="享学0"
        android:textSize="25dp"/>

</RelativeLayout>

运行效果图:

RecyclerView的点击事件

RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的api,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。实现方法有多种:

  • 可以监听RecyclerView的Touch事件然后判断手势做相应的处理
  • 也可以在通过在绑定ViewHolder的时候设置监听,然后通过Apater回调出去

这里选择第二种方法,更加直观和简单。

思路为:
通过在适配器中设置一个包含onItemClick函数的接口,然后添加setOnItemClickListener方法用于接收用户创建的该接口的实现类对象,最后在onBindViewHolder函数中调用onItemClick方法执行用于传入的对象的onItemClick方法。

设置步骤:

  • Step 1:创建OnItemClickListener接口
        public interface OnItemClickListener{
            void onItemClick(View view, int position);
            void onItemLongClick(View view, int position);
        }
  • Step 2:添加setOnItemClickListener方法()
        public void setOnItemClickListener(OnItemClickListener listenser) {
            this.onItemClickListener = listener;
        }
  • Step 3:在onBindViewHolder中调用实现类对象的onItemClick函数,也可在MyAdapter.MyViewHolder中调用实现类对象的onItemClick函数
        //第1种实现方法
        @Override
        public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
            holder.textView.setText(data.get(position).getName());
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(final View v) {
                    if(onItemClickListener != null) {
                        int pos = holder.getLayoutPosition();
                        onItemClickListener.onItemClick(holder.itemView, pos);
                    }
                }
            });
    
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    if(onItemClickListener != null) {
                        int pos = holder.getLayoutPosition();
                        onItemClickListener.onItemLongClick(holder.itemView, pos);
                    }
                    //表示此事件已经消费,不会触发单击事件
                    return true;
                }
            });
        }
        //第2种实现方法
        public class MyViewHolder extends RecyclerView.ViewHolder{
            TextView textView;
            public MyViewHolder(@NonNull View itemView) {
                super(itemView);
                textView = itemView.findViewById(R.id.tv);
                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if(onItemClickListener != null) {
                            onItemClickListener.onItemClick(itemView, getLayoutPosition());
                        }
                    }
                });
                itemView.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View view) {
                        if(onItemClickListener != null) {
                            onItemClickListener.onItemLongClick(itemView, getLayoutPosition());
                        }
                        return false;
                    }
                });
            }
        }

  • Step 5:调用setOnItemClickListener方法传入OnItemClickListener的实现类对象
            myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
                @Override
                public void onItemClick(View view, int position) {
                    Log.e("TAG", "onItemClick: " + position);
                }
    
                @Override
                public void onItemLongClick(View view, int position) {
                    Log.e("TAG", "onLongItemClick: " + position);
                }
            });

MyAdapter.java的完整代码:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private ArrayList<Bean> data;
    private Context context;

    public MyAdapter(ArrayList<Bean> data, Context context) {
        this.data = data;
        this.context = context;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = View.inflate(context, R.layout.recycler_item, null);
        MyViewHolder myViewHolder = new MyViewHolder(view);
        return myViewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.textView.setText(data.get(position).getName());
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemClick(holder.itemView, pos);
                }
            }
        });

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemLongClick(holder.itemView, pos);
                }
                //表示此事件已经消费,不会触发单击事件
                return true;
            }
        });
    }

    @Override
    public int getItemCount() {
        return data==null?0:data.size();
    }

    public interface OnItemClickListener{
        void onItemClick(View view, int position);
        void onItemLongClick(View view, int position);
    }
    private OnItemClickListener onItemClickListener;
    public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
        this.onItemClickListener = listener;
    }

    public class MyViewHolder extends RecyclerView.ViewHolder{
        TextView textView;
        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv);
//            itemView.setOnClickListener(new View.OnClickListener() {
//                @Override
//                public void onClick(View view) {
//                    if(onItemClickListener != null) {
//                        onItemClickListener.onItemClick(itemView, getLayoutPosition());
//                    }
//                }
//            });
//            itemView.setOnLongClickListener(new View.OnLongClickListener() {
//                @Override
//                public boolean onLongClick(View view) {
//                    if(onItemClickListener != null) {
//                        onItemClickListener.onItemLongClick(itemView, getLayoutPosition());
//                    }
//                    return false;
//                }
//            });
        }
    }
}

recycler_itme.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="享学0"
        android:textSize="25dp"/>

</RelativeLayout>

运行效果图:

第3章 Android的布局方式

3.1 LinearLayout(线性布局)

3.1.1 常用属性

  • orientation:布局中组件的排列方式
  • gravity:控制组件所包含的子元素的对齐方式,可多个组合
  • layout_gravity:控制该组件在父容器里的对其方式
  • background:为该组件设置一个背景图片,或者是直接用颜色覆盖
  • divider:分割线(可以使用View来设置分割线的效果)
  • showDividers:设置分割线所在的位置,none(无),beginning(开始),end(结束),middle(每两个组件间)
  • dividerPadding:设置分割线的padding
  • layout_weight(权重):该属性是用来按比例划分剩余的区域,按控件的对齐方式

更多详见:2.2.1 LinearLayout(线性布局) | 菜鸟教程 (runoob.com)

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:layout_width="300dp"
        android:layout_height="0dp"
        android:layout_weight="3"
        android:background="#FF0000"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000000"
        android:layout_marginVertical="10dp"/>

    <LinearLayout
        android:layout_width="300dp"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:background="#00FF00"/>

    <LinearLayout
        android:layout_width="300dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#0000FF"/>

</LinearLayout>

运行效果图:

3.2 RelativeLayout(相对布局)

3.2.1 相对父容器定位

  • layout_alignParentLeft:左对齐
  • layout_alignParentRight:右对齐
  • layout_alignParentTop:顶部对齐
  • layout_alignParentBottom:底部对齐
  • layout_centerHorizontal:水平居中
  • layout_centerVertical:垂直居中
  • layout_centerInParent:中间位置
     

3.2.2 相对兄弟容器定位

  • layout_toLeftOf:放置于参考组件的左边
  • layout_toRightOf:放置于参考组件的右边
  • layout_above:放置于参考组件的上方
  • layout_below:放置于参考组件的下方
  • layout_alignTop:对齐参考组件的上边界
  • layout_alignBottom:对齐参考组件的下边界
  • layout_alignLeft:对齐参考组件的左边界
  • layout_alignRight:对齐参考组件的右边界

3.2.3 通用属性

margin设置组件与父容器的边距

  • layout_margin:上下左右偏移
  • layout_marginLeft:左边距
  • layout_marginRight:右边距
  • layout_marginTop:上边距
  • layout_margiBottom:下边距

padding设置组件与子容器的边距

  • layout_padding:上下左右偏移
  • layout_paddingLeft:左边距
  • layout_paddingRight:右边距
  • layout_paddingTop:上边距
  • layout_paddingBottom:下边距

更多详见:2.2.2 RelativeLayout(相对布局) | 菜鸟教程 (runoob.com)

XML代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:id="@+id/rl1"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:background="#FF0000"/>

    <RelativeLayout
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_above="@+id/rl1"
        android:background="#00FF00"/>

    <RelativeLayout
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_alignTop="@+id/rl1"
        android:background="#0000FF"/>

    <RelativeLayout
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginLeft="100dp"
        android:background="#FFFF00"/>
</RelativeLayout>

运行效果图: 

3.3 TableLayout(表格布局)

3.3.1 常见属性

  • android:collapseColumns:设置需要被隐藏的列的序号,从0开始
  • android:stretchColumns:设置允许被拉伸的列的列序号,从0开始,要有被拉伸的空间才有效
  • ndroid:shrinkColumns:设置允许被收缩的列的列序号,从0开始,要有超出的空间才有效

3.3.2 子控件设置属性

  • android:layout_column:显示在第几列
  • android:layout_span:横向跨几列

更多详见:2.2.3 TableLayout(表格布局) | 菜鸟教程 (runoob.com)

XML代码:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TableRow>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第0个"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第1个"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第2个"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第3个"/>
    </TableRow>

    <TableRow>
        <Button
            android:layout_column="1"
            android:layout_span="2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第0个"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第1个"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第2个"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="第3个"/>
    </TableRow>
</TableLayout>

运行效果图: 

3.4 FrameLayout(帧布局)

3.4.1 常用属性

  • android:foreground设置前景
  • android:foregroundGravity设置前景位置

更多详见:2.2.4 FrameLayout(帧布局) | 菜鸟教程 (runoob.com)

XML代码:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="400dp"
        android:layout_height="400dp"
        android:background="#FF0000"/>

    <FrameLayout
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#00FF00"
        android:foreground="@drawable/ceshi"
        android:foregroundGravity="right|bottom"/>

    <FrameLayout
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="#0000FF"/>
</FrameLayout>

运行效果图:

3.5 GridLayout(网格布局)

3.5.1 常见属性

  • android:orientation:设置水平显示还是垂直显示
  • android:columnCount:设置行的显示个数
  • android:rowCount:设置列的显示个数

3.5.2 子控件属性

  • android:layout_column:显示在第几列
  • android:layout_columnSpan:横向跨几列
  • android:layout_columnWeight:横向剩余空间分配方式
  • android:layout_gravity:在网格中的显示位置
  • android:layout_row:显示在第几行
  • android:layout_rowSpan:横向跨几行
  • android:layout_rowWeight:纵向剩余空间分配方式

更多详见:2.2.5 GridLayout(网格布局) | 菜鸟教程 (runoob.com)

XML代码:

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:columnCount="2"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="第0个"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="第1个"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_column="1"
        android:layout_row="1"
        android:text="第2个"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_rowWeight="1"
        android:text="第3个"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="第4个"/>
</GridLayout>

运行效果图:

第4章 ConstraintLayout(约束布局)

ConstraintLayout采用方向约束的方式对控件进行定位,至少要保证水平和垂直方向都至少有一个约束才能确定控件的位置

4.1 布局的分类 

4.1.1 相对定位

我自己的XX位置和谁的YY位置对齐
注意:前四个不能与后四个的对应(Left-->Start、Right-->End)效果相同,但是它们不能交叉使用,否则后者无效果,后面内容同理

  • layout_constraintLeft_toLeftOf
  • layout_constraintLeft_toRightOf
  • layout_constraintRight_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintTop_toTopOf
  • layout_constraintTop_toBottomOf
  • layout_constraintBottom_toTopOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintBaseline_toBaselineOf:内容的底部对齐
  • layout_constraintStart_toEndOf
  • layout_constraintStart_toStartOf
  • layout_constraintEnd_toStartOf
  • layout_constraintEnd_toEndOf

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"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="20"
        android:textColor="@color/black"
        android:textSize="50sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="¥"
        android:textColor="@color/black"
        android:textSize="20sp"
        app:layout_constraintBaseline_toBaselineOf="@id/tv1"
        app:layout_constraintStart_toEndOf="@id/tv1" />

</androidx.constraintlayout.widget.ConstraintLayout>

运行效果图: 

4.1.2 外边距、内边距及goneMargin

外边距(margin):

  • android:layout_margin
  • android:layout_marginLeft
  • android:layout_marginTop
  • android:layout_marginRight
  • android:layout_marginBottom
  • android:layout_marginStart
  • android:layout_marginEnd

内边距(padding):

  • android:padding
  • android:paddingLeft
  • android:paddingTop
  • android:paddingRight
  • android:paddingBottom
  • android:paddingStart
  • android:paddingEnd

goneMargin:

goneMargin是一个依赖于目标的属性,当依赖的目标隐藏(android:visibility="gone")时才会生效,例如A被B依赖约束,当A隐藏时B设置的goneMargin就会生效,为不影响布局,往往会为B控件设置goneMargin,属性如下:

  • layout_goneMarginLeft
  • layout_goneMarginTop
  • layout_goneMarginRight
  • layout_goneMarginBottom
  • layout_goneMarginStart
  • layout_goneMarginEnd

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:id="@+id/A"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B"
        android:layout_width="60dp"
        android:layout_height="40dp"
        android:gravity="center"
        android:background="#FF0000"
        android:text="B"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="@id/A"
        app:layout_constraintStart_toEndOf="@id/A"
        app:layout_constraintTop_toTopOf="@id/A"
        app:layout_goneMarginStart="130dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

运行效果: 

4.1.3 偏移

  • layout_constraintHorizontal_bias:水平偏移,默认值0.5(水平居中)
  • layout_constraintVertical_bias:垂直偏移,默认值0.5(垂直居中)

4.1.4 角度约束

  • app:layout_constraintCircle:目标控件id
  • app:layout_constraintCircleRadius:到目标中心的距离
  • app:layout_constraintCircleAngle:对于目标的角度(0-360),正上方为0

4.1.5 尺寸约束及0dp值

约束控件的最小、最大尺寸:

  • android:minwidth
  • android:minHeight
  • android:maxWidth
  • android:maxHeight

控件大小的0dp值:

设置控件的大小除了传统的wrap_content 、指定具体尺寸、match_parent 外,ConstraintLayout还可以设置为0dp (MATCH_CONSTRAINT),并且 0dp的作用会根据设置的类型而产生不同的作用,进行设置类型的属性是:

  • layout_constraintWidth_default:水平方向控制
  • layout_constraintHeight_default:垂直方向控制

其属性可取值如下:

  • spread(默认):固定占用所有符合约束限制的空间,内容占用的空间不会侵占margin部分
  • wrap:匹配内容大小但不超过约束限制,内容占用的空间不会侵占margin部分
  • percent:父布局尺寸的一定比例,该模式需要配合layout_constrainWidth_prcent使用,但是写了layout_constrainWidth_prcent后,layout_constraintWidth_default="percent"其实就可以省略掉了,内容占用的空间不够时会侵占margin部分

注意:当空间的尺寸设置成wrap_content时内容占用的空间会去侵占margin部分,可通过以下属性是否强制限制

  • app:layout_constrainedWidth="true|false"
  • app:layout_constrainedHeight="true|false" 

0dp下的其他属性值:

  • layout_constraintWidth_min和layout_constraintHeight_min:设置维度的最小大小
  • layout_constraintWidth_max和layout_constraintHeight_max:设置维度的最大大小
  • layout_constraintWidth_percent和layout_constraintHeight_percent:会将维度的大小设置为父维度的百分比

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"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/A1"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="100dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="spread"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="spread" />

    <TextView
        android:id="@+id/A2"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="100dp"
        android:layout_marginEnd="100dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="app:layout_constraintWidth_defaultspread"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="spread" />

    <TextView
        android:id="@+id/B1"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="180dp"
        android:layout_marginEnd="100dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="wrap"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="wrap" />

    <TextView
        android:id="@+id/B2"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="260dp"
        android:layout_marginEnd="100dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="app:layout_constraintWidth_default=wrap"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="wrap" />

    <TextView
        android:id="@+id/C1"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="340dp"
        android:layout_marginEnd="100dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="percent"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="percent"/>

    <TextView
        android:id="@+id/C2"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="420dp"
        android:layout_marginEnd="100dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="app:layout_constraintWidth_default=percent"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_default="percent"/>

    <TextView
        android:id="@+id/C3"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="500dp"
        android:layout_marginEnd="100dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="app:layout_constraintWidth_percent=0.5"
        android:textColor="@color/black"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_percent="0.5"/>

</androidx.constraintlayout.widget.ConstraintLayout>

运行效果:

4.1.6 宽高比例约束(Ratio)

ConstraintLayout中可以对宽高设置比例,前提是至少有一个约束维度设置为0dp,这样比例才会生效,若两个都设置为0dp则以父元素的宽作为参考,该属性可使用两种设置:

  • app:layout_constraintDimensionRatio="宽度:高度":表示宽度和高度之间形式的比率
  • app:layout_constraintDimensionRatio="浮点值":表示宽度和高度之间的比率

注意:两个属性都有两个参数,第一个参数(W/H)可以省略,W表示宽度通过壁纸计算得来,H表示高度通过壁纸计算得来

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"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/A"
        android:layout_width="160dp"
        android:layout_height="0dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

运行效果图:

4.1.7 Chains(链)

Chains(链)它是将许多个控件在水平或者垂直方向,形成一条链,用于平衡这些控件的位置,设置成链的方式如下:

  • layout_constraintHorizontal_chainStyle:水平成链
  • layout_constraintVertical_chainStyle:垂直成链

可以属性如下:

  • spread(默认):均分剩余空间
  • packed:所有控件贴紧居中
  • spread_inside:两侧的控件贴近两边,剩余的控件均分剩余空间

Chains(链)还支持weight(权重)的配置,使用如下属性进行设置链元素的权重:

  • layout_constraintHorizontal_weight:水平方向权重分配
  • layout_constraintVertical_weight:垂直方向权重分配

注意:若需要进行权重设置,则该方向的上的尺寸设置为0dp

Chains(链)还支持bias(偏移)的配置,使用如下属性进行设置链元素的偏移:

  • layout_constraintHorizontal_bias:水平方向偏移,0.5表示居中
  • layout_constraintVertical_bias:垂直方向的偏移,0.5表示居中
  • 组合使用:

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"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/A"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/B"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="B"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/A"
        app:layout_constraintEnd_toStartOf="@id/C"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/C"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="C"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/B"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/A1"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginTop="100dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A1"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintHorizontal_chainStyle="spread_inside"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/B1"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B1"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="100dp"
        android:gravity="center"
        android:text="B1"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/A1"
        app:layout_constraintEnd_toStartOf="@id/C1"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/C1"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="100dp"
        android:gravity="center"
        android:text="C1"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/B1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/A2"
        android:layout_width="0dp"
        android:layout_height="80dp"
        android:layout_marginTop="200dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A2"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/B2"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B2"
        android:layout_width="0dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="200dp"
        android:gravity="center"
        android:text="B2"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintStart_toEndOf="@id/A2"
        app:layout_constraintEnd_toStartOf="@id/C2"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/C2"
        android:layout_width="0dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="200dp"
        android:gravity="center"
        android:text="C2"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintStart_toEndOf="@id/B2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/A3"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginTop="300dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A3"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/B3"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B3"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="300dp"
        android:gravity="center"
        android:text="B3"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/A3"
        app:layout_constraintEnd_toStartOf="@id/C3"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/C3"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="300dp"
        android:gravity="center"
        android:text="C3"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/B3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/A4"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginTop="400dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A4"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintHorizontal_bias="0.3"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/B4"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B4"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="400dp"
        android:gravity="center"
        android:text="B4"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/A4"
        app:layout_constraintEnd_toStartOf="@id/C4"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/C4"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:layout_marginTop="400dp"
        android:gravity="center"
        android:text="C4"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@id/B4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

运行效果图: 

4.2 辅助类

ConstraintLayout为了解决嵌套问题还提供了一系列的辅助控件帮助开发者布局

4.2.1 Guideline(参考线)

Guideline是一条参考线,可以帮助开发者进行辅助定位,它并不会真正显示在布局中,像是数学几何中的辅助线一样

  • android:orientation="horizontal|vertical" :辅助线的方向(垂直|水平)
  • app:layout_constraintGuide_percent:距离父级宽度或高度的百分比(0-1的小数形式)
  • app:layout_constraintGuide_begin:距离父级起始位置的距离(左侧或顶部)
  • app:layout_constraintGuide_end:距离父级结束位置的距离(右侧或底部)

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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/A"
        android:layout_width="120dp"
        android:layout_height="80dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@+id/guideline1"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="60dp" />

    <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="120dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

运行效果图:

4.2.2 Barrier(屏障)

当我们创建Android布局时,有时我们会遇到布局可以根据本地化而变化的情况。下面是一个来自于约束布局 (constraintlayout.com)非常简单的示例:

这里有三个文本视图:左边的textView1textView2;右边的textView3textView3被限制在textView1的末尾,这工作得很好——它完全根据我们需要来定位和大小textView3。然而,如果我们需要支持多种语言,事情会变得更加复杂。如果我们添加德语翻译,那么我们就会遇到一个问题,因为在英文版本中,textView1中的文本比textView2中的文本长,而在德语中,textView2中的文本比textView1长:

这里的问题在于textView3仍然是相对于textView1的,所以textView2直接插入了textView3中。在设计视图里看起来更明显(白色背景的那个)。比较直接的解决办法是使用TableLayout,或者把 textView1 & textView2 包裹在一个垂直的,android:layout_width="wrap_content"LinearLayout中。然后让textView3约束在这个LinearLayout的后面。但是我们有更好的办法:Barriers
Barriers的配置属性如下:

  • app:barrierDirection="top|bottom|left|right|start|end":用于控制Barrier相对于给定的View的位置
  • app:constraint_referenced_ids="id,id":取值是要依赖的控件的id,Barrier将会使用ids中最大的一个的宽/高作为自己的位置

4.2.3 Group(组)

ConstraintLayout中的Group的作用是对一组控件同时隐藏或显示,没有其他的作用,它的属性如下:

  • app:constraint_referenced_ids="id1,id2":加入组的控件id

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"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/A1"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:layout_marginTop="60dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="A1"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/A2"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:layout_marginTop="160dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="A2"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B1"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:layout_marginTop="60dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="B2"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B2"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:layout_marginTop="60dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="B2"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/B3"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:layout_marginTop="160dp"
        android:background="#0FF0F0"
        android:gravity="center"
        android:text="B2"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Group
        android:id="@+id/group1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="visible"
        app:constraint_referenced_ids="A1,A2" />

    <androidx.constraintlayout.widget.Group
        android:id="@+id/group2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="visible"
        app:constraint_referenced_ids="B1,B2,B3" />

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="28dp"
        android:layout_marginTop="300dp"
        android:text="A组隐藏/显示"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="272dp"
        android:layout_marginTop="300dp"
        android:text="B组隐藏/显示"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Java代码: 

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    Button btn1,btn2;
    Group group1,group2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_group);
        btn1 = findViewById(R.id.btn1);
        group1 = findViewById(R.id.group1);
        btn2 = findViewById(R.id.btn2);
        group2 = findViewById(R.id.group2);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == btn1.getId()){
            group1.setVisibility(group1.getVisibility()==Group.VISIBLE?Group.INVISIBLE:Group.VISIBLE);
        } else{
            group2.setVisibility(group2.getVisibility()==Group.VISIBLE?Group.INVISIBLE:Group.VISIBLE);
        }
    }
}

运行效果图:

4.2.4 Placeholder(占位符)

Placeholder的作用就是占位,它可以在布局中占好位置,通过app:content=""属性,或者动态调用setContent()设置内容,来让某个控件移动到此占位符中

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"
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/A"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="AAA"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Placeholder
        android:id="@+id/p"
        android:layout_width="100dp"
        android:layout_height="60dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="150dp"
        android:layout_marginTop="200dp"
        android:text="移动"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    Placeholder placeholder;
    TextView tv1,tv2;
    ConstraintLayout mConstraintLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_placeholder);
        Button btn1 = findViewById(R.id.btn1);
        placeholder = findViewById(R.id.p);
        tv1 = findViewById(R.id.A);
        tv2 = findViewById(R.id.B);
        mConstraintLayout = findViewById(R.id.constraintLayout);
        btn1.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        TransitionManager.beginDelayedTransition(mConstraintLayout);
        if (placeholder.getContent() == null) {
            System.out.println(tv1.getId());
            placeholder.setContentId(tv1.getId());
        }else {
            if (placeholder.getContent().getId()==tv1.getId()){
                placeholder.setContentId(tv2.getId());
            }else {
                placeholder.setContentId(tv1.getId());
            }
        }
    }
}

运行效果图: 

 注意:设置文本内容时样式发生了改变,而且placeholder不知如何还原初态

4.2.5 Flow(流式虚拟布局)

Flow是用于构建链的新虚拟布局,当链用完时可以缠绕到下一行甚至屏幕的另一部分。当您在一个链中布置多个项目,但是您不确定容器在运行时的大小时,这很有用。您可以使用它来根据应用程序中的动态尺寸(例如旋转时的屏幕宽度)构建布局。

1. 链约束(针对的时超出屏幕部分)

Flowconstraint_referenced_ids关联的控件是没有设置约束的,这一点和普通的链是不一样的,这种排列方式是Flow的默认方式none,我们可以使用app:flow_wrapMode=""属性来设置排列方式,并且我们还可以使用flow_horizontalGapflow_verticalGap分别设置两个view在水平和垂直方向的间隔,下面我们再添加几个控件来展示三种排列方式:

  • none(默认值):所有引用的view形成一条链,水平居中,超出屏幕两侧的view不可见
  • chian:所引用的view形成一条链,超出部分会自动换行,同行的view会平分宽度,纵横平均分配Flow空间
  • aligned:所引用的view形成一条链,超出部分会自动换行,同行的view会平分宽度,纵向平均分配Flow空间,横向的view会从start处开始排列,默认从左往右排列

 当flow_wrapMode的值是chianaligned时,我们还可以针对不同的链进行配置,这里就不一一展示效果了,具体的属性如下:

  • app:flow_horizontalStyle="packed|spread|spread_inside"  所有水平链的配置(app:flow_wrapMode="chian"
  • app:flow_verticalStyle="packed|spread|spread_inside"    所有垂直链的配置(app:flow_wrapMode="aligned"
  • app:flow_firstHorizontalStyle="packed|spread|spread_inside" 第一条水平链的配置,其他条不生效
  • app:flow_firstVerticalStyle="packed|spread|spread_inside"   第一条垂直链的配置,其他条不生效
  • app:flow_lastHorizontalStyle="packed|spread|spread_inside"  最后一条水平链的配置,其他条不生效 
  • app:flow_lastVerticalStyle="packed|spread|spread_inside"    最后一条垂直链的配置,其他条不生效 

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"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <TextView
        android:id="@+id/A"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="A"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/B"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="B"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/C"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="C"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/D"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="D"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/E"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="E"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/F"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="F"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/G"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="G"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/H"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#00FF00"
        android:gravity="center"
        android:text="E"
        android:textColor="@color/black"
        android:textSize="25sp"
        android:textStyle="bold" />

    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="A,B,C,D,E,F,G,H"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

2. 对齐约束

view的大小不同时,Flow也提供了相应的属性进行配置(flow_wrapMode="aligned"时,我试着没有效果)。

使用flow_verticalAlign时,要求orientation的方向是horizontal

使用flow_horizontalAlign时,要求orientation的方向是vertical

  • app:flow_verticalAlign="top|bottom|center|baseline"
  • app:flow_horizontalAlign="start|end|center"

 horizontal 水平排列

  • top

    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:flow_wrapMode="chain"
        android:orientation="horizontal"
        app:flow_verticalAlign="top"
        app:constraint_referenced_ids="A,B,C,D,E,F,G,H"
        app:layout_constraintTop_toTopOf="parent" />
  • button
    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:flow_wrapMode="chain"
        android:orientation="horizontal"
        app:flow_verticalAlign="bottom"
        app:constraint_referenced_ids="A,B,C,D,E,F,G,H"
        app:layout_constraintTop_toTopOf="parent" />
  •  center(默认)
    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:flow_wrapMode="chain"
        android:orientation="horizontal"
        app:flow_verticalAlign="center"
        app:constraint_referenced_ids="A,B,C,D,E,F,G,H"
        app:layout_constraintTop_toTopOf="parent" />
  • baseline(我这里无效果)

 垂直方向排列同理

3. 数量约束

flow_wrapMode属性为alignedchian时,通过flow_maxElementsWrap属性控制每行最大的子View数量,例如我们设置为flow_maxElementsWrap=4,效果图如下:

 

4.2.6 ImageFilterButton & ImageFilterView

ImageFilterButtonImageFilterView是两个控件,他们之间的关系就和ImageButtonImageView一样,即ImageFilterButton继承ImageButton,而ImageFilterView继承ImageView,所以这里就只拿ImageFilterView来做讲解。它们的大致作用有两部分,一是可以用来做圆角图片,二是可以叠加图片资源进行混合过滤,下面一一展示:

1. 圆角图片

ImageFilterButtonImageFilterView可以使用两个属性来设置图片资源的圆角,分别是roundPercentroundroundPercent接受的值类型是0-1的小数,根据数值的大小会使图片在方形和圆形之间按比例过度,round可以设置具体圆角的大小。下面请看例子:

2. 图片过滤

ImageFilterButtonImageFilterView不但可以使用src来设置图片资源,还可以使用altSrc来设置第二个图片资源,altSrc提供的资源将会和src提供的资源通过crossfade属性形成交叉淡化效果,默认情况下,crossfade=0altSrc所引用的资源不可见,取值在0-1。下面看例子:

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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/mi"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:roundPercent="0.7" />

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

    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:id="@+id/view1"
        android:layout_width="280dp"
        android:layout_height="280dp"
        android:src="@drawable/jetpack"
        app:altSrc="@drawable/bg1"
        app:crossfade="0.5"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline3" />

</androidx.constraintlayout.widget.ConstraintLayout>

运行效果图:

除此之外,还有以下属性(取值范围为:0-10):
warmth:调节色温
brightness:调节亮度
saturation:调节饱和度
contrast:调节对比度

第5章 动画效果

5.1 帧动画

Step 1:创建frame.xml。

  • 创建animation-list并编写标签,属性为:drable="资源",duration="持续时间"

Step 2:在activity_main.xml文件中引用frame资源为View的背景。

Step 3:获取View的背景强转为AnimationDrawable。

  • AnimationDrawable animationDrawable = (AnimationDrawable) relativeLayout.getBackground();

Step 3:调用AnimationDrawable的方法进行控制

  • start(); 启动
  • stop(); 停止

drawable/frame.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/frame1" android:duration="100"/>
    <item android:drawable="@drawable/frame2" android:duration="100"/>
    <item android:drawable="@drawable/frame3" android:duration="100"/>
    <item android:drawable="@drawable/frame4" android:duration="100"/>
</animation-list>

activty_main.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="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始" />

    <Button
        android:id="@+id/btn_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止" />

    <ImageView
        android:id="@+id/frame"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_gravity="center"
        android:background="@drawable/frame" />

</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        Button btn_start = findViewById(R.id.btn_start);
        Button btn_stop = findViewById(R.id.btn_stop);

        ImageView imageView = findViewById(R.id.frame);
        AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();

        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                animationDrawable.start();
            }
        });

        btn_stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                animationDrawable.stop();
            }
        });
    }
}

运行效果图: 

5.2 补间动画

5.2.1 补间动画的介绍

补间动画是一种设定动画开始状态、结束状态,其中间的变化由系统计算补充。

补间动画由Animation类来实现具体效果,包括透明度(AlphaAnimation)、旋转(RotateAnimation)、缩放(ScaleAnimation)、平移(TranslateAnimation)四个子类,四种变化。

5.2.2 透明度(AlphaAnimation)

透明度渐变效果,创建时许指定开始以及结束透明度,还有动画的持续 时间,透明度的变化范围(0,1),0是完全透明,1是完全不透明;对应<alpha/>标签 

步骤如下:

Step 1:编写activity_main.xml文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="100dp"
        android:layout_centerHorizontal="true"
        android:maxWidth="300dp"
        android:maxHeight="300dp"
        android:src="@drawable/ceshi"/>

    <Button
        android:id="@+id/btn_alpha"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="400dp"
        android:text="透明" />

</RelativeLayout>

Step 2:res/创建anim目录(Directory)/新建alpha.xml文件(Animation Resource File)

Step 3:编写alpha.xml文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:fromAlpha="0"
        android:toAlpha="1"
        android:duration="5000"/>
</set>

Step 4:1在ActivityMain.java中获取需要设置alpha的imageview控件,然后加载xml动画设置文件来创建一个 Animation对象,通过控件的imageview.startAnimation(animation)方法来启动动画

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView imageView;

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

        imageView = findViewById(R.id.iv);

        Button btn_alpha = findViewById(R.id.btn_alpha);

        btn_alpha.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        //通过加载xml动画设置文件来创建一个 Animation对象
        Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.alpha);
        Log.e("TAG", "onClick: " + view.getId());
        imageView.startAnimation(animation);
    }
}

Step 5:运行效果图:

5.2.3 旋转(RotateAnimation)

旋转渐变效果,创建时指定动画起始以及结束的旋转角度,以及动画 持续时间和旋转的轴心;对应<rotate/>标签

 步骤:同上

rotate.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate android:pivotX="50%" android:pivotY="50%"
        android:fromDegrees="0" android:toDegrees="360"
        android:duration="5000"/>
</set>

5.2.4 缩放(ScaleAnimation)

缩放渐变效果,创建时需指定开始以及结束的缩放比,以及缩放参考点, 还有动画的持续时间;对应<scale/>标签

 步骤:同上 

scale.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <scale android:pivotY="50%" android:pivotX="50%"
        android:fromXScale="0.2" android:fromYScale="0.2"
        android:toXScale="1" android:toYScale="1"
        android:duration="5000"/>
</set>

5.2.5 平移(TranslateAnimation)

位移渐变效果,创建时指定起始以及结束位置,并指定动画的持续时间即可;对应<translate/>标签

 步骤:同上 

translate.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="-40%" android:fromYDelta="0%"
        android:toXDelta="60%" android:toYDelta="0%"
        android:duration="5000"/>
</set>

5.2.6 组合(AnimationSet

组合渐变,就是前面多种渐变的组合,对应<set/>标签。以最大的时间为渐变时间

 步骤:同上

sets.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="0" android:toAlpha="1"
        android:duration="5000"/>
    <rotate android:pivotX="50%" android:pivotY="50%"
        android:fromDegrees="0" android:toDegrees="360"
        android:duration="5000"/>
    <scale android:pivotY="50%" android:pivotX="50%"
        android:fromXScale="0.2" android:fromYScale="0.2"
        android:toXScale="1" android:toYScale="1"
        android:duration="5000"/>
    <translate android:fromXDelta="-40%" android:fromYDelta="0%"
        android:toXDelta="60%" android:toYDelta="0%"
        android:duration="5000"/>
</set>

更多详见:8.4.2 Android动画合集之补间动画 | 菜鸟教程 (runoob.com)

activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="100dp"
        android:layout_centerHorizontal="true"
        android:maxWidth="300dp"
        android:maxHeight="300dp"
        android:src="@drawable/ceshi"/>

    <Button
        android:id="@+id/btn_alpha"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="400dp"
        android:text="透明" />

    <Button
        android:id="@+id/btn_rotate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="110dp"
        android:layout_marginTop="400dp"
        android:text="旋转" />

    <Button
        android:id="@+id/btn_scale"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="210dp"
        android:layout_marginTop="400dp"
        android:text="缩放" />

    <Button
        android:id="@+id/btn_translate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="310dp"
        android:layout_marginTop="400dp"
        android:text="平移" />

    <Button
        android:id="@+id/btn_sets"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="460dp"
        android:text="上诉组合" />
</RelativeLayout>

ActivityMain.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView imageView;

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

        imageView = findViewById(R.id.iv);

        Button btn_alpha = findViewById(R.id.btn_alpha);
        Button btn_rotate = findViewById(R.id.btn_rotate);
        Button btn_scale = findViewById(R.id.btn_scale);
        Button btn_translate = findViewById(R.id.btn_translate);
        Button btn_sets = findViewById(R.id.btn_sets);

        btn_alpha.setOnClickListener(this);
        btn_rotate.setOnClickListener(this);
        btn_scale.setOnClickListener(this);
        btn_translate.setOnClickListener(this);
        btn_sets.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Animation animation = null;
        switch (view.getId()){
            case R.id.btn_alpha:
                animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.alpha);
                break;
            case R.id.btn_rotate:
                animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.rotate);
                break;
            case R.id.btn_scale:
                animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.scale);
                break;
            case R.id.btn_translate:
                animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.translate);
                break;
            case R.id.btn_sets:
                animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.sets);
                break;
        }
        Log.e("TAG", "onClick: " + view.getId());
        imageView.startAnimation(animation);
    }
}

运行效果图:

5.3 属性动画

详见:8.4.3 Android动画合集之属性动画-初见 | 菜鸟教程 (runoob.com)

第6章 ViewPager(视图滑动切换)

6.1 ViewPager的简单介绍

ViewPager就是一个简单的页面切换组件,我们可以往里面填充多个View,然后我们可以左 右滑动,从而切换不同的View,我们可以通过setPageTransformer()方法为我们的ViewPager 设置切换时的动画效果,和前面学的ListView,GridView一样,我们也需要一个Adapter (适配器)将我们的View和ViewPager进行绑定,而ViewPager则有一个特定的Adapter—— PagerAdapter!另外,Google官方是建议我们使用Fragment来填充ViewPager的,这样 可以更加方便的生成每个Page,以及管理每个Page的生命周期!给我们提供了两个Fragment 专用的Adapter:FragmentPageAdapterFragmentStatePagerAdapter 我们简要的来分析下这两个Adapter的区别:

  • FragmentPageAdapter:和PagerAdapter一样,只会缓存当前的Fragment以及左边一个,右边 一个,即总共会缓存3个Fragment而已,假如有1,2,3,4四个页面:
    处于1页面:缓存1,2
    处于2页面:缓存1,2,3
    处于3页面:销毁1页面,缓存2,3,4
    处于4页面:销毁2页面,缓存3,4
    更多页面的情况,依次类推~
  • FragmentStatePagerAdapter:当Fragment对用户不 见得时,整个Fragment会被销毁, 只会保存Fragment的状态!而在页面需要重新显示的时候,会生成新的页面!

综上,FragmentPageAdapter适合固定的页面较少的场合;而FragmentStatePagerAdapter则适合 于页面较多或者页面内容非常复杂(需占用大量内存)的情况!

使用步骤:

Step 1:在activity_main.xml中编写ViewPage控件

<?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">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 2:在layout/创建多个布局layout.xml

Step 3:编写MyPagerAdapter.java继承PagerAdapter并实现其两个方法

  • public int getCount();
  • public boolean isViewFromObject(@NonNull View view, @NonNull Object object);

Step 4:1

6.2 PagerAdapter的使用

我们先来介绍最普通的PagerAdapter,如果想使用这个PagerAdapter需要重写下面的四个方法: 当然,这只是官方建议,实际上我们只需重写getCount()和isViewFromObject()就可以了~

  • getCount():获得viewpager中有多少个view
  • destroyItem():移除一个给定位置的页面。适配器有责任从容器中删除这个视图。 这是为了确保在finishUpdate(viewGroup)返回时视图能够被移除。

而另外两个方法则是涉及到一个key的东东:

  • instantiateItem():
    ①将给定位置的view添加到ViewGroup(容器)中,创建并显示出来
    ②返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以 自定义自己的key,但是key和每个view要一一对应的关系
  • isViewFromObject(): 判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是 代表的同一个视图(即它俩是否是对应的,对应的表示同一个View),通常我们直接写 return view == object!

6.3 ViewPager的简单实例

Step 1:在activity_main.xml中编写ViewPage控件

<?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">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Step 2:在layout/创建多个布局layouti.xm。

Step 3:编写MyPagerAdapter.java继承PagerAdapter并实现其方法。使用这个PagerAdapter需要重写下面的四个方法: 当然,这只是官方建议,实际上我们只需重写getCount()和isViewFromObject()就可以了

  • public int getCount();
    获得viewpager中有多少个view
  • public boolean isViewFromObject(@NonNull View view, @NonNull Object object);
    移除一个给定位置的页面。适配器有责任从容器中删除这个视图,这是为了确保在finishUpdate(viewGroup)返回时视图能够被移除。

 而另外两个方法则是涉及到一个key的东西:

  • public Object instantiateItem(@NonNull ViewGroup container, int position);
    ①将给定位置的view添加到ViewGroup(容器)中,创建并显示出来
    ②返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以 自定义自己的key,但是key和每个view要一一对应的关系
  • public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object);
    判断instantiateItem(ViewGroup, int)函数所返回来的Key与一个页面视图是否是 代表的同一个视图(即它俩是否是对应的,对应的表示同一个View),通常我们直接写 return view == object

 MyViewPagerAdapter.java

public class MyPagerAdapter extends PagerAdapter {

    private ArrayList<View> listView;

    public MyPagerAdapter(ArrayList<View> listView) {
        this.listView = listView;
    }

    @Override
    public int getCount() {
        return listView.size();
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
        return view == object;
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        container.addView(listView.get(position), 0);
        return listView.get(position);
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView(listView.get(position));
    }
}

Step 4:获取加载布局的系统服务。 

Step 5:准备数据。利用LayoutInflater对象的inflate()方法获取切换的布局并且添加到列表集合中。

  • LayoutInflater. inflate() 这个方法的作用类似于 findViewById() 。不同点是 inflate() 是用来找 res/layout/ 下的 xml 布局文件,并且实例化,而 findViewById() 是找 xml 布局文件下的具体 widget 控件

Step 6:实例化 PagerAdapter适配器。

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        //获取加载布局的系统服务
        LayoutInflater layoutInflater = LayoutInflater.from(this);

        View view1 = layoutInflater.inflate(R.layout.layout1, null);
        View view2 = layoutInflater.inflate(R.layout.layout2, null);
        View view3 = layoutInflater.inflate(R.layout.layout3, null);
        ArrayList<View> listView = new ArrayList<>();
        listView.add(view1);
        listView.add(view2);
        listView.add(view3);

        ViewPager viewPager = findViewById(R.id.vp);
        MyPagerAdapter myPagerAdapter = new MyPagerAdapter(listView);
        viewPager.setAdapter(myPagerAdapter);
    }
}

运行效果图:

更多详见:2.6.3 ViewPager的简单使用 | 菜鸟教程 (runoob.com)

第7章 Activity(活动)

7.1 Activity的介绍

7.1.1 什么是Activity

定义:Activity是Android的四大组件之一。是用户操作的可视化界面;它为用户提供了一个完成操作指令的窗口。当我们创建完毕Activity之后,需要调用setContentView()方法来完成界面的显示;以此来为用户提供交互的入口。在Android App 中只要能看见的几乎都要依托于Activity,所以Activity是在开发中使用最频繁的一种组件。

7.1.2 Activity的声明

要使应用能使用Activity就需要在AndroidManifest.xml文件中进行声明及其设置特定属性,步骤为:

  1. 打开AndroidManifest.xml配置文件
  2. 在<manifest>标签下的<application>标签中添加<activity>元素并指明android:name=".类名"

注意:其中'.'代表<manifest>标签下的package的属性值(包名),或者name属性值也可写Activity的全类名

7.2 Activity的生命周期

7.2.1 Activity的生命周期介绍

在Android中会维持一个Activity Stack(Activity栈),当一个新的Activity创建时,它就会放到栈顶,这个Activity就处于运行状态。当再有一个新的Activity被创建后,会重新压人栈顶,而之前的Activity则会在这个新的Activity底下,就像枪梭压入子弹一样。而且之前的Activity就会进入后台。
一个Activity实质上有四种状态:

  1. 运行中(Running/Active):这时Activity位于栈顶,是可见的,并且可以用户交互。
  2. 暂停(Paused):当Activity失去焦点,不能跟用户交互了,但依然可见,就处于暂停状态。当一个新的非全屏的Activity或者一个透明的Activity放置在栈顶,Activity就处于暂停状态;这个时候Activity的各种数据还被保持着;只有在系统内存在极低的状态下,系统才会自动的去销毁Activity。
  3. 停止(Stoped):当一个Activity被另一个Activity完全覆盖,或者点击HOME键退入了后台,这时候Activity处于停止状态。这里有些是跟暂停状态相似的:这个时候Activity的各种数据还被保持着;当系统的别的地方需要用到内容时,系统会自动的去销毁Activity。
  4. 销毁(Detroyed):当我们点击返回键或者系统在内存不够用的情况下就会把Activity从栈里移除销毁,被系统回收,这时候,Activity处于销毁状态。

7.2.2 Activity的生命周期状态

  • onCreate:表示窗口正在被创建,比如加载layout布局文件啊(setContentView)。 所以我们可以在这个方法中,做一些初始化的操作。
  • onStart:表示Activity正在被启动,即将开始,此时的窗口已经可见了,但是还没有出现在前台,所以无法和用户进行交互。也就是说此时的窗口正处在 不可见—>可见 的过程中。
  • onRestart:表示窗口正在重新启动。在什么场景下会调用这个呢?比如:从A页面进入B页面,然后点击BACK键(或者自己的返回上一页的按钮)回到A页面,那么就会调用A页面的onRestart方法了。(当前也牵扯到A和B页面的其他生命周期方法的调用,这个我们后面再详细说明)。再比如:点击HOME键回到桌面,然后通过点击任务栏或者点击应用图标再次进入A页面,都可以触发调用这个方法
  • onResume:表示此时的窗口已经可见了,显示在前台并且进行活动了,我们也可以与窗口进行交互了。
  • onPause:表示窗口正在停止,这是我们可以做一些存储数据、或者停止动画等一些不太耗时的操作,因为会影响到下一个Activity的显示。onPause执行完成之后,新的Activity的onResume才会执行。
  • onStop:表示窗口即将停止,此时,可以做一些稍微重量级的回收工作,但是也不能太耗时哈。
  • onDestroy:表示窗口即将被销毁。这是Activity生命周期中的最后一步了。这里,我们可以做一些回收工作和最终的资源释放工作。

7.2.3 Activity的生命周期调用

  • 启动一个Activity
    onCreate()–>onStart()–>onResume()
  • 在Activity创建完成后,点击Home回调主界面时
    onPause()–>onStop()
  • 当点击Home键后,点击App回到Activity时
    onRestart()–>onStart()–>onResume()
  • 在原有的Activity的基础上打新的Activity时
    开启新的Activity,原Activity生命周期执行方法顺序为:onPause()–>onStop(),事实上跟点击home键是一样的。
    但是这里有点要注意的是如果新的Activity使用了透明主题,那么当前Activity不会回调onStop方法。同时我们发现新Activity(SecondActivity)生命周期方法是在原Activity的onPause方法执行完成后才可以被回调,这也就是前面我们为什么说在onPause方法不能操作耗时任务的原因了。
  • 回退到原来的Activity时不会调用不会调用onRestart()
    onCreate()–>onStart()–>onResume()
  • 点击Back键回退时,相当于退出了当前Activity,Activity将被销毁
    onPause()–>onStop()–>onDestroy()

7.3 Intent和IntentFilter

7.3.1 Intent的基本概念

Intent中文意思指”意图”,按照Android的设计理念,Android使用Intent来封装程序的”调用意图”,不管启动Activity(活动)、Service(服务)、BroadcastReceiver(广播接收器),Android都使用统一的Intent对象来封装这一”启动意图”。此外,Intent也是应用程序组件之间通信的重要媒介。在Android中指定的了具体是某个组件,那么就是显性意图;如果只是提出要求没有指定具体的某个人,在Android中即没有指定某个具体的组件,那么就是隐式意图;所有Intent页面跳转的方式又分为显示跳转和隐式跳转。

7.3.2 Intent和三大组件

Android应用程序包含三种重要组件:Activity(活动)、Service(服务)、BroadcastReceiver(广播接收器),应用程序采用一致的方式启动它们,都是依靠Intent来进行启动的,Intent中封装了程序要启动的意图。

下面是Intent启动不同组件的部分方法:

  • Activity组件:
    startActivity(Intent intent);
    startActivityForResult(Intent intent,int requestCode);
  • Service组件:
    startService(Intent intent);
    bindService(Intent intent,ServiceConnection conn,int flags);
  • BroadcastReceiver组件:
    sendBroadcast(Intent intent);
    sendOrderedBroadcast(Intent intent,String receiverPermission);

7.3.3 Intent的跳转方式

  1. Intent显式跳转页面:
    显示意图的跳转是Intent对象中包含了目标的class文件
    Intent intent = new Intent(this, SecondActivity.class);
    startActivity(intent);
    如果要传递数据也可以通过Intent对象使用putExtra方法来传递数据。
    这里的目标文件必须是要在AndroidManifest.xml里面注册。
  2. Intent隐式跳转页面:
    隐式Intent不会明确指出需要激活的目标组件,它被广泛地应用在不同应用程序之间传递消息。Android系统会使用IntentFilter匹配相应的组件,匹配的属性主要包括以下三个:
    action:表示Intent对象要完成的意图动作。
    data:指定数据的URL或者数据MIME类型他的值通常与Intent的action属性有关联。
    category:表示activity的动作的类别。

注意:在隐式跳转中,匹配的每一个属性可以罗列多个

7.3.3 IntentFilter的基本概念

IntentFilter的意思是“意图过滤器”当我们隐式的启动系统组件的时候,就会根据IntentFilter来匹配相应的组件进行启动。

7.3.4 IntentFilter的属性

Intent通过下面的属性来描述的某个意图:

  • action(动作): 用来表示意图的动作,如:查看,发邮件,打电话
  • category(类别): 用来表示动作的类别。
  • data(数据): 表示与动作要操作的数据。如:查看指定的联系人
  • type(数据类型): 对data类型的描述。
  • extras(附加信息): 附加信息。如:详细资料,一个文件,某事。
  • component(目标组件): 目标组件。

7.3.5 action(动作)属性

动作很大程度上决定了Intent如何构建,特别是数据和附加信息,就像一个方法名决定了参数和返回值一样,所以应该尽可能明确地指定动作,并紧密关联到其他的Intent字段,如Category和Data。

常用动作 
最常用的是Action_MAIN(作为初始的Activity启动,没有数据的输入输出) 

  1. ACTION_MAIN 作为一个主要的进入口,而并不期望去接受数据
  2. ACTION_VIEW 向用户去显示数据
  3. ACTION_ATTACH_DATA 别用于指定一些数据应该附属于一些其他的地方,例如,图片数据应该附属于联系人 
  4. ACTION_EDIT 访问已给的数据,提供明确的可编辑
  5. ACTION_GET_CONTENT 允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音)
  6. ACTION_DIAL 拨打一个指定的号码,显示一个带有号码的用户界面,允许用户去启动呼叫
  7. ACTION_CALL 根据指定的数据执行一次呼叫(有缺陷,使用ACTION_DIAL) 
  8. ACTION_SEND 传递数据,被传送的数据没有指定,接收的action请求用户发数据
  9. ACTION_SENDTO 发送一条信息到指定的某人
  10. ACTION_ANSWER 处理一个打进电话呼叫
  11. ACTION_INSERT 插入一条空项目到已给的容器
  12. ACTION_DELETE 从容器中删除已给的数据
  13. ACTION_SEARCH 执行一次搜索
  14. ACTION_WEB_SEARCH 执行一次web搜索

上面的动作都是Intent对象引用才有实际意义的。 
setAction(String action) 用来设置Intent的动作,参数可以为常量 
getAction() 方法用来获取Intent动作名称 
上面的Action都是系统定义好的,具有一定意义的动作指向的动作。 
Intent的Action对象其实就是一个字符串常量,系统的Action对象是系统定义好的字符串常量,我们也可以自己定义自己的Action作为字符串常量。就像上面的例子使用到了自定义的Action字符串对象。

7.3.6 category(类别)属性

Intent的action、category属性都是普通的字符串,其中action表示Intent需要完成的一个抽象”动作”,而category则为action添加额外的类别信息,通常action和category一块使用。 
需要指出的是,一个Intent中只能包含一个action属性,但可以包含多个category属性。当程序创建Intent时,该Intent默认启动常量值为andorid.intent.category.DEFAULT的组件。这里的一个Intent中只能包含一个action属性,并不是Activity中xml的设置规范,而是你要跳转到的页面去,你只能设置一个Action的值。 
常用的Category: 

  1. CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity的执行方式执行。  
  2. CATEGORY_HOME:设置该组件为Home Activity。 
  3. CATEGORY_PREFERENCE:设置该组件为Preference。  
  4. CATEGORY_LAUNCHER:设置为当前应用程序优先级最高的Activity,通常与ACTION_MAIN配合使用。  
  5. CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。  
  6. CATEGORY_GADGET:设置该组件可以内嵌到另外的Activity中。 

上面的类别都是Intent对象引用才有实际意义的。

7.3.7 data(数据)属性

Data数据用来向Action属性提供动作的数据。这里的Data不是Intent里面的数据,而是指明动作的具体数据,比如说动作是打电话,那么打给具体的某一个人,就用到了date里面的数据来指定。同样发邮件、或打开具体的某一个网址也是通过Data数据。
Data属性只接受Uri对象,Uri对象是统一资源标识符。对应概念不用太多理解,只需知道里面某些具体值的表现形式就可以了。
Uri其实就是相当于一个网址,如图所示:

网址只是Uri其中一种格式的字符串,要使用它还要把它解析后转化为Uri类型。
为Intent对象添加Data数据,代码:
intent.setData(Uri.parse(“http://www.baidu.com“));
这里的Uri的有两个没显示的属性:port的值是8080,path的值是/index
通过下面三句代码就可以跳转到百度主页面:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);

7.3.8 type(数据类型)属性

与Data有关的,这个不是Intent的数据类型,是Intent的Action的Data数据的类型。 
比如: 
{“.mp3”, “audio/x-mpeg”}, 
{“.mp4”, “video/mp4”}, 
{“.gif”, “image/gif”}, 
{“.rmvb”, “audio/x-pn-realaudio”}, 
这里只是做几个简单的示例介绍,如果是打开gif格式的数据文件,设置type=“image/gif”

7.3.9 extras(附加信息)属性

Extras属性主要用于传递目标组件所需要的额外的数据。这个数据是可以通过Intent来保存的数据和Intent对象来获取数据。

通过putExtras()方法设置。保存数据
通过putExtras()方法设置。获取数据
通常我们使用Intent来直接传递Bundle对象,但也可以传递其他系统内置的一些参数。 
如果要传递是是对象,那么对象必须实现序列化。

7.3.10 component(目标组件)属性

这个属性用得比较少,最好不用。如果是显示调用直接指定目标类的class文件名就可以使用了。

7.3.11 Intent的Action属性的部分常量值

  1. ACTION_MAIN:Android Application的入口,每个Android应用必须且只能包含一个此类型的Action声明。
  2. ACTION_VIEW:系统根据不同的Data类型,通过已注册的对应Application显示数据。
  3. ACTION_EDIT:系统根据不同的Data类型,通过已注册的对应Application编辑示数据。
  4. ACTION_DIAL:系统默打开拨号程序,如果Data中设置电话号码,则拨号框中会显示此号码。
  5. ACTION_CALL:直接呼叫Data中所带的号码。
  6. ACTION_ANSWER:接听来电。
  7. ACTION_SEND:由用户指定发送方式进行数据发送操作。
  8. ACTION_SENDTO:系统根据不同的Data类型,通过已注册的对应Application进行数据发送操作。
  9. ACTION_BOOT_COMPLETED:Android系统在启动完毕后发出带有此Action的广播(Broadcast)。
  10. ACTION_TIME_CHANGED:Android系统的时间发生改变后发出带有此Action的广播(Broadcast)。
  11. ACTION_PACKAGE_ADDED:Android系统安装了新的App之后发出带有此Action的广播(Broadcast)。
  12. ACTION_PACKAGE_CHANGED:Android系统中已存在的App发生改变之后(如更新)发出带有此Action的广播(Broadcast)。
  13. ACTION_PACKAGE_REMOVED:Android系统卸载App之后发出带有此Action的广播(Broadcast)。

7.3.12 Intent的Category属性的部分常量值

  1. CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity的执行方式执行。
  2. CATEGORY_PREFERENCE:设置该组件为Preference。
  3. CATEGORY_LAUNCHER:设置为当前应用程序优先级最高的Activity,通常与ACTION_MAIN配合使用。
  4. CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。
  5. CATEGORY_GADGET:设置该组件可以内嵌到另外的Activity中。

7.3.13 Intent的Data属性的部分常量值

  1. tel:号码数据格式,后跟电话号码。 
  2. mailto:邮件数据格式,后跟邮件收件人地址。
  3. smsto:短息数据格式,后跟短信接收号码。
  4. content:内容数据格式,后跟需要读取的内容。 
  5. file:文件数据格式,后跟文件路径。
  6. market:search?q=pname:pkgname:市场数据格式,在Google Market里搜索包名为pkgname的应用。
  7. geo:latitude, longitude:经纬数据格式,在地图上显示经纬度所指定的位置。

7.3.14 Intent的Extras属性的部分常量值

  1. EXTRA_BCC:存放邮件密送人地址的字符串数组。
  2. EXTRA_CC:存放邮件抄送人地址的字符串数组。
  3. EXTRA_EMAIL:存放邮件地址的字符串数组。
  4. EXTRA_SUBJECT:存放邮件主题字符串。
  5. EXTRA_TEXT:存放邮件内容。
  6. EXTRA_KEY_EVENT:以KeyEvent对象方式存放触发Intent的按键。
  7. EXTRA_PHONE_NUMBER:存放调用ACTION_CALL时的电话号码。

7.3.15 Intent的MimeType属性的部分常量值

{“.3gp”, “video/3gpp”},
{“.apk”, “application/vnd.android.package-archive”},
{“.asf”, “video/x-ms-asf”},
{“.avi”, “video/x-msvideo”},
{“.bin”, “application/octet-stream”},
{“.bmp”, “image/bmp”},
{“.c”, “text/plain”},
{“.class”, “application/octet-stream”},
{“.conf”, “text/plain”},
{“.cpp”, “text/plain”},
{“.doc”, “application/msword”},
{“.docx”, “application/vnd.openxmlformats-officedocument.wordprocessingml.document”},
{“.xls”, “application/vnd.ms-excel”},
{“.xlsx”, “application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”},
{“.exe”, “application/octet-stream”},
{“.gif”, “image/gif”},
{“.gtar”, “application/x-gtar”},
{“.gz”, “application/x-gzip”},
{“.h”, “text/plain”},
{“.htm”, “text/html”},
{“.html”, “text/html”},
{“.jar”, “application/Java-archive”},
{“.java”, “text/plain”},
{“.jpeg”, “image/jpeg”},
{“.jpg”, “image/jpeg”},
{“.js”, “application/x-JavaScript”},
{“.log”, “text/plain”},
{“.m3u”, “audio/x-mpegurl”},
{“.m4a”, “audio/mp4a-latm”},
{“.m4b”, “audio/mp4a-latm”},
{“.m4p”, “audio/mp4a-latm”},
{“.m4u”, “video/vnd.mpegurl”},
{“.m4v”, “video/x-m4v”},
{“.mov”, “video/quicktime”},
{“.mp2”, “audio/x-mpeg”},
{“.mp3”, “audio/x-mpeg”},
{“.mp4”, “video/mp4”},
{“.mpc”, “application/vnd.mpohun.certificate”},
{“.mpe”, “video/mpeg”},
{“.mpeg”, “video/mpeg”},
{“.mpg”, “video/mpeg”},
{“.mpg4”, “video/mp4”},
{“.mpga”, “audio/mpeg”},
{“.msg”, “application/vnd.ms-outlook”},
{“.ogg”, “audio/ogg”},
{“.pdf”, “application/pdf”},
{“.png”, “image/png”},
{“.pps”, “application/vnd.ms-powerpoint”},
{“.ppt”, “application/vnd.ms-powerpoint”},
{“.pptx”, “application/vnd.openxmlformats-officedocument.presentationml.presentation”},
{“.prop”, “text/plain”},
{“.rc”, “text/plain”},
{“.rmvb”, “audio/x-pn-realaudio”},
{“.rtf”, “application/rtf”},
{“.sh”, “text/plain”},
{“.tar”, “application/x-tar”},
{“.tgz”, “application/x-compressed”},
{“.txt”, “text/plain”},
{“.wav”, “audio/x-wav”},
{“.wma”, “audio/x-ms-wma”},
{“.wmv”, “audio/x-ms-wmv”},
{“.wps”, “application/vnd.ms-works”},
{“.xml”, “text/plain”},
{“.z”, “application/x-compress”},
{“.zip”, “application/x-zip-compressed”},

7.3部分的内容为转载:CSDN博主「峥嵘life」的原创文章,原文链接:https://blog.csdn.net/wenzhi20102321/article/details/52876648

7.4 Activity之间的跳转

7.4.1 Activity之间的数据传递

Android提供的Intent可以在界面跳转时传递参数。使用Intent传递参数有两种方式,具体如下:

  1. 使用Intent的putExtra()方法传递数据
    putExtra()方法的参数为<key,value>的形式,并且Android系统提供了多个重载的putExtra()方法供不同类型的参数传递
    获取参数:首先通过getIntent()方法获取到Intent对象,然后通过Intent对象的getXxxExtra()方法获取到参数
  2. 使用Bundle类传递数据
    此方法是先将参数通过Bundle对象的putXxx()方法保存在Bundle对象中,同样是<key,value>的形式,然后再通过Intent的putExtras()方法保存到Intent对象中
    获取参数:首先通过getIntent()方法获取到Intent对象,然后通过Intent对象的getExtras()方法获取到Bundle对象,然后通过Intent对象的getXxx()方法获取到参数
  3. 如何传递对象(让对象具有被传递的功能)
    第一种方法:让对象类实现 Serializable 接口,同样通过以上两种方式传递,在另一个Activity中使用 getSerializableExtra("key")获取到对象,须强转为目标对象,此方法基于JVM虚拟机,不兼容Android虚拟机
    第二种方法:让对象类实现 Parcelable 接口,同样通过以上两种方式传递,Android推荐使用,在另一个Activity中使用 getParcelableExtra("key")获取到对象,不用强转为目标对象(使用了泛型),此方法兼容Android虚拟机

7.4.2 Activity之间的数据回传

当我们从MainActivity界面跳转到SecondActivity界面时,在SecondActivity界面进行操作后,当关闭SecondActivity界面时,想要从该界面返回一些数据到MainActivity界面。此时Android系统为我们提供了一些方法用于Activity之间数据的回传。具体如下:

  1. startActivityForResult(intent, REQUEST_CODE)方法:
    该方法跟startActivity()方法一样,也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。
    REQUEST_CODE为请求码,用于在之后的回调中判断数据的来源。
  2. setResult(RESULT_CODE, intent)方法:
    该方法是专门用于向上一个活动返回数据的。
    RESULT_CODE为返回码,用于向上一个活动返回处理结果,该结果可用于在之后的回调中判断数据的来源,一般只使用RESULT_OK或RESULT_CANCELED这两个值;
  3. onActivityResult()方法: 
    由于我们是使用startActivityForResult()方法来启动的Activity,在Activity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在MainActivity中重写这个方法来得到返回数据。

注意1:若使用此方法进行Activity之间数据的传递与回传,则必须同时使用以上3个方法,其中第3个方法是一个用于重写的方法

注意2:新手在使用Intent启动其他的Activity时,需要在AndroidManifest.xml清单文件中进行声明,其中android:exported 属性表示是否支持其它应用调用/启动该组件。

activity_main.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/et_name"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="姓名"/>

    <EditText
        android:id="@+id/et_score"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="分数"/>

    <Button
        android:id="@+id/btn_submit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="提交数据"/>

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:hint="返回的结果"/>

</LinearLayout>

MainActivity.java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name,et_score;
    private Button btn_submit;
    private TextView tv_result;
    public static final int REQUEST_CODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name = findViewById(R.id.et_name);
        et_score = findViewById(R.id.et_score);
        btn_submit  = findViewById(R.id.btn_submit);
        tv_result = findViewById(R.id.tv_result);

        btn_submit.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        String name = et_name.getText().toString();
        Double score = Double.valueOf(et_score.getText().toString());
        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra("name",name);
        intent.putExtra("score", score);
        startActivityForResult(intent, REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE) {
            if (resultCode == SecondActivity.RESULT_CODE) {
                tv_result.setText("返回的结果为:"+data.getStringExtra("result"));
            }
        }
    }
}

activity_second.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".SecondActivity">

    <TextView
        android:id="@+id/tv_data"
        android:layout_width="match_parent"
        android:layout_height="150dp"/>

    <EditText
        android:id="@+id/et_result"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:hint="返回的数据"/>

    <Button
        android:id="@+id/btn_return"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="返回数据"/>

</LinearLayout>

SecondActivity.java代码:

public class SecondActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_data;
    private EditText et_result;
    private Button btn_return;
    public static final int RESULT_CODE = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        tv_data = findViewById(R.id.tv_data);
        et_result = findViewById(R.id.et_result);
        btn_return = findViewById(R.id.btn_return);

        Intent intent = getIntent();
        StringBuffer data = new StringBuffer();
        data.append("姓名:" + intent.getStringExtra("name"));
        data.append("\n分数:" + intent.getDoubleExtra("score", 0));
        tv_data.setText(data);

        btn_return.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra("result", et_result.getText().toString());
        setResult(RESULT_CODE, intent);
        finish(); //用于结束一个Activity的生命周期,finish会调用到onDestory()方法。
    }
}

运行效果:

第8章 Service(服务)

8.1 线程的相关概念

8.1.1 相关概念

  • 程序:为了完成特定任务,用某种语言编写的一组指令集合(一组静态代码)
  • 进程:进程是系统调度与资源分配(CPU、内存)的基本单位,它是程序执行时的一个实例。操作系统会为每个进程分配一段内存空间!程序的依次动态执行,经历代码的加载,执行, 执行完毕的完整过程!
  • 线程:线程是程序执行时的最小单位,是进程的一个执行流,CPU调度和分派的基本单位。每个进程可能有多个线程,线程需要放在一个进程中才能执行,线程由程序负责管理,而进程则由系统进行调度!
  • 多线程的理解:并行执行多条指令,将CPU时间片按照调度算法分配给各个 线程,实际上是分时执行的,只是这个切换的时间很短,用户感觉到"同时"而已!

8.1.2 线程的生命周期

8.1.3  创建线程的三种方式

  • 继承Thread类,并重写run方法
  • 实现Callable接口,并重写call()方法
  • 实现Callable接口,并重写call()方法

详见1:https://www.jianshu.com/p/0977349d20db

详见2:https://blog.csdn.net/chaochao2113/article/details/118861041

8.2 Service与Thread线程的区别

其实他们两者并没有太大的关系,不过有很多朋友经常把这两个混淆了! Thread是线程,程序执行的最小单元,分配CPU的基本单位! 而Service则是Android提供一个允许长时间留驻后台的一个组件,最常见的用法就是做轮询操作!或者想在后台做一些事情,比如后台下载更新、音乐播放等! 记得别把这两个概念混淆! 

8.3 Service的生命周期

8.3.1 Service的生命周期图

8.3.2 Service的相关方法详解

  • onCreate():当Service第一次被创建后立即回调该方法,该方法在整个生命周期 中只会调用一次!
  • onDestory():当Service被关闭时会回调该方法,该方法只会回调一次!
  • onStartCommand(intent,flag,startId):早期版本是onStart(intent,startId), 当客户端调用startService(Intent)方法时会回调,可多次调用StartService方法, 但不会再创建新的Service对象,而是继续复用前面产生的Service对象,但会继续回调 onStartCommand()方法!
  • IBinder onOnbind(intent):该方法是Service都必须实现的方法,该方法会返回一个 IBinder对象,app通过该对象与Service组件进行通信!
  • onUnbind(intent):当该Service上绑定的所有客户端都断开时会回调该方法!

8.3.3 Service的两种使用方式

  • StartService启动Service
    首次启动会创建一个Service实例,依次调用onCreate()和onStartCommand()方法,此时Service 进入运行状态,如果再次调用StartService启动Service,将不会再创建新的Service对象, 系统会直接复用前面创建的Service对象,调用它的onStartCommand()方法!
    但这样的Service与它的调用者无必然的联系,就是说当调用者结束了自己的生命周期, 但是只要不调用stopService,那么Service还是会继续运行的!
    无论启动了多少次Service,只需调用一次StopService即可停掉Service

  • BindService启动Service
    当首次使用bindService绑定一个Service时,系统会实例化一个Service实例,并调用其onCreate()和onBind()方法,然后调用者就可以通过IBinder和Service进行交互了,此后如果再次使用bindService绑定Service,系统不会创建新的Sevice实例,也不会再调用onBind()方法,只会直接把IBinder对象传递给其他后来增加的客户端!
    如果我们解除与服务的绑定,只需调用unbindService(),此时onUnbind和onDestory方法将会被调用!这是一个客户端的情况,假如是多个客户端绑定同一个Service的话,情况如下 当一个客户完成和service之间的互动后,它调用 unbindService() 方法来解除绑定。当所有的客户端都和service解除绑定后,系统会销毁service。(除非service也被startService()方法开启)
    另外,和上面那张情况不同,bindService模式下的Service是与调用者相互关联的,可以理解为 "一条绳子上的蚂蚱",要死一起死,在bindService后,一旦调用者销毁,那么Service也立即终止!
    通过BindService调用Service时调用的Context的bindService的解析 bindService(Intent Service,ServiceConnection conn,int flags)
    service:通过该intent指定要启动的Service
    conn:ServiceConnection对象,用户监听访问者与Service间的连接情况, 连接成功回调该对象中的onServiceConnected(ComponentName,IBinder)方法; 如果Service所在的宿主由于异常终止或者其他原因终止,导致Service与访问者间断开 连接时调用onServiceDisconnected(CompanentName)方法,主动通过unBindService() 方法断开并不会调用上述方法!
    flags:指定绑定时是否自动创建Service(如果Service还未创建), 参数可以是0(不自动创建),BIND_AUTO_CREATE(自动创建)

  • StartService启动Service后bindService绑定
    如果Service已经由某个客户端通过StartService()启动,接下来由其他客户端 再调用bindService()绑定到该Service后调用unbindService()解除绑定最后在 调用bindService()绑定到Service的话,此时所触发的生命周期方法如下:
    onCreate( )->onStartCommand( )->onBind( )->onUnbind( )->onRebind( )
    PS:前提是:onUnbind()方法返回true!!! 这里或许部分读者有疑惑了,调用了unbindService后Service不是应该调用 onDistory()方法么!其实这是因为这个Service是由我们的StartService来启动的 ,所以你调用onUnbind()方法取消绑定,Service也是不会终止的!
    得出的结论: 假如我们使用bindService来绑定一个启动的Service,注意是已经启动的Service!!! 系统只是将Service的内部IBinder对象传递给Activity,并不会将Service的生命周期 与Activity绑定,因此调用unBindService( )方法取消绑定时,Service也不会被销毁!

StartService启动与BindService启动的区别:
①启动方式:前者startService、后者bindService;
②和Activity联系:前者Acitivty销毁,service仍然继续运行,后者跟随Acitivity一起销毁;
③方法调用和数据交换:前者不可以,后者可以;
④回调的周期函数:前者是onStartCommand,后者是onBind
⑤结束方式:前者是stopService或者stopSelf,后者是unbindService。

生命周期的不同:
startService方式:调用startService方法->onCreate->onStartCommand->Servcie运行->调用stopService->Service停止->onDestroy->Service销毁;
bindService方式:调用bindServcie方法->onCreate->onBind->绑定Service->调用unbindService方法->解绑Service->onUnBind->onDestroy->Service销毁。

第9章 BroadcastReceiver(广播接收器) 

9.1 广播与广播接收器

9.1.1 什么是广播

广播(Broadcast)是 Android 系统中应用程序间通信的手段。
当有特定事件发生时,例如有来电、有短信、电池电量变化等事件发生时,Android 系统都会产生特定的 Intent 对象并且自动进行广播,而针对特定事件注册的 BroadcastReceiver 会接收到这些广播,并获取 Intent 对象中的数据进行处理。
在广播 Intent 对象时可以指定用户权限,以此限制仅有获得了相应权限的 BroadcastReceiver 才能接收并处理对应的广播。

9.1.2 什么是广播接收器 

        BroadcastReceiver翻译为广播接收者,它是一个系统全局的监听器,用于监听系统全局的Broadcast消息,所以它可以很方便的进行系统组件之间的通信。

  BroadcastReceiver虽然是一个监听器,但是它和之前用到的OnXxxListener不同,那些只是程序级别的监听器,运行在指定程序的所在进程中,当程序退出的时候,OnXxxListener监听器也就随之关闭了,但是BroadcastReceiver属于系统级的监听器,它拥有自己的进程,只要存在与之匹配的Broadcast被以Intent的形式发送出来,BroadcastReceiver就会被激活。

  虽然同属Android的四大组件,BroadcastReceiver也有自己独立的声明周期,但是和Activity、Service又不同。当在系统注册一个BroadcastReceiver之后,每次系统以一个Intent的形式发布Broadcast的时候,系统都会创建与之对应的BroadcastReceiver广播接收者实例,并自动触发它的onReceive()方法,当onReceive()方法被执行完成之后,BroadcastReceiver的实例就会被销毁。虽然它独自享用一个单独的进程,但也不是没有限制的,如果BroadcastReceiver.onReceive()方法不能在10秒内执行完成,Android系统就会认为该BroadcastReceiver对象无响应,然后弹出ANR(Application No Response)对话框,所以不要在BroadcastReceiver.onReceive()方法内执行一些耗时的操作。

  如果需要根据广播内容完成一些耗时的操作,一般考虑通过Intent启动一个Service来完成该操作,而不应该在BroadcastReceiver中开启一个新线程完成耗时的操作,因为BroadcastReceiver本身的生命周期很短,可能出现的情况是子线程还没有结束,BroadcastReceiver就已经退出的情况,而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程,而空线程无异是优先级最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。

9.2 发送广播的类型

9.2.1 标准广播

它是完全异步的,也就是说,在逻辑上,当一个Broadcast被发出之后,所有的与之匹配的BroadcastReceiver都同时接收到Broadcast。优点是传递效率比较高,但是也有缺点,就是一个BroadcastReceiver不能影响其他响应这条Broadcast的BroadcastReceiver。

  • sendBroadcast():发送普通广播。

9.2.2 有序广播

它是同步执行的,就是说有序广播的接收器将会按照预先声明的优先级(-1000~1000)依次接受Broadcast,优先级越高,越先被执行。因为是顺序执行,所有优先级高的接收器,可以把执行结果传入下一个接收器中,也可以终止Broadcast的传播(通过abortBroadcast()方法),一旦Broadcast的传播被终止,优先级低于它的接收器就不会再接收到这条Broadcast了。

  • sendOrderedBroadcast():发送有序广播。

注:有序广播能被设置了高优先级的广播接收者的 abortBroadcast() 截断。

9.3 如何使用BroadcastReceiver

        BroadcastReceiver本质上还是一个监听器,所以使用BroadcastReceiver的方法也是非常简单,只需要继承BroadcastReceiver,在其中重写onReceive(Context context,Intent intent)即可。一旦实现了BroadcastReceiver,并部署到系统中后,就可以在系统的任何位置,通过sendBroadcast、sendOrderedBroadcast方法发送Broadcast给这个BroadcastReceiver。

        但是仅仅继承BroadcastReceiver和实现onReceive()方法是不够的,同为Android系统组件,它也必须在Android系统中注册,注册一个BroadcastReceiver有两种方式:

9.3.1 静态注册(不建议使用)

使用清单文件AndroidManifest.xml注册,在<application/>节点中,使用<receiver/>节点注册,并用android:name属性中指定注册的BroadcastReceiver对象,一般还会通过<Intent-filter/>指定<action/>和<category/>,并在<Intent-filter/>节点中通过android:priority属性设置BroadcastReceiver的优先级,在-1000~1000范围内,数值越到优先级越高。

        <receiver android:name=".MyBroadcastReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="com.example.mybroadcastreceiver.staticReceiver"/>
            </intent-filter>
        </receiver>

广播接收者接收Android 8.0及以上的静态注册的广播:

intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);

intent.setComponent(new ComponentName("com.example.mybroadcastreceiver","com.example.mybroadcastreceiver.MyBroadcastReceiver"));
//第一个参数为广播接收者的包名,第二个参数为广播接收者的类名

intent.setPackage("com.example.mybroadcastreceiver");
//参数为广播接收者的报名

9.3.2 动态注册

就是在Java代码中指定 IntentFilter,然后添加不同的 Action 即可,想监听什么广播就写什么 Action,最后通过registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 方法进行注册,另外动态注册的广播,一定要调用unregisterReceiver() 方法撤销广 播的注册。

        MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.mybroadcastreceiver.dynamicReceiver");
//参数为标识名,用于发送广播时识的别时
        registerReceiver(myBroadcastReceiver, intentFilter);

9.3.3 广播接收总结

  • 广播接收的特点:
    ①广播接收器接收到相应广播后,会自动回调 onReceive() 方法
    ②一般情况下,onReceive方法会涉及 与 其他组件之间的交互,如发送Notification、启动Service
    ③默认情况下,广播接收器运行在 UI 线程,因此,onReceive()方法不能执行耗时操作(10s的时间),否则将导致ANR
  • 实现自定义的广播接收:
    ①继承BroadcastReceiver类,实现自定义的广播接收者,实现onReceive(Context context, Intent intent)方法用于接受广播
    ②注册BroadcastReceiver,静态注册 / 动态注册
    sendBroadcast(Intent intent); 发送广播
    ④在onReceive(Context context, Intent 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/btn_static"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="静态注册广播"
        android:textSize="30sp"
        app:layout_constraintBottom_toTopOf="@+id/btn_dynamic"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_dynamic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="动态注册广播"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_static" />

</androidx.constraintlayout.widget.ConstraintLayout>

MyBroadcastReceiver.java代码:

public class MyBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "zhumeng";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e(TAG, "接收到广播消息‘"+intent.getStringExtra("msg")+"’");
    }
}

MainActivity.java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MyBroadcastReceiver myBroadcastReceiver;

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

        Button btn_static = findViewById(R.id.btn_static);
        Button btn_dynamic = findViewById(R.id.btn_dynamic);

        btn_static.setOnClickListener(this);
        btn_dynamic.setOnClickListener(this);
        
        initReceiver();
    }

    private void initReceiver() { //动态注册广播接收者
        myBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.mybroadcastreceiver.dynamicReceiver");
        registerReceiver(myBroadcastReceiver, intentFilter);
    }

    @Override
    public void onClick(View view) {
        Log.e("zhumeng", "单击事件发生");
        Intent intent;
        switch (view.getId()){
            case R.id.btn_static:
                intent = new Intent();
                intent.setAction("com.example.mybroadcastreceiver.staticReceiver");
//                intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
//                intent.setComponent(new ComponentName("com.example.mybroadcastreceiver","com.example.mybroadcastreceiver.MyBroadcastReceiver"));
                intent.setPackage("com.example.mybroadcastreceiver");
                intent.putExtra("msg","静态注册的广播");
                sendBroadcast(intent);
                break;
            case R.id.btn_dynamic:
                intent = new Intent();
                intent.setAction("com.example.mybroadcastreceiver.dynamicReceiver");
                intent.putExtra("msg","动态注册的广播");
                sendBroadcast(intent);
                break;
        }
    }

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

运行效果图:

第10章 Fragment(碎片)

10.1 Fragment是什么

10.1.1 Fragment的历史

 Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿。

10.1.2 Fragment是什么

Fragment(碎片)是一种嵌入在Activity中的UI片段,它可以用来描述Activity中的一部分布局。如果Activity界面布局中的控件比较多比较复杂,那么Activity管理起来就很麻烦,我们可以使用Fragment把屏幕划分成几个片段,进行模块化的管理,从而使程序更加合理和充分地利用屏幕的空间。
一个Activity中可以包含多个Fragment,一个Fragment也可以在多个Activity中使用,如果在Activity中有多个相同的业务模块,则可以复用Fragment。
想想,如果一个很大的界面,我们就一个布局,写起界面来会有多麻烦,而且如果组件多的话是管理起来也很麻烦!而使用Fragment我们可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理!从而可以更加方便的在运行过程中动态地更新Activity的用户界面!另外Fragment并不能单独使用,他需要嵌套在Activity中使用,尽管他拥有自己的生命周期,但是还是会受到宿主Activity的生命周期的影响。比如,当Activity暂停时,其中的所有片段也会暂停;当Activity被销毁时,所有片段也会被销毁。 不过,当Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个片段,如添加或移除它们。 当您执行此类片段事务时,您也可以将其添加到由Activity管理的返回栈,Activity中的每个返回栈条目都是一条已发生片段事务的记录。返回栈让用户可以通过按返回按钮撤消片段事务(后退)。
下面是一个典型的示例演示如何让两个由碎片定义的UI模块,在为平板设计的活动中组合,在为手持设备设计的活动中分离。

当运行在在平板尺寸的设备上,这个应用程序可以在活动A中嵌入两个碎片。在手机设备屏幕上,由于没有足够的空间,活动A仅包含有文章列表的碎片,当用户点击文章时,启动包含第二个碎片的活动B来阅读文章。 

10.2 Fragment的生命周期

10.2.1 Fragment的生命周期简介

Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。Fragment状态与Activity类似,也存在如下4种状态:

  • 运行:当前Fmgment位于前台,用户可见,可以获得焦点。
  • 暂停:其他Activity位于前台,该Fragment依然可见,只是不能获得焦点。
  • 停止:该Fragment不可见,失去焦点。
  • 销毁:该Fragment被完全删除,或该Fragment所在的Activity被结束。

10.2.2 Fragment的生命周期状态 

Fragment的生命周期看起来和Activity的生命周期类似,但是多了一些,因为Fragment的生命周期是和其关联的Activity有关。

(1)onAttach(Context context):在Fragment和Activity关联上的时候调用,且仅调用一次。在该回调中我们可以将context转化为Activity保存下来,从而避免后期频繁调用getAtivity()获取Activity的局面,避免了在某些情况下getAtivity()为空的异常(Activity和Fragment分离的情况下)。同时也可以在该回调中将传入的Arguments提取并解析,在这里强烈推荐通过setArguments给Fragment传参数,因为在应用被系统回收时Fragment不会保存相关属性,具体之后会讲解。

(2)onCreate(Bundle savedInstanceState):在最初创建Fragment的时候会调用,和Activity的onCreate类似。

(3)View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState):在准备绘制Fragment界面时调用,返回值为Fragment要绘制布局的根视图,当然也可以返回null。注意使用inflater构建View时一定要将attachToRoot指明false,因为Fragment会自动将视图添加到container中,attachToRoot为true会重复添加报错。onCreateView并不是一定会被调用,当添加的是没有界面的Fragment就不会调用,比如调用FragmentTransaction的add(Fragment fragment, String tag)方法。

(4)onActivityCreated(Bundle savedInstanceState):在Activity的onCreated执行完时会调用。

(5)onStart():Fragment对用户可见的时候调用,前提是Activity已经started。

(6)onResume():Fragment和用户之前可交互时会调用,前提是Activity已经resumed。

(7)onPause():Fragment和用户之前不可交互时会调用。

(8)onStop():Fragment不可见时会调用。

(9)onDestroyView():在移除Fragment相关视图层级时调用。

(10)onDestroy():最终清楚Fragment状态时会调用。

(11)onDetach():Fragment和Activity解除关联时调用。

10.2.3 Fragment的生命周期调用

1)创建Fragment

onAttach() —> onCreate() —> onCreateView() —> onActivityCreated() —> onStart() —> onResume()

2)按下Home键回到桌面 / 锁屏

onPause() —> onStop()

3)从桌面回到Fragment / 解锁

onStart() —> onResume()

4)切换到其他Fragment

onPause() —> onStop() —> onDestroyView()

5)切换回本身的Fragment

onCreateView() —> onActivityCreated() —> onStart() —> onResume()

6) 按下Back键退出

onPause() —> onStop() —> onDestroyView() —> onDestroy() —> onDetach()

注意:Fragment必须依赖于Activity,理解为Activity的寄生虫

详见1:Fragment生命周期 - 第壹时间 - 博客园 (cnblogs.com) 

详见2:Fragment的生命周期_杭州小白的博客-程序员宅基地_fragment生命周期

10.3 Fragment用法

10.3.1 Fragment的静态加载

实现步骤:

  • Step 1:自定义一个Fragment类,需要继承Fragment或者他的子类,重写onCreateView()方法,在该方法中调用inflater.inflate()方法加载Fragment的布局文件并实例化view对象,接着返回加载的view对象
    public class BlankFragment1 extends Fragment {
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_blank1, container, false);
            Button btn = view.findViewById(R.id.btn);
            TextView tv = view.findViewById(R.id.tv);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    tv.setText("Yes,I am,and you!");
                }
            });
            return view;
        }
    }
  • Step 2:若第1步在Java目录下New Fragment类的话就会自动生成Fragment的布局(否则需要自己定义),就是fragment显示内容的
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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=".BlankFragment1"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_blank_fragment"
            android:textSize="30sp"/>
    
        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="获取回答" />
    
    </LinearLayout>
  • Step 3:在需要加载Fragment的Activity对应的布局文件中添加fragment的标签,必需写name属性且是全限定类名哦,就是要包含Fragment的包名,还需要有一个id属性
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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">
    
        <fragment
            android:id="@+id/fragment1"
            android:name="com.example.fragmentbase.BlankFragment1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    
    </LinearLayout>
  • Step 4:Activity在onCreate( )方法中调用setContentView()加载布局文件即可

运行效果图: 

注:若要添加多个Fragment就直接创建多个继承Fragment的类,然后在activity_main.xml中通过name属性引用即可

10.3.2 Fragment的动态加载

实现步骤:

  • Step 1:自定义一个Fragment类,需要继承Fragment或者他的子类,重写onCreateView()方法,在该方法中调用inflater.inflate()方法加载Fragment的布局文件并实例化view对象,接着返回加载的view对象
  • Step 2:若第1步在Java目录下New Fragment类的话就会自动生成Fragment的布局(否则需要自己定义),就是fragment显示内容的
  • Step 3:在需要加载Fragment的Activity对应的布局文件中添加fragment的标签,必需写name属性且是全限定类名哦,就是要包含Fragment的包名,还需要有一个id属性
  • Step 4:Activity在onCreate( )方法中调用setContentView()加载布局文件即可
  • Step 5:通过getFragmentManager()获得FragmentManager对象fragmentManager;
  • Step 6:通过fragmentManager.beginTransaction()获得FragmentTransaction对象fragmentTransaction;
  • Step 7:调用fragmentTransaction的add()方法或者replace()方法加载Fragment;
    第一个参数:要传入的布局容器
    第二个参数:要加载的fragment对象
  • Step 8:在前面的基础上还需调用commit()方法提交事务。当然还有其他的方法如remove()等

注意:若点击back键按照添加过程退出时需要在commit()方法之前执行transation.addToBackStack(null);

MainActivity.java代码:其中BlankFragment1类和ItemFragment1类由AS自动生成

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn1 = findViewById(R.id.btn1);
        Button btn2 = findViewById(R.id.btn2);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn1:
                replaceFragment(new BlankFragment1());
                break;
            case R.id.btn2:
                replaceFragment(new ItemFragment1());
                break;
        }
    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fragment1, fragment);

        transaction.addToBackStack(null);

        transaction.commit();
    }
}

运行效果图: 

10.4 Fragment的通信

10.4.1 Fragment与activity通信——原生Bundle

MainActivity.java代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn1 = findViewById(R.id.btn1);
        Button btn2 = findViewById(R.id.btn2);
        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn1:
                BlankFragment blankFragment = new BlankFragment();
                Bundle bunlsle = new Bundle();
                bunlsle.putString("name", "张三");
                bunlsle.putInt("age", 18);
                blankFragment.setArguments(bunlsle);
                replaceFragment(blankFragment);
                break;
            case R.id.btn2:
                replaceFragment(new ItemFragment());
                break;
        }
    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.fragment1, fragment);

        transaction.addToBackStack(null);

        transaction.commit();
    }
}

获取参数:在BlankFragment类中任何位置都可以通过Bundle bundle = getArguments()的bundle获取到通过blankFragment.setArguments(bunlsle)传递到该类的参数

运行效果图: 

10.4.2 Fragment与activity通信——Java类的接口

实现步骤:

  • 新建用于传递消息的接口IFragmentCallback
    public interface IFragmentCallback {
        void sendMsgToActivity(String msg);
        String getMsgFromActivity(String msg);
    }
  • 在需要进行触发消息传递的类BlankFragment中创建接口的实例并为其编写set方法public void setiFragmentCallback(IFragmentCallback iFragmentCallback),然后在onCreateView方法中编写监听器用于触发发送、获取消息
    public class BlankFragment extends Fragment {
    
        private static final String TAG = "TAG";
        private IFragmentCallback iFragmentCallback;
        private View view;
    
        public void setiFragmentCallback(IFragmentCallback iFragmentCallback) {
            this.iFragmentCallback = iFragmentCallback;
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            if (view == null) {
                view = inflater.inflate(R.layout.fragment_blank, container, false);
            }
            Button btn3 = view.findViewById(R.id.btn3);
            Button btn4 = view.findViewById(R.id.btn4);
            btn3.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    iFragmentCallback.sendMsgToActivity("hello, I'm from Fragment.");
                }
            });
            btn4.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    String msg = iFragmentCallback.getMsgFromActivity("hello, I'm Fragment.");
                    Toast.makeText(BlankFragment.this.getContext(), msg, Toast.LENGTH_SHORT).show();
                }
            });
            return view;
        }
    }
  • 在目标布局的方法中用BlankFragment对象调用setiFragmentCallback()方法进行对接口实例化
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button btn1 = findViewById(R.id.btn1);
            Button btn2 = findViewById(R.id.btn2);
            btn1.setOnClickListener(this);
            btn2.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn1:
                    BlankFragment blankFragment = new BlankFragment();
                    Bundle bunlsle = new Bundle();
                    bunlsle.putString("name", "张三");
                    bunlsle.putInt("age", 18);
                    blankFragment.setArguments(bunlsle);
                    blankFragment.setiFragmentCallback(new IFragmentCallback() {
                        @Override
                        public void sendMsgToActivity(String msg) {
                            Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
                        }
    
                        @Override
                        public String getMsgFromActivity(String msg) {
                            return "I'm from Activity.";
                        }
                    });
                    replaceFragment(blankFragment);
                    break;
                case R.id.btn2:
                    replaceFragment(new ItemFragment());
                    break;
            }
        }
    
        private void replaceFragment(Fragment fragment) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            transaction.replace(R.id.fragment1, fragment);
    
            transaction.addToBackStack(null);
    
            transaction.commit();
        }
    }

fragment_blank.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".BlankFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送数据"
        android:layout_marginTop="50dp"/>

    <Button
        android:id="@+id/btn4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取数据"
        android:layout_marginTop="100dp"/>

</FrameLayout>

运行效果图:

10.5 Fragment与ViewPager2的联合应用

10.5.1 ViewPage2的使用

使用步骤:

  1. 添加依赖
    在build.gradle文件中添加implementation 'androidx.viewpager2:viewpager2:1.0.0'
  2. 新建item_page.xml并编写其内容
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="hello"
            android:layout_centerInParent="true"
            android:textSize="30sp"/>
    
    </RelativeLayout>
  3. 在activity_main.xml中使用ViewPager2
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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">
    
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/vp"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </LinearLayout>
  4. 自定义ViewPagerAdapter类继承RecyclerView.Adapter<VH>。其中VH是自定义继承自RecyclerView.ViewHolder的ViewPagerHolder类(可以写成匿名内部类)
    public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewPagerHolder> {
    
        private ArrayList<String> data = new ArrayList<>();
    
        public ViewPagerAdapter(ArrayList<String> data) {
            this.data = data;
        }
    
        @NonNull
        @Override
        public ViewPagerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            ViewPagerHolder viewPagerHolder = new ViewPagerHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_pager, parent, false));
            return viewPagerHolder;
        }
    
        @Override
        public void onBindViewHolder(@NonNull ViewPagerHolder holder, int position) {
            holder.textView.setText(data.get(position).toString());
        }
    
        @Override
        public int getItemCount() {
            return data.size();
        }
    
        class ViewPagerHolder extends RecyclerView.ViewHolder{
    
            TextView textView;
    
            public ViewPagerHolder(@NonNull View itemView) {
                super(itemView);
                textView = itemView.findViewById(R.id.tv);
            }
        }
    }
  5. 在Java中装填数据和使用ViewPager2

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ArrayList<String> data = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
                data.add(new String("你好,我是第"+i+"个Pager"));
            }
    
            ViewPager2 viewPager = findViewById(R.id.vp);
            ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(data);
            viewPager.setAdapter(pagerAdapter);
        }
    }

运行效果图:

10.5.2 Fragment与ViewPager2实例WeChat首页)

FragmentPagerAdapter与FragmentStatePagerAdapter介绍

  • FragmentPagerAdapter
    FragmentPagerAdapter继承PagerAdapter。使用FragmentPagerAdapter 时,Fragment对象会一直存留在内存中,所以当有大量的显示页时,就不适合用FragmentPagerAdapter了,FragmentPagerAdapter 适用于只有少数的page情况,像选项卡。
  • FragmentStatePagerAdapter
    FragmentStateAdapter继承RecyclerView.Adapter。如果page比较多,这个时候你可以考虑使用FragmentStatePagerAdapter ,当使用FragmentStatePagerAdapter 时,如果Fragment不显示,那么Fragment对象会被销毁,(滑过后会保存当前界面,以及下一个界面和上一个界面(如果有),最多保存3个,其他会被销毁掉)但在回调onDestroy()方法之前会回调onSaveInstanceState(Bundle outState)方法来保存Fragment的状态,下次Fragment显示时通过onCreate(Bundle savedInstanceState)把存储的状态值取出来,FragmentStatePagerAdapter 比较适合页面比较多的情况,像一个页面的ListView 。
  • 如上所述,使用FragmentStatePagerAdapter更省内存,但是销毁和新建也是需要时间的。一般情况下,如果你是制作主页面,用3、4个Tab,那么可以考虑使用FragmentPagerAdapter,如果你是用于ViewPager展示数量较多的条目时,那么建议使用FragmentStatePagerAdapter。

WeChat首页的滑动窗口实现:

  1. 在activity_main.xml中使用ViewPager2
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    
    </LinearLayout>
  2. 新建BlankFragment继承于Fragment并生成fragment_blank.xml文件
  3. 编写fragment_blank.xml文件
    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout 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=".BlankFragment">
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/hello_blank_fragment"
            android:textSize="30sp"
            android:gravity="center"/>
    
    </FrameLayout>
  4. 编写BlankFragment类
    public class BlankFragment extends Fragment {
    
        private static final String ARG_PARAM1 = "param1";
    
        private String mParam1;
    
        private View fragmentView;
    
        public BlankFragment() {
            // Required empty public constructor
        }
    
        public static BlankFragment newInstance(String param1) {
            BlankFragment fragment = new BlankFragment();
            Bundle args = new Bundle();
            args.putString(ARG_PARAM1, param1);
            fragment.setArguments(args);
            return fragment;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (getArguments() != null) {
                mParam1 = getArguments().getString(ARG_PARAM1);
            }
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            if (fragmentView == null) {
                fragmentView = inflater.inflate(R.layout.fragment_blank, container, false);
            }
            TextView textView = fragmentView.findViewById(R.id.textView);
            textView.setText(mParam1);
            return fragmentView;
        }
    }
  5. 新建BlankFragment类的适配器MyFragmentPagerAdapter继承自FragmentStatePagerAdapter(LifeCycle是GoogleApp架构中推荐的一个组件,作用就是用来监听Activity与Fragment的生命周期变化。)
    public class MyFragmentPagerAdapter extends FragmentStateAdapter {
    
        ArrayList<Fragment> fragmentList = new ArrayList<>();
    
        public MyFragmentPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, ArrayList<Fragment> fragmentList) {
            super(fragmentManager, lifecycle);
            this.fragmentList = fragmentList;
        }
    
        @NonNull
        @Override
        public Fragment createFragment(int position) {
            return fragmentList.get(position);
        }
    
        @Override
        public int getItemCount() {
            return fragmentList.size();
        }
    }
  6. 编写MainActivity类
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ViewPager2 viewPager = findViewById(R.id.viewpager);
    
            ArrayList<Fragment> fragmentList = new ArrayList<>();
            fragmentList.add(BlankFragment.newInstance("微信聊天"));
            fragmentList.add(BlankFragment.newInstance("通讯录"));
            fragmentList.add(BlankFragment.newInstance("发现"));
            fragmentList.add(BlankFragment.newInstance("我的主页"));
            MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(),getLifecycle(),fragmentList);
            viewPager.setAdapter(myFragmentPagerAdapter);
        }
    }
  7. 运行效果图

 WeChat首页的雏形实现:

  1. 准备图片并在src/main/res/drawable/新建资源,资源名为tab_wexin.xml(同理新建4个用于选择的资源并编写)
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/weixin_pressed" android:state_pressed="true"/>
        <item android:drawable="@drawable/weinxin_normal"/>
    </selector>
  2. 在src/main/res/color/新建tab_color.xml资源,作为颜色选择器
    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:color="#09C15F" android:state_selected="true"/>
        <item android:color="@color/black"/>
    </selector>
  3. 在src/main/res/layout/新建bottom_layout.xml并编写内容
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="55dp"
        android:background="@color/gray">
    
        <LinearLayout
            android:id="@+id/tab_wechat"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_wechat_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_wechat"/>
            <TextView
                android:id="@+id/tab_wechat_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="微信"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/tab_contact"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_contact_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_contact"/>
            <TextView
                android:id="@+id/tab_contact_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="通讯录"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/tab_find"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_find_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_find"/>
            <TextView
                android:id="@+id/tab_find_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="发现"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
        <LinearLayout
            android:id="@+id/tab_profile"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">
            <ImageView
                android:id="@+id/tab_profile_iv"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:background="@drawable/tab_profile"/>
            <TextView
                android:id="@+id/tab_profile_tv"
                android:layout_width="32dp"
                android:layout_height="wrap_content"
                android:text="我"
                android:gravity="center"
                android:textColor="@color/tab_color"/>
        </LinearLayout>
    
    </LinearLayout>
  4. 在activity_main.xml中用include标签引入bottom布局
    <include layout="@layout/bottom_layout"/>
  5. 编写MainActivity.java类
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private ViewPager2 viewPager;
        private LinearLayout llWechat, llContact, llFind, llProfile;
        private ImageView ivWechat, ivContact, ivFind, ivProfile, ivCurrent;
        private TextView tvWechat, tvContact, tvFind, tvProfile, tvCurrent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initFragmentPage();
    
            initTabView();
    
        }
    
        private void initTabView() {
            llWechat = findViewById(R.id.tab_wechat);
            ivWechat = findViewById(R.id.tab_wechat_iv);
            tvWechat = findViewById(R.id.tab_wechat_tv);
    
            llContact = findViewById(R.id.tab_contact);
            ivContact = findViewById(R.id.tab_contact_iv);
            tvContact = findViewById(R.id.tab_contact_tv);
    
            llFind = findViewById(R.id.tab_find);
            ivFind = findViewById(R.id.tab_find_iv);
            tvFind = findViewById(R.id.tab_find_tv);
    
            llProfile = findViewById(R.id.tab_profile);
            ivProfile = findViewById(R.id.tab_profile_iv);
            tvProfile = findViewById(R.id.tab_profile_tv);
    
            ivWechat.setSelected(true);
            tvWechat.setSelected(true);
            ivCurrent = ivWechat;
            tvCurrent = tvWechat;
    
            llWechat.setOnClickListener(this);
            llContact.setOnClickListener(this);
            llFind.setOnClickListener(this);
            llProfile.setOnClickListener(this);
        }
    
        private void initFragmentPage() {
            viewPager = findViewById(R.id.viewpager);
    
            ArrayList<Fragment> fragmentList = new ArrayList<>();
            fragmentList.add(BlankFragment.newInstance("微信聊天"));
            fragmentList.add(BlankFragment.newInstance("通讯录"));
            fragmentList.add(BlankFragment.newInstance("发现"));
            fragmentList.add(BlankFragment.newInstance("我的主页"));
            MyFragmentPagerAdapter myFragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager(),getLifecycle(),fragmentList);
            viewPager.setAdapter(myFragmentPagerAdapter);
    
            viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                }
    
                @Override
                public void onPageSelected(int position) {
                    super.onPageSelected(position);
                    switch (position){
                        case 0:
                            changeTab(ivWechat, tvWechat);
                            break;
                        case 1:
                            changeTab(ivContact, tvContact);
                            break;
                        case 2:
                            changeTab(ivFind, tvFind);
                            break;
                        case 3:
                            changeTab(ivProfile, tvProfile);
                            break;
                    }
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
                    super.onPageScrollStateChanged(state);
                }
            });
        }
    
        private void changeTab(ImageView imageView, TextView textView) {
            ivCurrent.setSelected(false);
            tvCurrent.setSelected(false);
            imageView.setSelected(true);
            textView.setSelected(true);
            ivCurrent = imageView;
            tvCurrent = textView;
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.tab_wechat:
                    changeTab(ivWechat, tvWechat);
                    viewPager.setCurrentItem(0);
                    break;
                case R.id.tab_contact:
                    changeTab(ivContact, tvContact);
                    viewPager.setCurrentItem(1);
                    break;
                case R.id.tab_find:
                    changeTab(ivFind, tvFind);
                    viewPager.setCurrentItem(2);
                    break;
                case R.id.tab_profile:
                    changeTab(ivProfile, tvProfile);
                    viewPager.setCurrentItem(3);
                    break;
            }
        }
    }
  6. 运行

运行效果图: 

第11章 数据存储

11.1 数据存储的选择

11.1.1 数据存储介绍

SP:保存配置信息或记忆信息(如记住密码)

SQLite:数据库

Room(新):数据库,更简洁

11.1.2 SP特点介绍

SP为SharedPreference(首选项)的缩写

首选项用于存储软件的配置信息:
window        .ini
android        .xml

自动登录,记住密码,主题记录等等
首选项不能存在太多的信息。特点:当程序运行首选项里面的数据会全部加载进内容(map的格式,key--value)

注意:很小、很简单的数据可以保存到首选项SP里面去

11.2 SP的简单使用

11.2.1 保存数据

保存目录:/data/data/包名/shared_prefs/文件名.xml

@override
public SharedPreferences getSharedPreferences(String name,int mode){
    return mBase.getSharedpreferences(name, mode);
}

参数1:SP文件的名字
参数2:SP保存的模式
        常规(每次保存都会更新/覆盖):Context.MODE_PRIVATE
        追加(每次保存都会追加到后面):Context.MODE_APPEND

11.2.2 获取数据

总代码:

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

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

    //保存数据到SP中
    public void saveToSP(View view) {
        SharedPreferences sp = getSharedPreferences("SPName", Context.MODE_PRIVATE);
        sp.edit().putString("book1","《九阳神功》").apply();
    }

    //获取SP中的数据
    public void getSPData(View view) {
        SharedPreferences sp = getSharedPreferences("SPName", Context.MODE_PRIVATE);
        String book1 = sp.getString("book1", "数据值不存在");
        Toast.makeText(this, book1, Toast.LENGTH_SHORT).show();
    }
}

运行效果:

11.2.3 SP实战(记住密码和自动登录)

注意:由于时间关系,没有考虑程序的健壮性,该项目有Bug,在点击自动登录时需要连勾保存密码,在取消勾选保存密码时需要取消勾选自动登录,该功能只需要实现复选框改变的监听函数即可。

activity_main代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:padding="50dp"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="用户名:" />

        <EditText
            android:id="@+id/et_username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="请输入用户名"/>
    </LinearLayout>

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="密    码:" />

        <EditText
            android:id="@+id/et_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="请输入密码"
            android:inputType="textPassword"/>
    </LinearLayout>

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

        <CheckBox
            android:id="@+id/cb_rememberPassword"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="记住密码"/>

        <CheckBox
            android:id="@+id/cb_autoLogin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="自动登录"/>
    </LinearLayout>

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

        <Button
            android:id="@+id/btn_register"
            android:layout_width="wrap_content"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="注册"/>

        <Button
            android:id="@+id/btn_login"
            android:layout_width="wrap_content"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="登录"/>
    </LinearLayout>
</LinearLayout>

MainActivity代码:

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private SharedPreferences configSP;
    private EditText et_username, et_password;
    private CheckBox cb_rememberPassword, cb_autoLogin;
    private Button btn_register, btn_login;

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

        init();

        //若是打勾了,则下一次登录是回显数据
        boolean rememberPassword = configSP.getBoolean("rememberPassword", false);
        boolean autoLogin = configSP.getBoolean("autoLogin", false);
        if (rememberPassword){
            et_username.setText(configSP.getString("username", ""));
            et_password.setText(configSP.getString("password", ""));
            cb_rememberPassword.setChecked(true);
        }
        if (autoLogin){
            cb_autoLogin.setChecked(true);
            Toast.makeText(this, "自动登录成功", Toast.LENGTH_SHORT).show();
        }
    }

    private void init() {
        configSP = getSharedPreferences("config", Context.MODE_PRIVATE);

        et_username = findViewById(R.id.et_username);
        et_password = findViewById(R.id.et_password);
        cb_rememberPassword = findViewById(R.id.cb_rememberPassword);
        cb_autoLogin = findViewById(R.id.cb_autoLogin);
        btn_register = findViewById(R.id.btn_register);
        btn_login = findViewById(R.id.btn_login);

        MyOnClickListener myOnClickListener = new MyOnClickListener();
        btn_register.setOnClickListener(myOnClickListener);
        btn_login.setOnClickListener(myOnClickListener);
    }
    public class MyOnClickListener implements View.OnClickListener{

        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn_register:
                    break;
                case R.id.btn_login:
                    login(view);
                    break;
            }
        }
    }

    private void login(View view) {
        String username = et_username.getText().toString().trim();
        String password = et_password.getText().toString().trim();
        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)){
            Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
        } else {
            if (TextUtils.equals("admin", username) && TextUtils.equals("123456", password)){
                SharedPreferences.Editor edit = configSP.edit(); //获取到编辑
                if (cb_rememberPassword.isChecked()){ //是否需要保存密码
                    edit.putString("username", username);
                    edit.putString("password", password);
                    edit.putBoolean("rememberPassword", true);
                    edit.apply();
                }
                if (cb_autoLogin.isChecked()){ //是否需要自动登录
                    edit.putBoolean("autoLogin", true);
                    edit.apply();
                }Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show();

            } else {
                Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

运行效果图:

11.3 SQLite的简单使用

11.3.1 SQLite介绍

SQLite为关系型数据库
嵌入式的数据库,体积小功能强大,几十kb,嵌入式设备上:计算器、手表等。
在Android平台上,集成了一个嵌入式关系型数据库——SQLite,SQLite3支持NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型,虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s)等数据类型,只不过在运算或保存时会转成对应的五种数据类型。

SQLite最大的特点是你可以把各种类型的数据保存到字段中,但是主键只能是Integer类型的。Sqlite数据库一般要求主键是_id,当然也可以是id。
Android里面的数据库是由底层的sqilte.c的代码来动态生成的。

11.3.2 SQLite的可视化工具

安装SQLite的可视化工具,将SQLite数据库导出,然后将导出的SQLite数据库拖入可视化工具中即可

SQLiteExpert官网链接:SQLite administration | SQLite Expert

更多SQLite的可视化工具:(4条消息) sqlite 免费的可视化工具_Lucas__liu的博客-程序员宅基地_sqlite可视化工具

11.3.3 SQLiteOpenHelper类的方法介绍

方法名

作用

onCreate()

创建数据库,一个Helper类只会创建一次表

onUpgrade()

升级数据库

close()

关闭所有打开的数据库对象

execSQL()

执行增删改操作的SQL语句

rawQuery() 执行查询操作的SQL语句

insert()

插入数据

delete()

删除数据

query() 查询数据

update()

修改数据

getWritableDatabase()

创建或打开可写的数据库

getReadableDatabase()

创建或打开可读的数据

11.3.4 SQLite数据库的操作

1、创建SQLite数据库的辅助类(包括创库和创表)

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.Nullable;

//MySqliteOpenHelper是工具类 --> 单例模式(1.构造函数私有化  2.对外提供函数)

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    private static MySQLiteOpenHelper mySQLiteHelper = null;

    //2.对外提供函数
    public static synchronized MySQLiteOpenHelper getInstance(Context context){
        if (mySQLiteHelper == null){
            mySQLiteHelper = new MySQLiteOpenHelper(context,"user.db",null,1);
        }
        return mySQLiteHelper;
    }

    //1.构造函数私有化
    private MySQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL("create table persons(_id integer primary key autoincrement, name varchar(20), sex varchar(2), age integer)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

2、创建JavaBean类

public class Person {
    private String name;
    private String sex;
    private Integer age;

    public Person() {
    }

    public Person(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                '}';
    }
}

3、编写activity_main.xml代码 

4、操作SQLite数据库

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MySQLiteOpenHelper myHelper;
    private String TAG = "MainActivity";

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

        initi();
    }

    private void initi() {
        myHelper = MySQLiteOpenHelper.getInstance(this);

        Button btn_insert = findViewById(R.id.btn_insert);
        Button btn_delete = findViewById(R.id.btn_delete);
        Button btn_query = findViewById(R.id.btn_query);
        Button btn_update = findViewById(R.id.btn_update);

        btn_insert.setOnClickListener(this);
        btn_delete.setOnClickListener(this);
        btn_query.setOnClickListener(this);
        btn_update.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_insert:
                Log.i(TAG, "插入数据");
                insert("张三", "女", 18);
                break;
            case R.id.btn_delete:
                Log.i(TAG, "删除数据");
                delete("李四");
                break;
            case R.id.btn_query:
                Log.i(TAG, "查询数据");
                query();
                break;
            case R.id.btn_update:
                Log.i(TAG, "更新数据");
                update("张三", "李四");
                break;
        }
    }

    private void insert(String name, String sex, Integer age) {
        SQLiteDatabase writableDatabase = myHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("sex", sex);
        values.put("age", age);
        long insertCount = writableDatabase.insert("persons", null, values);
        /**
         * 第一个参数:操作的表名
         * 第二个参数:插入的列没有数据匹配时的默认值
         * 第三个参数:插入的数据列表
         */
        if (insertCount > 0){
            Toast.makeText(this, "插入成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "插入失败", Toast.LENGTH_SHORT).show();
        }
    }

    private void delete(String name) {
        SQLiteDatabase writableDatabase = myHelper.getWritableDatabase();
        String[] whereArgs = {name};
        int deleteCount = writableDatabase.delete("persons", "name=?", whereArgs);
        /**
         * 第一个参数:操作的表名
         * 第二个参数:条件语句, 如果为null, 就删除所有行
         * 第三个参数:条件字符串中"?"占位符的匹配列表
         */
        if (deleteCount > 0){
            Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show();
        }
        writableDatabase.close();
    }

    private void query() {
        SQLiteDatabase readableDatabase = myHelper.getReadableDatabase();
        Cursor cursor = readableDatabase.query("persons", null, null, null, null, null, null);
        while (cursor.moveToNext()) {
            String name = cursor.getString(cursor.getColumnIndex("name"));
            String sex = cursor.getString(cursor.getColumnIndex("sex"));
            int age = cursor.getInt(cursor.getColumnIndex("age"));
            Person person = new Person(name, sex, age);
            Log.i(TAG, person.toString());
        }
        cursor.close();
        readableDatabase.close();
    }

    private void update(String name, String name1) {
        SQLiteDatabase writableDatabase = myHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", name1);
        String[] whereArgs = {name};
        int updateCount = writableDatabase.update("persons", values, "name=?", whereArgs);
        /**
         * 第一个参数:操作的表名
         * 第二个参数:更新的数据列表
         * 第三个参数:条件语句, 如果为null, 就更新所有行
         * 第四个参数:条件字符串中"?"占位符的匹配列表
         */
        if (updateCount > 0){
            Toast.makeText(this, "更新成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "更新失败", Toast.LENGTH_SHORT).show();
        }
        writableDatabase.close();
    }
}

5、运行效果图

注意:在数据库查询操作的cursor.getString(cursor.getColumnIndex("name"));处报错,但不影响编译(可分为两步写解决报错问题)
        操作顺序为插入-->查询-->更新--查询-->删除-->查询

11.4 Room的简单使用

11.4.1 Room的介绍

Room是一个对象关系映射(ORM)库。Room抽象了SQLite的使用,可以在充分利用SQLite的同时访问流畅的数据库。

Room官方文档介绍 https://developer.android.com/training/data-storage/room/

Room由三个重要的组件组成(三角色):Entity、Dao、Database。

  • Entity:数据库实体,系统根据Entity类创建数据库,里面规定了PrimaryKey,列名、表名等数据库必备设定
  • Dao:Database access object:定义了一些操作数据库的操作,比如说增删改查
  • Database:可以认为是完整的数据库,完整的数据库包括数据库信息和数据库操作,也就是EntityDao

11.4.2 Room三角色的实现

注意:Room操作数据库时都是基于主键操作的

1、Student(Entity)

import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;

@Entity(indices = {@Index(value = {"studentNumber"}, unique = true)})
public class Student {
    @PrimaryKey(autoGenerate = true)
    private Integer id;
    private String studentNumber;
    private String name;
    private String classes;

    public Student(Integer id, String studentNumber, String name, String classes) {
        this.id = id;
        this.studentNumber = studentNumber;
        this.name = name;
        this.classes = classes;
    }

    public Integer getId() {
        return id;
    }

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

    public String getStudentNumber() {
        return studentNumber;
    }

    public void setStudentNumber(String studentNumber) {
        this.studentNumber = studentNumber;
    }

    public String getName() {
        return name;
    }

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

    public String getClasses() {
        return classes;
    }

    public void setClasses(String classes) {
        this.classes = classes;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", studentNumber='" + studentNumber + '\'' +
                ", name='" + name + '\'' +
                ", classes='" + classes + '\'' +
                '}';
    }
}

2、StudentDao(Dao)

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

import java.util.List;

@Dao
public interface StudentDao {
    @Insert
    void insertStudent(Student student);

    @Insert
    void insertStudents(List<Student> students);

    @Query("DELETE FROM student WHERE studentNumber=:studentNumber")
    int deleteStudent(String studentNumber);

    @Query("DELETE FROM student")
    int deleteAllStudent();

    @Query("SELECT * FROM Student")
    List<Student> queryAllStudent();

    @Query("SELECT * FROM Student WHERE studentNumber=:studentNumber")
    Student queryStudentByStudentNumber(String studentNumber);

    @Query("SELECT * FROM Student WHERE name=:name")
    List<Student> queryStudentByName(String name);

    @Update
    int updateStudent(Student student);
}

3、StudentDatabase(Database)

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

/**
 * entities:传入所有Entity的class对象;
 * version:数据库版本号。
 * exportSchema:设置是否导出数据库schema,默认为true,需要在build.gradle中设置
 */
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class StudentDatabase extends RoomDatabase {

    private static StudentDatabase studentDatabase = null;

    public static synchronized StudentDatabase getInstance(Context context){
        if (studentDatabase == null){
            studentDatabase = Room.databaseBuilder(context.getApplicationContext(), StudentDatabase.class, "student")
                    .allowMainThreadQueries() //强制在主线程执行
                    .build();
        }
        return studentDatabase;
    }

    public abstract StudentDao getStudentDao();
}

11.4.3 使用Room三角色

1、编写activity_main.xml

2、编写ActivityMain.java

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";
    private StudentDao studentDao;

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

    private void init() {
        studentDao = StudentDatabase.getInstance(this).getStudentDao();

        Button btn_insert = findViewById(R.id.btn_insert);
        Button btn_delete = findViewById(R.id.btn_delete);
        Button btn_query = findViewById(R.id.btn_query);
        Button btn_update = findViewById(R.id.btn_update);

        btn_insert.setOnClickListener(this);
        btn_delete.setOnClickListener(this);
        btn_query.setOnClickListener(this);
        btn_update.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_insert:
                Log.i(TAG, "插入数据");
                insert("38241319601", "张三", "2019级计算机科学与技术六班");
                insert("38241319602", "李si", "2019级计算机科学与技术六班");
                insert("38241319603", "王五", "2019级计算机科学与技术六班");
                insert("38241319604", "赵六", "2019级计算机科学与技术六班");
                break;
            case R.id.btn_delete:
                Log.i(TAG, "删除数据");
                delete("38241319602");
                break;
            case R.id.btn_query:
                Log.i(TAG, "查询数据");
                queryAll();
                break;
            case R.id.btn_update:
                Log.i(TAG, "更新数据");
                update(2, "李四");
                break;
        }
    }

    private void insert(String studentNumber, String name, String classes) {
        Student student = new Student(null, studentNumber, name, classes);
        student.setStudentNumber(studentNumber);
        studentDao.insertStudent(student);
    }

    private void delete(String studentNumber) {
        int deleteCount = studentDao.deleteStudent(studentNumber);
        if (deleteCount > 0){
            Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show();
        }
    }

    private void queryAll() {
        List<Student> students = studentDao.queryAllStudent();
        for (Student student : students) {
            Log.i(TAG, student.toString());
        }
    }

    private void update(Integer id, String name) {
        Student student = new Student(id, null, name, null);
        int updateCount = studentDao.updateStudent(student);
        if (updateCount > 0){
            Toast.makeText(this, "更新成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "更新失败", Toast.LENGTH_SHORT).show();
        }
    }
}

6、运行效果

第12章 多媒体应用

Android 多媒体框架支持捕获和编码各种常见的音频和视频格式。 

12.1 MediaRecorder概述

用于录制音频和视频的一个类。

12.1.1 状态转换图

说明:

下面是关于MediaRecorder状态图的各个状态的介绍:

Initial:初始状态,当使用new()方法创建一个MediaRecorder对象或者调用了reset()方法时,该MediaRecorder对象处于Initial状态。在设定视频源或者音频源之后将转换为Initialized状态。另外,在除Released状态外的其它状态通过调用reset()方法都可以使MediaRecorder进入该状态。

Initialized:已初始化状态,可以通过在Initial状态调用setAudioSource()或setVideoSource()方法进入该状态。在这个状态可以通过setOutputFormat()方法设置输出格式,此时MediaRecorder转换为DataSourceConfigured状态。另外,通过reset()方法进入Initial状态。

DataSourceConfigured:数据源配置状态,这期间可以设定编码方式、输出文件、屏幕旋转、预览显示等等。可以在Initialized状态通过setOutputFormat()方法进入该状态。另外,可以通过reset()方法回到Initial状态,或者通过prepare()方法到达Prepared状态。

Prepared:就绪状态,在DataSourceConfigured状态通过prepare()方法进入该状态。在这个状态可以通过start()进入录制状态。另外,可以通过reset()方法回到Initialized状态。

Recording:录制状态,可以在Prepared状态通过调用start()方法进入该状态。另外,它可以通过stop()方法或reset()方法回到Initial状态。

Released:释放状态(官方文档给出的词叫做Idle state 空闲状态),可以通过在Initial状态调用release()方法来进入这个状态,这时将会释放所有和MediaRecorder对象绑定的资源。

Error:错误状态,当错误发生的时候进入这个状态,它可以通过reset()方法进入Initial状态。

提示:使用MediaRecorder录音录像时需要严格遵守状态图说明中的函数调用先后顺序,在不同的状态调用不同的函数,否则会出现异常。

12.1.2 使用MediaRecorder的使用

1、添加权限

    <!-- 授予该程序录制声音的权限 -->
	<uses-permission android:name="android.permission.RECORD_AUDIO"/>
	<!-- 授予该程序使用摄像头的权限 -->	
	<uses-permission android:name="android.permission.CAMERA"/>

2、MainActivity.java(含申请权限) 

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

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        init();
    }

    public void init(){
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA},
                1000);

        Button btn_record = findViewById(R.id.btn_record);
        Button btn_playVideo = findViewById(R.id.btn_playVideo);
        Button btn_playAudio = findViewById(R.id.btn_playAudio);
        Button btn_playVideoVV = findViewById(R.id.btn_playVideoVV);

        btn_record.setOnClickListener(this);
        btn_playVideo.setOnClickListener(this);
        btn_playAudio.setOnClickListener(this);
        btn_playVideoVV.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_record:
                startActivity(new Intent(this, RecordActivity.class));
                break;
            case R.id.btn_playVideo:
                startActivity(new Intent(this, VideoActivity.class));
                break;
            case R.id.btn_playVideoVV:
                startActivity(new Intent(this, VideoViewActivity.class));
                break;
            case R.id.btn_playAudio:
                startActivity(new Intent(this, RecordActivity.class));
                break;
        }
    }
}

3、RecordActivity.java(问题:声音和图像分开播放,未解决)

import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.IOException;

public class RecordActivity extends AppCompatActivity implements View.OnClickListener {
//    private SurfaceView surfaceView;
    private TextureView textureView;
    private Button btn_opt;

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

//        surfaceView = findViewById(R.id.surfaceView);
        textureView = findViewById(R.id.textureView);

        btn_opt = findViewById(R.id.btn_opt);
        btn_opt.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        CharSequence btn_optText = btn_opt.getText();
        MediaRecorder mediaRecorder = null;
        Camera camera = Camera.open();
        if (TextUtils.equals("开始", btn_optText)){
            btn_opt.setText("结束");
            camera.setDisplayOrientation(90); //竖屏
            camera.unlock();
            mediaRecorder = new MediaRecorder();
            mediaRecorder.setCamera(camera);
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置音频源 麦克风
            mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); //设置视频源 麦克风
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //设置视频输出格式
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); //设置音频编码格式
            mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); //设置视频格式
            mediaRecorder.setOrientationHint(90); //设置文件正向
            mediaRecorder.setOutputFile(new File(getExternalFilesDir(""), "a.mp4").getAbsolutePath()); //设置文件输出路径
            mediaRecorder.setVideoSize(640, 480); //设置视频显示大小
//            mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface()); //低版本不行
            mediaRecorder.setPreviewDisplay(new Surface(textureView.getSurfaceTexture()));
            try {
                mediaRecorder.prepare(); //录制准备
            } catch (IOException e) {
                e.printStackTrace();
            }
            mediaRecorder.start(); //开始录制
        } else {
            btn_opt.setText("开始");
            mediaRecorder.stop();
            mediaRecorder.release(); //释放mediaRecorder
            camera.stopPreview();
            camera.release();
        }
    }
}

4、activity_record.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=".RecordActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/btn_opt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始"
        app:layout_constraintBottom_toBottomOf="@+id/textureView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

12.2 MediaPlayer简介

MediaPlayer类是媒体框架最重要的组成部分之一。此类的对象能够获取、解码以及播放音频和视频,而且只需极少量设置。它支持多种不同的媒体源,例如:

  • 本地资源
  • 内部URI,例如您可能从内容解析器那获取的URI·外部网址(流式传输)

媒体格式列表:https://developer.android.google.cn/guide/topics/media/media-formats?hl=zh_cn

12.2.1 状态转换图

12.2.2 自定义

1、VideoActivity.java

import android.media.MediaPlayer;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.IOException;

public class VideoActivity extends AppCompatActivity implements View.OnClickListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {
    private TextureView textureView;
    private Button btn_opt;

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

        textureView = findViewById(R.id.textureView);

        btn_opt = findViewById(R.id.btn_opt);
        btn_opt.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        CharSequence btn_optText = btn_opt.getText();
        MediaPlayer mediaPlayer = null;
        if (TextUtils.equals("开始", btn_optText)) {
            btn_opt.setText("结束");
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setOnPreparedListener(this); //加载完成监听
            mediaPlayer.setOnCompletionListener(this); //播放完成监听
            try {
                mediaPlayer.setDataSource(new File(getExternalFilesDir(""), "a.mp4").getAbsolutePath());
            } catch (IOException e) {
                e.printStackTrace();
            }
            mediaPlayer.setSurface(new Surface(textureView.getSurfaceTexture())); //播放画布
            mediaPlayer.prepareAsync(); //异步加载
        } else {
            btn_opt.setText("开始");
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }

    @Override
    public void onPrepared(MediaPlayer mediaPlayer) {
        mediaPlayer.start(); //播放
    }

    @Override
    public void onCompletion(MediaPlayer mediaPlayer) {
        btn_opt.setText("开始");
        mediaPlayer.stop();
        mediaPlayer.release();
    }
}

2、activity_video.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=".RecordActivity">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

12.2.3 Android自带的VideoView

1、VideoViewActivity.java

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.MediaController;
import android.widget.VideoView;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;

public class VideoViewActivity extends AppCompatActivity implements View.OnClickListener {

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

        VideoView videoView = findViewById(R.id.videoView);

        MediaController mediaController = new MediaController(this);
        mediaController.setPrevNextListeners(this, this); //上一曲、下一曲
        videoView.setMediaController(mediaController);
        videoView.setVideoPath(new File(getExternalFilesDir(""), "a.mp4").getAbsolutePath());
        videoView.start();
    }

    @Override
    public void onClick(View view) {
        Log.i("VideoView", "======");
    }
}

2、activity_videoview.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=".VideoViewActivity">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

12.3 SoundPool音效播放

12.3.1 SoundPool简介

MediaPlayer虽然也能播放音频,但是它有资源占用量较高、延迟时间较长、不支持多个音频同时播放等缺点。这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的场景。而SoundPool一般用来播放密集、急促而又短暂的音效,比如:"滴滴一下,马上出发”。

12.3.2 使用SounPool

1、Music.java

public class Music {
    private String name;
    private int musicId;

    public Music() {
    }

    public Music(String name, int musicId) {
        this.name = name;
        this.musicId = musicId;
    }

    public String getName() {
        return name;
    }

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

    public int getMusicId() {
        return musicId;
    }

    public void setMusicId(int musicId) {
        this.musicId = musicId;
    }

    @Override
    public String toString() {
        return "Music{" +
                "name='" + name + '\'' +
                ", musicId=" + musicId +
                '}';
    }
}

2、创建SoundActivity

activity_sound.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=".SoundActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/teal_200"/>

</androidx.constraintlayout.widget.ConstraintLayout>

3、music_item.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:orientation="vertical"
    android:background="@color/red">

    <TextView
        android:id="@+id/tv_musicName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="30sp"/>
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/black"/>

</LinearLayout>

4、MusicAdamter.java

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

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

import java.util.List;

public class MusicAdapter extends RecyclerView.Adapter<MusicAdapter.ViewHolder> {
    private List<Music> musicList;
    private Context context;

    public MusicAdapter(List<Music> musicList, Context context){
        this.musicList = musicList;
        this.context = context;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //用来创建ViewHolder实例,再将加载好的布局传入构造函数,最后返回ViewHolder实例
//        View view = View.inflate(context, R.layout.music_item, null);
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.music_item, parent, false); //解决宽度不能铺满
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int position = view.getVerticalScrollbarPosition();
                Toast.makeText(context, musicList.get(position).getName(), Toast.LENGTH_SHORT).show();
            }
        });
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.textView.setText(musicList.get(position).getName());
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if(onItemClickListener != null) {
                    int pos = holder.getLayoutPosition();
                    onItemClickListener.onItemClick(holder.itemView, pos);
                }
            }
        });
    }

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

    public interface OnItemClickListener{
        void onItemClick(View view, int position);
    }
    private OnItemClickListener onItemClickListener;
    public void setOnItemClickListener(MusicAdapter.OnItemClickListener listener) {
        this.onItemClickListener = listener;
    }


    public class ViewHolder extends RecyclerView.ViewHolder{
        protected TextView textView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_musicName);
        }
    }
}

5、SoundActivity.java

import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

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

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

public class SoundActivity extends AppCompatActivity {

    private List<Music> musicList = new ArrayList<>();
    private SoundPool soundPool;

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

        initMusics();

        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);  //设置方向
        recyclerView.setLayoutManager(layoutManager);
        MusicAdapter musicAdapter = new MusicAdapter(musicList, this);
        musicAdapter.setOnItemClickListener(new MusicAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                Toast.makeText(SoundActivity.this, "点击了"+musicList.get(position).getName(), Toast.LENGTH_SHORT).show();
                Music music = musicList.get(position);
                AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
                int volumem = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
                int volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
                soundPool.play(music.getMusicId(), volumem, volume, 1, 1, 1);
            }
        });
        recyclerView.setAdapter(musicAdapter);
    }

    private void initMusics() {
        soundPool = new SoundPool.Builder().setMaxStreams(6).build();
        for (int i = 0; i < 6; i++) {
            musicList.add(new Music("a"+(i+1), soundPool.load(this, R.raw.a1+i, 1))); //不建议
        }

//        musicList.add(new Music("a2", soundPool.load(this, R.raw.a2, 1)));
//        musicList.add(new Music("a3", soundPool.load(this, R.raw.a3, 1)));
//        musicList.add(new Music("a4", soundPool.load(this, R.raw.a4, 1)));
//        musicList.add(new Music("a5", soundPool.load(this, R.raw.a5, 1)));
//        musicList.add(new Music("a6", soundPool.load(this, R.raw.a6, 1)));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        for (Music music : musicList) {
            soundPool.unload(music.getMusicId());
        }
        soundPool.release();
    }

音效资源链接:https://pan.baidu.com/s/1yIY5l7xQf0kG7lO_U6A4uQ 
提取码:neyt

第13章 项目发布

13.1 项目安全

13.1.1 加固

为什么应用需要加固:防止应用被逆向分析、反编译、二次打包,防止嵌入各类病毒、广告等恶意代码,从源头保护数据安全和开发者利益。

13.1.2 设置多渠道

1、统计各个渠道包的情况,例如哪个渠道的下载量更大,哪个渠道下载的客户活跃度或者粘性更高等信息。
2、针对不同的渠道做一些不同的操作。
目前常用的多渠道打包工具有三种:

  • 友盟
  • 美团
  • 360

11.1.3 生成release apk

 

常用:【Build】-->【Generate Signed Bundle / APK...】-->【APK】-->【Next】--> 【Key story path】-->【Next】-->【使用加固工具进行加固】 -->【应用发布】

更多知识点详见:Android基础入门教程 | 菜鸟教程 (runoob.com)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_57542177/article/details/123166580

智能推荐

PyTorch 命名张量(Named Tensors)的介绍_runtimeerror: nyi: named tensors are not supported-程序员宅基地

文章浏览阅读3.5k次。请到这里去查看图文教程:http://studyai.com/pytorch-1.4/intermediate/named_tensor_tutorial.html命名张量旨在通过允许用户将显式名称与张量维度关联,使张量更易于使用。在大多数情况下, 采用维度参数的操作将接受维度名称,从而避免了按位置跟踪维度的需要。此外,命名张量使用 名称自动检查API在运行时是否正确使用,从而提供额外的安全性。..._runtimeerror: nyi: named tensors are not supported with the tracer

IDEA解决Maven依赖不上问题的三种办法_idea maven 项目dependency 扫描不到.-程序员宅基地

文章浏览阅读1.7w次,点赞13次,收藏51次。在使用IDEA工具的时候,我们经常会出现pom.xml文件依赖速度慢或者是依赖不上等问题,导致pom.xml文件出现报错情况,比如这样:此时,解决办法有三种:一是使用Reimport继续等待直到问题解决;二是从Maven的中央仓库下载jar包手动安装;三是修改中央仓库地址,使用国内镜像网站。第一种方式:使用Reimport命令此方式比较简单,直接在IDEA工具中找到此命令,重新依赖一下即可..._idea maven 项目dependency 扫描不到.

Android HttpUtils工具类-程序员宅基地

文章浏览阅读774次。import java.io.BufferedReader;import java.io.InputStream;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.URL;/** * Created by YaphetZhao on 2015/8/13 0013. *

MFC 小知识总结四-程序员宅基地

文章浏览阅读65次。1 PlaySound 播放WAV格式的音乐This function plays a sound specified by a file name, resource, or system event.&lt;strong&gt;BOOL WINAPI PlaySound( LPCSTR&lt;/strong&gt; &lt;em&gt;&lt;a tar..._windows面向对mfc知识点

spring、springMvc、springBoot和springCloud-程序员宅基地

文章浏览阅读212次。等用这些框架做一些项目再添加自己的语言吧看了几篇不错的博客,推荐一下。spring、springMvc、springBoot和springCloud的联系与区别_alan_liuyue的博客-程序员宅基地继续继续···

性能测试实战(七):数据驱动_性能测试数据驱动-程序员宅基地

文章浏览阅读5.2k次。一、数据驱动性能测试1、DDT:数据驱动测试,数据驱动性能测试2、在性能测试中,一定会使用很多数据来做测试3、数据:造数据、生成数据造数据:直接插入数据库、文件导入数据库、jmeter工具调用接口运行比较长的时间4、CSV数据元件设置(配置元件)(1)用自己已经造好的数据进行测试(2)文件名:一定不能为空,【浏览】默认打开的是jmx保存的地方,建议跟脚本放一起文件,可以支持文本文件(txt、csv)csv文件,是用逗号分隔列..._性能测试数据驱动

随便推点

95. 不同的二叉搜索树 II_95 .lo; : i;!:95 .lo; : i;!:-程序员宅基地

文章浏览阅读57次。算法题(程序员面试宝典)解题思路主要来源于leetcode官方与《程序员面试宝典》。95. 不同的二叉搜索树 II给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。示例 1:输入:n = 3输出:[[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]示例 2:输入:n = 1输出:[[1]]提示:1 <=_95 .lo; : i;!:95 .lo; : i;!:

Unity3d OnDrop触发条件_unity ondrop-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏8次。Unity3d OnDrop触发条件Unity3d OnDrop触发条件刚开始接触UGUI,把新知识记录一下OnDrop作用检测拖拽事件(OnDrag)放下的操作OnDrop依赖于OnDrag,先有OnDrag后OnDrop才可能被触发拖拽放下时如果可以检测OnDrop事件的物体上方有其他物体遮挡住的当前物体,OnDrop事件也不会触发,因为屏幕射线被阻截了可以在物体上添加Canv..._unity ondrop

UGUI粒子遮罩(UI Mask 遮挡粒子)_粒子组件遮罩-程序员宅基地

文章浏览阅读5k次,点赞7次,收藏14次。前言在Unity中UGUI的Mask遮罩无法对Partice System(粒子)起到直接的作用,于是这里我们使用Shader来实现UI Mask对粒子的遮罩效果。关于粒子显示在同一层两个不同UI之间叠加的问题:实现方案首先使用一张image图片,添加mask遮罩,这张图片默认使用的是UI/Default的shader,这里我们替换成UI/MaskDefault的shader,这里贴出源码:// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' wit_粒子组件遮罩

Silk To Solder Mask (Clearance=10mil) (IsPad),(All))的类似错误-程序员宅基地

文章浏览阅读5w次,点赞31次,收藏84次。在我们一开始用AD画PCB的时候相信会有很多小伙伴都会出现类似这样的错误Silk To Solder Mask (Clearance=10mil) (IsPad),(All))或是Silk to Silk (Clearance=10mil) (All),(All)其实这些错误都是丝印层和丝印层或者是丝印层和阻焊层之间间距大小引起的问题,这是封装引起的问题,一般情况下问题不大,可以忽略,如果..._silk to solder mask (clearance=10mil) (ispad),(all)

vue项目端口号修改-程序员宅基地

文章浏览阅读244次。不同的项目端口号配置地址也不同我的项目端口号配置文件是vue.config.js中在这里更改这个port的值就可以了获得的感悟:百度一大堆,百度了个寂寞,也就全局搜索一下的事_vue项目端口号

树莓派下的驱动开发_简化微软树莓哦 平台下设备驱动程序的开发-程序员宅基地

文章浏览阅读6.6k次。最近想学习一下linux驱动,看了一些书和教学视频,大概了解了一下,不过要想深入,肯定需要实践。手上有几块linux的板子,最终选择了树莓派作为我的实验平台,资料比较丰富,接口也比较简单。 程序员的入门经典当然就是hello world程序了,我的第一个实验就是要搭建实验环境和工具链,通过交叉编译的方式,在上位机完成hello world驱动程序的编写,最终在板子上运行。_简化微软树莓哦 平台下设备驱动程序的开发