安卓(Android)是一种基于Linux内核(不包含GNU组件)的自由及开放源代码的移动操作系统。主要应用于移动设备,如智能手机和平板电脑,由美国Google公司和开放手机联盟领导及开发。Android操作系统最初由安迪·鲁宾开发,主要支持手机。2005年8月由Google收购注资。2007年11月,Google与84家硬件制造商、软件开发商及电信营运商组建开放手机联盟共同研发改良Android系统。随后Google以Apache开源许可证的授权方式,发布了Android的源代码。

1.下载Android Studio和SDK

Install Android Studio | Android Developers (google.cn)

2.观察APP运行日志

Log.e表示错误信息

Log.w表示警告信息

Log.i表示一般消息

Log.d表示调试信息

Log.v表示冗余信息

3.工程目录结构

3.1.gradle

.gradle和.idea Android Studio自动生成的文件,不用了解,一般不要改动。

app 代码、资源等内容(常用)

build 编译文件夹,一般不用

gradle gradle wrapper的配置文件,Android Studio默认没有启动gradle wrapper的方式,如果需要打开,可以点击Android Studio导航栏 –> File –> Settings –> Build,Execution,Deployment –> Gradle,进行配置更改。

.gitignore 将指定的目录或文件排除在版本控制之外

build.gradle 项目全局的gradle构建脚本,通常这个文件中的内容是不需要修改的

gradle.properties 全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本

gradlew和gradlew.bat 在命令行界面中执行gradle命令,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。

HelloWorld.iml iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJ IDEA开发的),用于标识这是一个IntelliJ IDEA项目,我们不需要修改这个文件中的任何内容

local.properties 指定本机中的Android SDK路径,通常内容都是自动生成的,我们并不需要修改。除非你本机中的Android SDK位置发生了变化,那么就将这个文件中的路径改成新的位置即可。

settings.gradle 用于指定项目中所有引入的模块。需要我们手动去修改这个文件的场景可能比较少

3.2.app

build 编译时自动生成的文件,一般不用

libs 第三方jar包,jar包都放在libs目录下

AndroidTest 用来编写Android Test测试用例的,可以对项目进行一些自动化测试

java java目录是放置我们所有java代码的地方,展开该目录,你将看到我们刚才创建的HelloWorldActivity文件就在里面

res 所有资源文件都在这里 图片放在drawable目录下 布局放在layout目录下,(相当于html中的body) 应用图标在mipmap目录下
字符串放在values目录下

AndroidManifest.xml 整个Android项目的配置文件,你在程序中定义的所以四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。

test 此处是用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。

.gitignore 这个文件用于将app模块内的指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。

app.iml IntelliJ IDEA项目自动生成的文件,我们不需要关心或修改这个文件中的内容。

build.gradle 这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置。

proguard-rules.pro 这个文件用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望代码被别人破解

4.创建新的App页面

1.创建一个layout布局文件

2.创建一个Activity Java类

使用startActivity切换页面

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.tv1);
        tv.setText("你好,RICHU");
        Log.d("ning","create");
        Button btn = findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, MainActivity_2.class);
                startActivity(intent);
            }
        });
    }
}

5.Andriod控件

5.1.设置文本的内容

1.在XML文件中通过属性andriod:text

<!--res/values-->
<resources>
    <string name="mytext" translatable="false">HAHAHA</string>
</resources>
<!--res/layoutres-->
<?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:id="@+id/mytest"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:text="@string/mytext"
              
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.在Java代码中调用文本视图对象的setText方法设置文本

<!--res/values-->
<resources>
    <string name="mytext" translatable="false">HAHAHA</string>
</resources>
//java/com/example/application1/MainActivity_2.java
public class MainActivity_2 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);
        TextView tv = findViewById(R.id.tv);
        tv.setText(R.string.mytext);
    }
}

5.2.设置文本的大小

px 像素

dpi 指屏幕每英寸有多少个像素点

dip 长度单位,同一个单位在不同设备上有不同的显示效果,简称dp,与设备无关只与屏幕的尺寸有关

sp 专门用来设置字体大小,在系统中可以设置调整字体大小

px = dpi*dip/160

相同尺寸的手机,即使分辨率不同,同dp的组件占用屏幕比例也相同

//默认使用sp
tv.setTextSize(30);

5.3.设置文本颜色

使用自带的颜色,在Color类里面选择

tv.setTextColor(Color.BLACK);

使用自定义的颜色类似于argb() 前两位是透明度 ff为不透明,不设置透明度就直接透明了

tv.setTextColor(0xff00ff00);

在布局文件中设置 后两位是透明度ff为透明

八位编码#FFEEDDCC中,FF表示透明度,EE表示红色的浓度,DD表示 绿色的浓度,CC表示蓝色的浓度。透明度为FF表示完全不透明,为00表示完全透明。

<TextView
          android:id="@+id/tv"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/mytext"
          android:textColor="#8ec5fc"
          tools:ignore="MissingConstraints" />

5.4.设置视图的宽高

视图宽高通过属性Android:layout_width表达,视图高度通过属性android:layout_height表达

match_parent 表示与上级视图保持一致

wrap_content 表示与内容自适应

以dp为单位的具体尺寸

<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:textColor="#000000"
          android:textSize="17sp"
          android:text="@string/mytext"
          android:background="#00ffff"
          android:layout_marginTop="5dp"
/>
public class MainActivity_2 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);
        TextView tv = findViewById(R.id.tv);
        ViewGroup.LayoutParams params = tv.getLayoutParams();
        params.width = diptopx.dip(this,300);
        tv.setLayoutParams(params);
    }
}

工具类将dip转为px

public class diptopx {
    public static int dip(Context context, float dip){
        //获取当前手机的像素密度
        float scale = context.getResources().getDisplayMetrics().density;
        //四舍五入
        return (int)(dip * scale +0.5f);
    }
}

5.5.设置视图的间距

margin 当前视图和周围平级视图之间的距离

padding 当前视图与内部下级视图之间的距离

与前端的margin和padding类似有四个方向,layout_marginTop….

5.6.设置视图的对齐方式

layout_gravity 当前视图相对于上级视图的对齐方式

gravity下级视图相对于当前视图的对齐方式

left right top bottom

用|连接表示合并方向left|top 为左上方

6.布局

6.1LinearLayout线性布局

orientation

  • horizontal 内部视图在水平方向从左到右排列
  • vertical 内部视图在水平方向从上到下排列

如果不指定orientation属性,则LinearLayout默认水平方向排列

线性布局的权重概念,值得是线性布局的下级视图各自拥有多大比例的宽高

权重属性名叫layout_weight.但该属性不在LinearLayout节点设置,而在线性布局的直接下级视图设置,表示该下级视图占据的宽高比例。

  • layout_width=0 layout_weight表示水平方向的宽高比例
  • layout_height=0 layout_weight表示垂直方向的宽高比例
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="横排第一个"
        android:textSize="17sp"
        android:textColor="#000000"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="横排第一个"
        android:textSize="17sp"
        android:textColor="#000000"/>
</LinearLayout>

6.2RelativeLayout相对布局

相对布局的下级视图位置由其他视图决定。用于确定下级视图位置的参照物分两种:

  • 与该视图自身平级的视图
  • 该视图的上级视图

如果不设置下级视图的参照物,那么下级视图默认显示在RelativeLayout内部的左上角

与父容器相关的定位:

android:layout_alignParentTop 控件顶部与父布局的顶部对齐。
android:layout_alignParentBottom 控件底部与父布局的底部对齐。
android:layout_alignParentLeft 控件左边与父布局的左边对齐。
android:layout_alignParentRight 控件右边与父布局的右边对齐。
android:layout_alignParentStart 将控件的起始边(根据布局方向,可能是左边或右边)与父容器的起始边对齐。
android:layout_alignParentEnd 将控件的结束边(根据布局方向,可能是右边或左边)与父容器的结束边对齐。
android:layout_centerHorizontal 将控件水平居中对齐。
android:layout_centerVertical 将控件垂直居中对齐。
android:layout_centerInParent 水平垂直都居中

相对定位属性:

android:layout_above 控件放置在指定控件的上方。
android:layout_below 控件放置在指定控件的下方。
android:layout_toLeftOf 控件放置在指定控件的左边。
android:layout_toRightOf 控件放置在指定控件的右边。
android:layout_alignTop 控件的顶部与指定控件的顶部对齐。
android:layout_alignBottom 控件的底部与指定控件的底部对齐。
android:layout_alignLeft 控件的左边与指定控件的左边对齐。
android:layout_alignRight 控件的右边与指定控件的右边对齐。
android:layout_alignStart 将控件的起始边(根据布局方向,可能是左边或右边)与指定控件的起始边对齐。
android:layout_alignEnd 将控件的结束边(根据布局方向,可能是右边或左边)与指定控件的结束边对齐。
android:layout_alignBaseline 控件的基线与指定控件的基线对齐。

6.3GridLayout网格布局

支持多行多列的布局

默认从左往右从上到下

  • columnCount属性,指定了网格的列数
  • rowCount属性,指定了网格的行数

设置视图的对齐方式

  • 采用layout_gravity属性,它指定了当前视图相对于上级视图的对齐方式
  • 采用gravity属性,它指定了下级视图相对于当前视图的对齐方式

layout_gravity和gravity取值为left,top,right,bottom ,center

left|top为左上

<GridLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    <TextView
              android:layout_columnWeight="1"
              android:layout_rowWeight="1"/>

设置权重layout_columnWeight,layout_rowWeight

6.4滚动视图ScrollView

ScrollView,它是垂直方向的滚动视图;垂直方向滚动时,layout_width属性值设置为match_parent,layout_height属性值设置为wrap_content

HorizontalScrollViewScrollView,它是水平方向的滚动视图水平方向滚动时,layout_width属性值设置为wrap_content,layout_height属性设置为match_parent

6.5.Button

由TextView派生

  • 有默认背景
  • 文本居中对齐

相对于TextView新增的属性

android:textAllCaps=”true”

  • true将小写转为大写
  • false则不会转换

android:onClick=”doclick”

绑定事件(不常用)

6.6点击事件

setOnClickListener

public class MainActivity_2 extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);

        TextView tv = findViewById(R.id.tv);
        Button bt = findViewById(R.id.bt);
        bt.setOnClickListener(new MyOnclickListener(tv));
    }

    static class MyOnclickListener implements View.OnClickListener{
        private final TextView tv;
        public MyOnclickListener(TextView tv) {
            this.tv = tv;
        }
        @Override
        public void onClick(View v) {
            String s = String.format("%s点击%s", SimpleUtil.dateformat(),((Button)v));
            tv.setText(s);
        }
    }

创建公用OnClickListener

public class MainActivity_2 extends AppCompatActivity implements View.OnClickListener{
	@override
    public void Onclick(View v){
        if (v.getId()==R.id.bt){
            
        }
    }
}

6.7长按点击事件

true则为开启事件冒泡,false则不开启

bt.setOnLongClickListener(v->{
    String s = String.format("%s点击%s", SimpleUtil.dateformat(),((Button)v));
    tv.setText(s);
    return true;
});

6.8禁用恢复按钮

是否允许点击enable

bt1.setEnabled(true);
bt1.setEnabled(false);

true启用,false禁用

6.9ImageView

图像视图ImageView

图像视图展示的图片通常位于res/drawable***目录,设置图像视图的显示图片有两种方式:

在XML文件中,通过属性android:src设置图片资源,属性值格式形如“@drawable/不含扩展名的图片名称”。

在Java代码中,调用setlmageResource方法设置图片资源,方法参数格式形如“R.drawable.不含扩展名的图片名称”。

<ImageView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:layout_marginTop="5dp"
    android:src="@drawable/test"/>

scaleType属性

matrix:使用matrix方式进行缩放。

fitXY:横向、纵向独立缩放,以适应该ImageView。

fitStart:保持纵横比缩放图片,并且将图片放在ImageView的左上角。

fitCenter:保持纵横比缩放图片,缩放完成后将图片放在ImageView的中央。

fitEnd:保持纵横比缩放图片,缩放完成后将图片放在ImageView的右下角。

center:把图片放在ImageView的中央,但是不进行任何缩放。

centerCrop:保持纵横比缩放图片,以使图片能完全覆盖ImageView。

centerInside:保持纵横比缩放图片,以使得ImageView能完全显示该图片。(用于缩小图片)

ImageView imageView = findViewById(R.id.image);
//设置图片源
imageView.setImageResource(R.drawable.test);
//设置缩放对齐方式
imageView.setScaleType(ImageView.ScaleType.FIT_XY);

6.10ImageButton

同时显示文本和图像

通过Button上的drawable属性设置周围文本的图标

drawableTop:指定文字上方的图片

drawableBottom:指定文字下方的图片

drawableLeft:指定文字左方的图片

drawableRight:指定文字右方的图片

drawablePadding:指定图片与文字的间距

6.11计算器项目

package com.example.application1;

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

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class CaculateActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_result;
    private String firstNum = "";
    private String operator="";
    private String secondNum = "";

    private String showText = "";
    private String result = "";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_caculate);
        //获取结果文本视图
        tv_result = findViewById(R.id.tv_result);
        //给每个按钮绑定事件
        findViewById(R.id.btn_cancel).setOnClickListener(this);
        findViewById(R.id.btn_div).setOnClickListener(this);
        findViewById(R.id.btn_mul).setOnClickListener(this);
        findViewById(R.id.btn_clear).setOnClickListener(this);
        findViewById(R.id.btn_seven).setOnClickListener(this);
        findViewById(R.id.btn_eight).setOnClickListener(this);
        findViewById(R.id.btn_nine).setOnClickListener(this);
        findViewById(R.id.btn_plus).setOnClickListener(this);
        findViewById(R.id.btn_four).setOnClickListener(this);
        findViewById(R.id.btn_five).setOnClickListener(this);
        findViewById(R.id.btn_six).setOnClickListener(this);
        findViewById(R.id.btn_mius).setOnClickListener(this);
        findViewById(R.id.btn_one).setOnClickListener(this);
        findViewById(R.id.btn_two).setOnClickListener(this);
        findViewById(R.id.btn_three).setOnClickListener(this);
        findViewById(R.id.btn_sqrt).setOnClickListener(this);
        findViewById(R.id.btn_divone).setOnClickListener(this);
        findViewById(R.id.btn_zero).setOnClickListener(this);
        findViewById(R.id.btn_point).setOnClickListener(this);
        findViewById(R.id.btn_equal).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        String inputtext;
        if (v.getId() == R.id.btn_sqrt){
            inputtext = "√";
        }else{
            inputtext = ((TextView) v).getText().toString();
        }
        if (v.getId() == R.id.btn_clear) {
            clear();
        }else if(v.getId() == R.id.btn_cancel) {
            cancel();
        }else if(v.getId() == R.id.btn_plus) {
            operator = inputtext;
            refreshText(showText+inputtext);
        }else if (v.getId() == R.id.btn_mius) {
            operator = inputtext;
            refreshText(showText+inputtext);
        }else if (v.getId() == R.id.btn_mul) {
            operator = inputtext;
            refreshText(showText+inputtext);
        }else if (v.getId() == R.id.btn_div) {
            operator = inputtext;
            refreshText(showText+inputtext);
        }else if (v.getId() == R.id.btn_equal) {
            double caculate_result = Caculate();
            refreshOpreator(String.valueOf(caculate_result));
            refreshText(showText+"="+caculate_result);
        }else if (v.getId() == R.id.btn_sqrt) {
            double sqrt_result = Math.sqrt(Double.parseDouble(firstNum));
            refreshOpreator(String.valueOf(sqrt_result));
            refreshText(showText + "^=" + sqrt_result);
        }else if (v.getId()==R.id.btn_divone) {
            double sqrt_result = 1/Double.parseDouble(firstNum);
            refreshOpreator(String.valueOf(sqrt_result));
            refreshText(showText + "/=" + sqrt_result);
        }
        else{
            if (!result.isEmpty()){
                clear();
            }
            if (operator.isEmpty()) {
                firstNum = firstNum + inputtext;
            } else {
                secondNum = secondNum + inputtext;
            }
            //整数不需要前面的0
            if (showText.equals("0") && !inputtext.equals(".")) {
                refreshText(inputtext);
            } else {
                refreshText(showText + inputtext);
                }
            }
        }
    private void cancel(){
        if (showText.length()>1){
            refreshText(showText.substring(0,showText.length()-1));
        }else{
            refreshText("0");
        }
    }
    private void refreshText(String text){
        showText = text;
        tv_result.setText(showText);
    }
    private void clear() {
        refreshOpreator("");
        refreshText("0");
    }
    private void refreshOpreator(String newresult){
        result = newresult;
        firstNum = result;
        secondNum = "";
        operator = "";
    }
    private double Caculate(){
        switch (operator){
            case "+":
                return Double.parseDouble(firstNum) + Double.parseDouble(secondNum);
            case "-":
                return Double.parseDouble(firstNum) - Double.parseDouble(secondNum);
            case "X":
                return Double.parseDouble(firstNum) * Double.parseDouble(secondNum);
            case "÷":
                return Double.parseDouble(firstNum) / Double.parseDouble(secondNum);
        }
        return 0;
    }
}

7.Activity

7.1启停activity

启动新Activity

startActivity(new Intent(原页面.this,目标页面.class))

返回

finnish()//结束当前的活动页面

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        startActivity(new Intent(this,MainActivity2.class));
    }
}
public class MainActivity2 extends AppCompatActivity implements View.OnClickListener {

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        findViewById(R.id.btn1).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn1){
            finish();
        }
    }
}

7.2Activity生命周期

onCreate:表示Activity正在被创建。生命周期的第一个方法,当打开一个activity时首先回调这个方法。在这个方法中一般做一些初始化工作,例如加载界面布局资源(setContentView)、数据初始化(findviewbyid)

onRestart : 表示Activity正在被重新启动。当前activity从不可见变为可见状态时这个方法就会回调。一般是用户行为导致,比如用户摁home键回到桌面,当用户再次回到本activity时,当前activity 走 onRestart->onStart->onResume

onStart :表示activity正在被启动。activity可见,未出现在前台。可以理解为activity已经显示出来了,但是我们看不到,不能与之交互。

onResume :表示activity已经可见,并且出现在前台,可以与用户进行交互。例如activity上有Button,此时我们就可以进行点击了。

onPause:表示activity正在停止,接着很快执行onStop。注意极端情况下,本Activity跳转其他activity后快速的回到当前activity时,当前activity的生命周期:onPause->onResume
但是这个“快速回到”要很快,一般情况下都是onPause->onStop->onRestart->onStart->onResume。这里不能做太耗时操作,可以做一些数据存储,动画停止工作。

onStop:表示activity即将停止,可以做一些回收工作。但是不能太耗时。

onDestroy :activity即将被销毁 ,activity生命周期最后一个回调,这里可以做一些回收工作,资源释放。

onNewIntent:重用已有的活动实例

activity.png

7.3Activity启动模式

1.在配置文件中设置启动模式

<activity
          android:name=".MainActivity"
          android:exported="true"
          android:launchMode="standard">

standard

默认启动模式

启动Activity的会依照顺序依次被压入Task栈内

singleTop

如果栈顶为新建的Activity(目标Activity),那么就不会重复创建新的Activity

适合开启渠道多,多应用开启调用的Activity,通过这种设置可以避免创建过的Activity被重复创建,多数通过动态设置使用

singleTask

栈内复用

程序的主界面,耗费系统资源的Activity

全局唯一模式

singleInstance

单独为它创建一个任务栈,新的Task只会有它一个实例。如果已经创建过,则不会创建新的Activity,而是将之前的Activity唤醒

2.在代码中动态设置

启动标志的取值说明如下:
Intent.FLAG_ACTIVITY_NEW_TASK:开辟一个新的任务栈
Intent.FLAG_ACTIVITY_SINGLE_TOP:当栈顶为待跳转的活动实例之时,则重用栈顶的实例
Intent.FLAG_ACTIVITY_CLEAR_TOP:当栈中存在待跳转的活动实例时,则重新创建一个新实例,并清除原实例上方的所有实例
Intent.FLAG_ACTIVITY_NO_HISTORY:栈中不保存新启动的活动实例
Intent.FLAG_ACTIVITY_CLEAR_TASK:跳转到新页面时,栈中的原有实例都被清空

public void onClick(View v) {
    Intent intent = new Intent(this,MainActivity2.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    startActivity(intent);
}

登录后不再返回登录界面

Intent intent = new Intent(this,MainActivity2.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

7.4传递数据

7.4.1显示Intent和隐式Intent

Intent是各个组件之间信息沟通的桥梁,用于各组件的通信

显示Intent,直接指定来源活动和目标活动,属于精确匹配

  • 在Intent的构造函数中指定
  • 调用意图对象的SetClass方法指定
  • 调用意图对象的SetComponent方法指定
//第一种
Intent intent = new Intent(this,MainActivity2.class);
//第二种,调用意图对象的setClass方法指定
Intent intent = new Intent();
intent.setClass(this, MainActivity2.class);
//第三种,调用意图对象的SetComponet方法指定
ComponentName componentName = new ComponentName(this, MainActivity2.class);
intent.setComponent(componentName);

隐式Intent,没有明确指定要跳转的目标活动,只要给出一个动作字符串让系统自动匹配,属于模糊匹配

String phoneNumber ="1234";
Intent intent = new Intent();
//设置意图动作
intent.setAction(Intent.ACTION_DIAL);
Uri uri = Uri.parse("tel:"+phoneNumber);
intent.setData(uri);
startActivity(intent);

启动自定义的程序

Intent intent = new Intent();
intent.setAction("android.intent.action.RICHU");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);

在要启动的程序中配置

<intent-filter>
    <action android:name="android.intent.action.RICHU" />

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

常见系统动作的取值说明

intent 类的系统动作常量名 系统动作的常量值 说明
ACTION_MAIN android.intent.action.MAIN App启动时的入口
ACTION_VIEW android.intent.action.VIEW 向用户显示数据
ACTION_SEND android.intent.action.SEND 分享内容
ACTION_CALL android.intent.action.CALL 直接拨号
ACITON_DIAL android.intent.action.DIAL 准备拨号
ACTION_SENDTO android.intent.action.SENDTO 发送短信
ACTION_ANSWER android.intent.action.ANSWER 接听电话

7.4.2向下一个Activity发送数据

使用Bundle对象存储待传递的数据信息

发送

Intent intent = new Intent(this, MainActivity2.class);
//创建一个新包裹
Bundle bundle = new Bundle();
Button send = findViewById(R.id.btn);
bundle.putString("request_time", MyDate.gettime());
bundle.putString("request_content",send.getText().toString());
intent.putExtras(bundle);
startActivity(intent);

接收

TextView  show = findViewById(R.id.show);
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
String test = String.format("hahah\nha%s\n%s",request_time,request_content);
show.setText(test);

7.4.3向上一个Activity返回数据

1.上一个页面打包好请求数据后,调用StartActivityForResult方法执行跳转动作

2.下一个页面接收并解析请求数据,进行相应处理

3.下一个页面返回上一个页面时,打包应答数据并调用SetResult方法返回数据包裹

4.上一个页面重写方法OnActivityResult,解析获得下一个页面的返回数据

package com.example.application2;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.example.application2.Dateutil.MyDate;

public class MainActivity3 extends AppCompatActivity implements View.OnClickListener {

    private String myrequest = "this is a test";
    private String getrequest;
    private ActivityResultLauncher<Intent> register;

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);

        TextView begin = findViewById(R.id.begin);
        begin.setText(myrequest);

        findViewById(R.id.mybtn).setOnClickListener(this);

        register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), o -> {
            if (o != null){
                Intent intent = o.getData();
                if (intent != null && o.getResultCode() == Activity.RESULT_OK){
                    Bundle bundle = intent.getExtras();
                    String request_time = bundle.getString("response");
                    String request_content = bundle.getString("content");
                    getrequest = String.format("hahah\n回复%s\n%s",request_time,request_content);
                    TextView end = findViewById(R.id.end);
                    end.setText(getrequest);
                }
            }
        });
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, MainActivity4.class);
        Bundle bundle = new Bundle();
        bundle.putString("request_time", MyDate.gettime());
        bundle.putString("request_content",myrequest);
        intent.putExtras(bundle);

        register.launch(intent);
    }
}
package com.example.application2;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.icu.util.BuddhistCalendar;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.example.application2.Dateutil.MyDate;

public class MainActivity4 extends AppCompatActivity implements View.OnClickListener {

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main4);

        TextView get = findViewById(R.id.get);
        Bundle bundle = getIntent().getExtras();
        String request_time = bundle.getString("request_time");
        String request_content = bundle.getString("request_content");
        String test = String.format("hahah\n接收%s\n%s",request_time,request_content);
        get.setText(test);
        findViewById(R.id.set).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        bundle.putString("response", MyDate.gettime());
        //待返回的数据
        bundle.putString("content","chli");
        intent.putExtras(bundle);
        setResult(Activity.RESULT_OK,intent);
        finish();
    }
}

7.4.4利用资源配置文件配置字符串

String test = getString(R.string.test);

7.4.5利用元数据传递配置信息

<meta-data android:name="test" android:value="test"></meta-data>

8.Drawable

drawable-ldpi存放低分辨率的图片,基本没有这样的手机了

drawable-mdpi存放中等分辨率的图片,使用较少

drawable-hdpi存放高分辨率的图片 4-5英寸

drawable-xhdpi存放高分辨率的图片 5-5.5英寸

drawable-xxhdpi存放高分辨率的图片 6-6.5英寸

drawable-xxxhdpi存放高分辨率的图片 >7英寸

8.1形状图形

Shape图形又称形状图形,它用来描述常见的几何形状,包括矩形,圆角矩形,圆形,椭圆等等

rectangle矩形

oval椭圆

line直线

ring圆环

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#66ffaa"/>
    <stroke android:width="1dp" android:color="#66ffaa"/>

</shape>
package com.example.application2;

import android.os.Bundle;
import android.view.View;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity6 extends AppCompatActivity implements View.OnClickListener {

    private View v_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main6);
        v_content = findViewById(R.id.content);
        findViewById(R.id.rectangle).setOnClickListener(this);
        findViewById(R.id.oval).setOnClickListener(this);
        v_content.setBackgroundResource(R.drawable.react_drawable);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.rectangle){
            v_content.setBackgroundResource(R.drawable.react_drawable);
        }else if(v.getId() == R.id.oval){
            v_content.setBackgroundResource(R.drawable.react1_drawable);
        }
    }
}

size(尺寸)

size时shape的下级节点,描述形状图形的宽高尺寸,若无size,则与宿主图形一样(View)

  • height
  • width

stroke(描边)

  • color颜色
  • dashGap像素;类型
  • dashWidth虚线的宽度
  • width厚度

dashWidth和dashGap为0为实线

solid填充

color:颜色

padding

上下左右

gradient渐变

  • angle 整形渐变的起始角度
  • type 渐变的类型
类型 说明
linear 线性渐变
radial 放射渐变
sweep 滚动渐变

8.2九宫格图片

test.9.png

上下左右边缘线

黑线区域以内的图像会拉伸,黑线以外的图像保持原状

内部文字只能放在黑线区域内

8.3状态列表图形

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 <item android:drawable="@drawable/test" android:state_pressed="true"/>
 <item android:drawable="@drawable/tests"/>
</selector>

state

状态类型的属性名称 说明 适用的组件
state_pressed 是否按下 按钮Button
state_checked 是否勾选 复选框CheckBox、单选按钮RadioButton
state_focused 是否获取焦点 文本编辑EditText
state_selected 是否选中 各组件通用

8.4复选框CheckBOx

<CheckBox
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="系统checkbox"/>

checked属性

设置是否选择,可以设置默认选择

public class MainActivity7 extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {
    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main7);
         CheckBox cb = findViewById(R.id.select);
         cb.setOnCheckedChangeListener(this);
    }

    //CompoundButton
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        String desc = String.format("您%s这个Checkbox",isChecked?"勾选":"取消");
        buttonView.setText(desc);
    }
}

视图—》文本视图 —》按钮 —》复合按钮 ===》复选框、单选按钮、开关按钮

XML中CompoundButton

  • checked;指定按钮的勾选状态
  • button:指定左侧勾选图标的图像资源

Java代码中CompoundButton

  • setChecked设置按钮的勾选状态
  • setButtonDrawable设置左侧勾选图标的图像资源
  • setOnCheckedChangeListener设置勾选状态变化的监听器
  • isChecked判断按钮是否勾选

8.5Switch

Switch是开关按钮,它在选中和取消选中时可展现的界面元素比复选框丰富

textOn:打开时的文本

textOff:设置左侧关闭的文本

track:设置开关轨道的背景

thumb:设置开关表示的图标

<Switch
    android:id="@+id/select"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="start|center"
    android:text="switch"
    />
//使用checkBox实现Switch开关按钮    
<CheckBox
        android:id="@+id/select"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="start|center"
        android:text="switch"
        //设置按钮为空
        android:button="@null"
        android:background="@drawable/ic_launcher_background"
        />

8.6单选按钮

一组中只能选择一个

RadioButton,放入一个RadioGroup组内部

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="提示信息"
        android:layout_gravity="center"/>
<RadioGroup
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <RadioButton
        android:id="@+id/male"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="man"/>

    <RadioButton
        android:id="@+id/female"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="woman"/>
</RadioGroup>

监听单选按钮

public class MainActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {

    private TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RadioGroup radioGroup = findViewById(R.id.gender);
        tv = findViewById(R.id.show);
        radioGroup.setOnCheckedChangeListener(this);
    }

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        if (checkedId == R.id.male){
            tv.setText("male");
        }else if (checkedId == R.id.female){
            tv.setText("female");
        }
    }
}

8.7文本输入框

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="输入登录信息"/>

去掉默认边框

<EditText
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:text="输入密码信息"
    android:background="@null"
    android:hint="自定义"
    android:inputType="text"/>

inputType:指定输入的文本类型

允许输入的最大长度

hint:指定提示文本的内容

textColorHint指示文本的颜色

输入类型 说明
text 文本
textPassword 文本密码
number 整数型
numberSigned 带符号的数字
numberDecimal 带小数点的数字
numberpassword 数字密码
datetime 时间日期格式
date 日期格式
time 事件格式

8.8焦点变更监听器

Toast.makeText(this,”请输入8位密码”,Toast.LENGTH_SHORT).show();

//弹出提示

LENGTH_SHORT

LENGTH_LONG 持续时间更长

public class MainActivity2 extends AppCompatActivity implements View.OnFocusChangeListener {
    private EditText et_message;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        et_message = findViewById(R.id.message);
        EditText et_password = findViewById(R.id.password);
        et_password.setOnFocusChangeListener(this);
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus){
            String password  = et_message.getText().toString();
            if (TextUtils.isEmpty(password) || password.length() < 8){
                et_message.requestFocus();
                Toast.makeText(this,"请输入8位用户名",Toast.LENGTH_SHORT).show();
            }
        }
    }
}

8.9文本变化监听器

调用编辑框对象的addTextChangelistener方法即可注册文本监听器

文本监听器的接口名位TextWatcher,该接口提供了3个监控方法

  • beforeTextChanged
  • OnTextChanged
  • afterTextChanged

隐藏输入框

public class hidenView {
    public static void HidenInput(Activity ac, View v){
        //从系统获取输入法管理器
        InputMethodManager imm = (InputMethodManager) ac.getSystemService(Context.INPUT_METHOD_SERVICE);
        //关闭输入法    得到窗口管理器
        imm.hideSoftInputFromWindow(v.getWindowToken(),0);
    }
}
package com.example.application3;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.view.inputmethod.EditorBoundsInfo;
import android.widget.EditText;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.example.application3.utils.hidenView;

public class MainActivity2 extends AppCompatActivity{
    private EditText et_password;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        et_password = findViewById(R.id.password);
        et_password.addTextChangedListener(new HidenTextWatcher(et_password,11));
    }

    //定义,当达到指定值后隐藏文本框
    private class HidenTextWatcher implements TextWatcher{
        private EditText et;
        private int maxLength;
        public HidenTextWatcher(EditText et,int maxlength){
            this.et = et;
            this.maxLength = maxlength;
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            String test = s.toString();
            if (test.length() == 11 && maxLength==11){
                hidenView.HidenInput(MainActivity2.this,et_password);
            }
        }
    }
}

8.10提醒对话框

AlertDialog

借助建造器AlertDialog.Builder才能完成设置

调用建造器的create方法生产对话框实例,再调用对话框实例的show方法,在页面提示对话框

public class MainActivity3 extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_dialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
        findViewById(R.id.btn_diolog).setOnClickListener(this);
        tv_dialog = findViewById(R.id.diolog_view);
    }

    @Override
    public void onClick(View v) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("尊敬的用户");
        builder.setMessage("是否卸载");
        builder.setPositiveButton("取消",((dialog, which) -> {
            tv_dialog.setText("没有卸载");
        }));
        builder.setNegativeButton("确认",((dialog, which) -> {
            tv_dialog.setText("卸载成功");
        }));
        AlertDialog alertDialog = builder.create();
        alertDialog.show();
    }
}

setIcon:设置图标

setTitle设置对话框的标题文本

setMessage设置对话框的文本

setPositiveButton设置肯定按钮

setNegativeButton 设置否定按钮

setNeutralButton设置中性按钮

8.11日期选择器

<DatePicker
    android:id="@+id/dp_date"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:calendarViewShown="false"
    android:datePickerMode="spinner" />
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main4);
    findViewById(R.id.dp_btn).setOnClickListener(this);
    dp = findViewById(R.id.dp_date);
    dp_show =findViewById(R.id.dp_dateshow);
}

@Override
public void onClick(View v) {
    if (v.getId() == R.id.dp_btn){
        String format = String.format("日期是%d年%d月%d日",dp.getYear(),dp.getMonth()+1,dp.getDayOfMonth());
        dp_show.setText(format);
    }
}

新建一个Calender实例获取时间

Calendar calendar = Calendar.getInstance();
calendar.get(Calendar.YEAR);
calendar.get(Calendar.MONTH);
calendar.get(Calendar.DAY_OF_MONTH);

DatePickDiolog

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.dp_btn){
/*            String format = String.format("日期是%d年%d月%d日",dp.getYear(),dp.getMonth()+1,dp.getDayOfMonth());
            dp_show.setText(format);*/
          /*  Calendar calendar = Calendar.getInstance();
            calendar.get(Calendar.YEAR);
            calendar.get(Calendar.MONTH);
            calendar.get(Calendar.DAY_OF_MONTH);*/
            DatePickerDialog dialog = new DatePickerDialog(this,this,2090,5,11);
            dialog.show();
        }
    }

    @Override
    public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
        String format = String.format("日期是%d年%d月%d日",year,month+1,dayOfMonth);
        dp_show.setText(format);
    }

8.12时间选择器

TimePickDialog

TimePicker类似DatePicker,可以选择具体的小时

TimePickDiolog的用法类似DatePickDiolog

TimePickDialog dialog = new TimePickDialog(this,[android.R.style.Theme.Holo_Light_dialog    ],this,calendar.get(Calendar.HOUR_OF_DAY),calender.get(Calendar.MINUTE),true)
    //android.R.style.Theme.Holo_Light_dialog 设置为横向选择样式
    //true 为是否开启24小时制

8.13小案例

页面布局

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

    <RadioGroup
        android:id="@+id/rg_login"
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_layout_height"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/rg_withpwd"
            android:checked="true"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="@string/login_withpwd"
            android:textSize="@dimen/item_line_height"
            />

        <RadioButton
            android:id="@+id/withoutpwd"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="@string/login_withoutpwd"
            android:textSize="@dimen/item_line_height"/>
    </RadioGroup>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_layout_height"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@string/phonenumber"
            android:gravity="center"
            android:textColor="@color/black"
            android:textSize="@dimen/item_line_height"/>

        <EditText
            android:id="@+id/et_phone"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="5dp"
            android:layout_marginStart="5dp"
            android:background="@drawable/shape"
            android:hint="@string/inputnumber"
            android:maxLength="11"
            android:textColor="@color/black"
            android:inputType="number"
            android:textColorHint="@color/gray"
            android:textSize="@dimen/item_line_height"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_layout_height"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_password"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@string/loginpassword"
            android:gravity="center"
            android:textColor="@color/black"
            android:textSize="@dimen/item_line_height"/>



        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1">

            <EditText
                android:id="@+id/et_password"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:layout_marginTop="5dp"
                android:layout_marginBottom="5dp"
                android:layout_marginStart="5dp"
                android:background="@drawable/shape"
                android:hint="@string/inputpassword"
                android:maxLength="6"
                android:textColor="@color/black"
                android:inputType="numberPassword"
                android:textColorHint="@color/gray"
                android:textSize="@dimen/item_line_height"/>

            <Button
                android:id="@+id/btn_password"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="@string/forgetpassword"
                android:layout_alignParentEnd="true"
                android:background="@color/gray"
                android:textColorHint="@color/gray"
                android:textSize="@dimen/item_line_height"/>
        </RelativeLayout>

    </LinearLayout>
    <CheckBox
        android:id="@+id/check_number"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/remenumber"/>

    <Button
        android:id="@+id/beginlogin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/login"
        android:textSize="@dimen/item_line_height"
        android:textColor="@color/gray"/>
</LinearLayout>

代码逻辑

package com.example.application3;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ViewUtils;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.example.application3.utils.hidenView;

import java.util.Random;

public class MainActivity5 extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener, View.OnClickListener {

    private TextView tv_password;
    private EditText et_password;
    private Button btn_password;

    private CheckBox checkBox;

    private RadioButton withpwd;
    RadioButton withoutpwd;
    private ActivityResultLauncher<Intent> register;
    private EditText et_phone;
    private Button btn_login;

    private String clearPassword="111111";
    private String verifycode;

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main5);
        RadioGroup group= findViewById(R.id.rg_login);
        group.setOnCheckedChangeListener(this);
        tv_password = findViewById(R.id.tv_password);
        et_password = findViewById(R.id.et_password);
        btn_password = findViewById(R.id.btn_password);
        btn_password.setOnClickListener(this);
        checkBox = findViewById(R.id.check_number);
        et_phone = findViewById(R.id.et_phone);
        withpwd = findViewById(R.id.rg_withpwd);
        withoutpwd = findViewById(R.id.withoutpwd);
        btn_login = findViewById(R.id.beginlogin);
        btn_login.setOnClickListener(this);
        et_phone.addTextChangedListener(new HideTextWatcher(et_phone,11));
        et_password.addTextChangedListener(new HideTextWatcher(et_password,6));
        register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult o) {
                Intent intent = o.getData();
                if (intent != null && o.getResultCode()== Activity.RESULT_OK){
                    String newpassword = intent.getStringExtra("password");
                    clearPassword = newpassword;
                }
            }
        });
    }

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        if (checkedId==R.id.rg_withpwd){
            tv_password.setText(getString(R.string.loginpassword));
            et_password.setHint(getString(R.string.inputpassword));
            btn_password.setText(getString(R.string.forgetpassword));
            checkBox.setVisibility(View.VISIBLE);
        }else if (checkedId == R.id.withoutpwd){
            tv_password.setText(getString(R.string.verifycode));
            et_password.setHint(getString(R.string.input_verifycode));
            btn_password.setText(getString(R.string.getverifycode));
            checkBox.setVisibility(View.GONE);
        }
    }

    @Override
    public void onClick(View v) {
        String phone = et_phone.getText().toString();
        if (v.getId() == R.id.btn_password){
            if (withpwd.isChecked()){
                Intent intent = new Intent(this, MainActivity7.class);
                intent.putExtra("phone",phone);
                register.launch(intent);
            }else if (withoutpwd.isChecked()){
                verifycode = String.format("%06d",new Random().nextInt(999999));
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("验证码");
                builder.setMessage(verifycode);
                builder.setPositiveButton("确认",null);
                AlertDialog dialog = builder.create();
                dialog.show();
            }
        }else if (v.getId() == R.id.beginlogin){
            String password = et_password.getText().toString();
            if (withpwd.isChecked()){
                if (!password.equals(clearPassword)){
                    Toast.makeText(this,"请输入正确的密码",Toast.LENGTH_SHORT).show();
                    return;
                }
                LoinSuccess();
            }else if (withoutpwd.isChecked()){
                if (!password.equals(verifycode)){
                    Toast.makeText(this,"请输入正确的验证码",Toast.LENGTH_SHORT).show();
                    return;
                }
                LoinSuccess();
            }
        }
    }

    private void LoinSuccess() {
        String mes = "登录成功";
        AlertDialog.Builder builder= new AlertDialog.Builder(this);
        builder.setTitle("登录成功");
        builder.setMessage(mes);
        builder.setPositiveButton("确认返回",((dialog, which) -> {
            finish();
        }));
        builder.setNegativeButton("我再看看",null);
        Dialog dialog = builder.create();
        dialog.show();
    }

    private class HideTextWatcher implements TextWatcher {
        private EditText et;

        private int maxlength;

        public HideTextWatcher(EditText et_phone,int maxlength){
            this.et = et_phone;
            this.maxlength = maxlength;
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }

        @Override
        public void afterTextChanged(Editable s) {
            if (s.toString().length() == maxlength ){
                hidenView.HidenInput(MainActivity5.this,et);
            }
        }
    }
}

找回密码布局

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_layout_height"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@string/newpassword"
            android:gravity="center"
            android:textColor="@color/black"
            android:textSize="@dimen/item_line_height"/>

        <EditText
            android:id="@+id/new_password"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="5dp"
            android:layout_marginStart="5dp"
            android:background="@drawable/shape"
            android:hint="@string/inputnewpassword"
            android:maxLength="6"
            android:textColor="@color/black"
            android:inputType="number"
            android:textColorHint="@color/gray"
            android:textSize="@dimen/item_line_height"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_layout_height"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@string/newpasswordagain"
            android:gravity="center"
            android:textColor="@color/black"
            android:textSize="@dimen/item_line_height"/>

        <EditText
            android:id="@+id/new_passwordagain"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_marginTop="5dp"
            android:layout_marginBottom="5dp"
            android:layout_marginStart="5dp"
            android:background="@drawable/shape"
            android:hint="@string/inputnewpasswordagain"
            android:maxLength="6"
            android:textColor="@color/black"
            android:inputType="number"
            android:textColorHint="@color/gray"
            android:textSize="@dimen/item_line_height"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/item_layout_height"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_newverify"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="@string/verifycode"
            android:gravity="center"
            android:textColor="@color/black"
            android:textSize="@dimen/item_line_height"/>



        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1">

            <EditText
                android:id="@+id/et_newverify"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:layout_marginTop="5dp"
                android:layout_marginBottom="5dp"
                android:layout_marginStart="5dp"
                android:background="@drawable/shape"
                android:hint="@string/input_verifycode"
                android:maxLength="6"
                android:textColor="@color/black"
                android:inputType="numberPassword"
                android:textColorHint="@color/gray"
                android:textSize="@dimen/item_line_height"/>

            <Button
                android:id="@+id/btn_newverify"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="@string/getverifycode"
                android:layout_alignParentEnd="true"
                android:background="@color/gray"
                android:textColorHint="@color/gray"
                android:textSize="@dimen/item_line_height"/>
        </RelativeLayout>

    </LinearLayout>
    <Button
        android:id="@+id/beginnewpassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/click"
        android:textColor="@color/gray"
        android:textSize="@dimen/item_line_height"/>



</LinearLayout>

找回密码逻辑

package com.example.application3;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.util.Random;

public class MainActivity7 extends AppCompatActivity implements View.OnClickListener {

    private String phone;
    private String verifycode;
    private EditText tv_newpassword;
    private EditText tv_newpasswordagin;
    private EditText et_newverify;

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main7);
        phone = getIntent().getStringExtra("phone");
        findViewById(R.id.btn_newverify).setOnClickListener(this);
        findViewById(R.id.beginnewpassword).setOnClickListener(this);
        tv_newpassword = findViewById(R.id.new_password);
        tv_newpasswordagin = findViewById(R.id.new_passwordagain);
        et_newverify = findViewById(R.id.et_newverify);
    }

    @Override
    public void onClick(View v) {
        if (v.getId()==R.id.btn_newverify){
            verifycode = String.format("%06d",new Random().nextInt(999999));
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("验证码");
            builder.setMessage(verifycode);
            builder.setPositiveButton("确认",null);
            AlertDialog dialog = builder.create();
            dialog.show();
        }else if (v.getId() == R.id.beginnewpassword){
            String passwd = tv_newpassword.getText().toString();
            String newpasswd = tv_newpasswordagin.getText().toString();
            String mycode = et_newverify.getText().toString();
            if (passwd.length()<6 || newpasswd.length()<6){
                Toast.makeText(this,"密码长度不合适",Toast.LENGTH_SHORT).show();
                return;
            }
            if (!passwd.equals(newpasswd)){
                Toast.makeText(this,"两次密码不一致",Toast.LENGTH_SHORT).show();
                return;
            }
            if (!mycode.equals(verifycode)){
                Toast.makeText(this,"验证码错误",Toast.LENGTH_SHORT).show();
                return;
            }
            Intent intent = new Intent();
            intent.putExtra("password",passwd);
            setResult(Activity.RESULT_OK,intent);
            finish();
        }
    }
}

9.SharePreferences

SharePreferences是Android的一个轻量级存储工具,采用键值对的方式

/data/data/应用包名/shared_prefs/文件名.xml

App个性化配置信息,用户使用App的行为信息、临时需要保存的片段信息

package com.richuff.application4;

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

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_name;
    private EditText et_age;
    private EditText et_height;
    private EditText et_weight;
    private SharedPreferences preferences;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name = findViewById(R.id.et_name);
        et_age = findViewById(R.id.et_age);
        et_height = findViewById(R.id.et_height);
        et_weight = findViewById(R.id.et_weight);

        findViewById(R.id.submit).setOnClickListener(this);

        preferences = getSharedPreferences("config", Context.MODE_PRIVATE);
    }

    @Override
    public void onClick(View v) {
        String name = et_name.getText().toString();
        String age = et_age.getText().toString();
        String height = et_height.getText().toString();
        String weight = et_weight.getText().toString();
        SharedPreferences.Editor ediit = preferences.edit();
        ediit.putString("name",name);
        ediit.putInt("age",Integer.parseInt(age));
        ediit.putFloat("height",Float.parseFloat(height));
        ediit.putFloat("weight",Float.parseFloat(weight));
        ediit.apply();
    }
}

data/data/包名/shared_prefs

private void reload() {
    //取数据
    String name =  preferences.getString("name",null);
    if (name!=null){
        et_name.setText(name);
    }
    int age = preferences.getInt("age",0);
    if (age !=0){
        et_age.setText(String.valueOf(age));
    }
    float height = preferences.getFloat("height",0f);
    if (height !=0f){
        et_height.setText(String.valueOf(height));
    }
    float weight = preferences.getFloat("weight",0f);
    if (height !=0f){
        et_weight.setText(String.valueOf(weight));
    }
}

10.SQLLite

1.管理类

openDatabase:打开指定路径的数据库

isopen:判断数据库是否打开

close:关闭数据库

getVersion:获取数据库的般般号

setVersion:设置数据库的版本号

package com.example.application3;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

public class MainActivity8 extends AppCompatActivity implements View.OnClickListener {

    private String mDatabase;
    private TextView tv_database;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main8);
        findViewById(R.id.createdatabase).setOnClickListener(this);
        findViewById(R.id.deletedatabase).setOnClickListener(this);
        //数据库的路径
        mDatabase = getFilesDir() + "/test.db";
        tv_database = findViewById(R.id.tv_view);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.createdatabase){
            SQLiteDatabase sqLiteDatabase = openOrCreateDatabase(mDatabase, Context.MODE_PRIVATE, null);
            String desc = String.format("%s创建%s",sqLiteDatabase.getPath(), "成功");
            tv_database.setText(desc);
        }else if(v.getId() == R.id.deletedatabase) {
            boolean result = deleteDatabase(mDatabase);
            String desc = String.format("%s删除%s",mDatabase,result ? "成功" : "失败");
            tv_database.setText(desc);
        }
    }
}

SQLiteOPenHeaper

package com.example.application3.datebase;

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

import com.example.application3.enity.user;

public class UserDBHelper extends SQLiteOpenHelper {
    
    private static final String DB_NAME = "user.db";
    private static final int DB_VSERSION = 1;

    private static final String TABLE_NAME = "user_info";
    public static UserDBHelper mhelper = null;
    private SQLiteDatabase mRdb = null;

    private SQLiteDatabase mWdb = null;
    private UserDBHelper(Context context){
        super(context,DB_NAME,null,DB_VSERSION);
    }

    //利用单例模式获取数据库帮助器的唯一实例
    public static UserDBHelper getInstance(Context context){
        if (mhelper == null){
            mhelper = new UserDBHelper(context);
        }
        return mhelper;
    }
    //打开数据库的读连接
    public SQLiteDatabase openReadLink(){
        if (mRdb == null || mRdb.isOpen()){
            mRdb = mhelper.getReadableDatabase();
        }
        return mRdb;
    }
    //打开数据库的写连接
    public SQLiteDatabase openWriteLink(){
        if (mWdb == null || mWdb.isOpen()){
            mWdb = mhelper.getWritableDatabase();
        }
        return mWdb;
    }
    //关闭数据库
    public  void closeLink(){
        if (mRdb != null && mRdb.isOpen()){
            mRdb.close();
            mRdb = null;
        }
        if (mWdb != null && mWdb.isOpen()){
            mWdb.close();
            mWdb = null;
        }
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "create table if not exists user_info(_id integer primary key autoincrement not null,"
                +"name varchar not null"+")";
        db.execSQL(sql);
    }
    
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}

增删改

//增
public long insert(user user){
    ContentValues values = new ContentValues();
    values.put("name",user.name);
    return mWdb.insert(TABLE_NAME,null,values);
}
//删
public long delete(String name){
    return mWdb.delete(TABLE_NAME,"name=?",new String[]{name});
}
//改
public long update(user user){
    ContentValues contentValues = new ContentValues();
    contentValues.put("name",user.name);
    return mWdb.update(TABLE_NAME,contentValues,"name=?",new String[]{user.name});
}

2.事务类

beginTransaction:开始事务

setTransactionSuccessful:设置事务成功的标志

endTransaction:结束事务

3.数据库版本更新

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

4.外部存储和内部存储,外部SD卡

读写文件操作类

package com.richuff.application4.enity;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileUtil {
    public static void saveText(String path,String txt) throws IOException {
        BufferedWriter os = null;
        try{
            os = new BufferedWriter(new FileWriter(path));
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (os!=null){
                os.close();
            }
        }
    }
    
    public static String openText(String path) throws IOException {
        BufferedReader rs = null;
        StringBuffer sb = new StringBuffer();
        try{
            rs = new BufferedReader(new FileReader(path));
            String line = null;
            while((line=rs.readLine())!=null){
                sb.append(line);
            }
        }catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if (rs != null) {
                rs.close();
            }
        }
        return rs.toString();
    }
}

获取外部存储的私有空间

String path  = directory+ File.separatorChar+fileName;

获取外部存储公共空间

//获取外部公共空间
directory=Environment.getExternalStorageState();

内部存储私有空间

//内部存储私有空间
directory = getFilesDir().toString();

5.在存储卡读写图片文件

Bitmap

BitmaoFactory读写各种来源的图片

decodeResource 从资源文件中读取图片信息

decodeFile 指定路径的图片获取到bitmap对象

decodeStream从输入流获取位图数据

@Override
public void onClick(View v) {
    if (v.getId() == R.id. create){
        String fileName = System.currentTimeMillis() + ".png";
        path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString()+ File.separatorChar+fileName;
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.gl);
        FileUtil.saveImage(path,bitmap);
    }else if (v.getId() == R.id.delete){
        Bitmap bitmap = FileUtil.openImage(path);
        im.setImageBitmap(bitmap);
    }
}

文件操作FileUtil

public static void saveImage(String path, Bitmap bitmap) {
    FileOutputStream fos = null;
    try{
        fos = new FileOutputStream(path);
        bitmap.compress(Bitmap.CompressFormat.PNG,100,fos);
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        if (fos != null){
            try {
                fos.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public static Bitmap openImage(String path) {
    Bitmap bitmap = null;
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(path);
        bitmap = decodeStream(fis);
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        if (fis != null){
            try {
                fis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    return bitmap;
}

11.Application

11.1Application

Application是Android的一大组件,在App运行过程中有且仅有一个Application对象贯穿整个生命周期

package com.richuff.application4;

import android.content.res.Configuration;
import android.util.Log;

import androidx.annotation.NonNull;

public class Application extends android.app.Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("richu","onCreate");
    }

    //Andriod设备上不会执行
    @Override
    public void onTerminate() {
        super.onTerminate();
        Log.d("richu","onTerminate");
    }

    //配置改变时
    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d("richu","onConfigure");
    }
}

11.2全局变量

适用:会频繁读取的信息,用户名

package com.richuff.application4;

import android.content.res.Configuration;
import android.util.Log;

import androidx.annotation.NonNull;

import java.util.HashMap;

public class Application extends android.app.Application {
    private static Application application;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    public static Application getinstance(){
        return application;
    }

    //Andriod设备上不会执行
    @Override
    public void onTerminate() {
        super.onTerminate();
        application = this;
        Log.d("richu","onTerminate");
    }

    public HashMap<String,String> infoMap= new HashMap<>();

    //配置改变时
    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d("richu","onConfigure");
    }
}
application.infoMap.put("name","richu");
name = application.infoMap.get("name");

12.Room

implementation ("androidx.room:room-runtime:2.2.6")
implementation ("androidx.room:room-ktx:2.2.6")
testImplementation ("androidx.room:room-testing:2.2.6")

数据实体 Entry 表示应用的数据库中的表。数据实体用于更新表中的行所存储的数据以及创建新行供插入。

数据访问对象 (DAO) 提供应用在数据库中检索、更新、插入和删除数据所用的方法。

数据库类持有数据库,并且是应用数据库底层连接的主要访问点。数据库类为应用提供与该数据库关联的 DAO 的实例。

package com.richuff.application4.dao;

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

import com.richuff.application4.enity.Books;

import java.util.List;

@Dao
public interface BookDao {
    @Insert()
    void insert(Books... books);

    @Delete
    void delete(Books... books);

    @Update
    int update(Books... books);

    @Query("select * from Books")
    List<Books> queryAll();
    
    @Query("select * from Books where id= :id")
    Books queryById(int id);
}
//entities表示有哪些表  version为版本号 exportSchema表示是否导出数据库的json串
@Database(entities = {Books.class},version = 1,exportSchema = false)
public abstract class BookDatabase extends RoomDatabase {
    
}

在自定义的Appication创建BookDatabase实例

bookDatabase = Room.databaseBuilder(this,BookDatabase.class,"book").
    //允许迁移数据库
    addMigrations().
    //允许主线程中操作数据库
    allowMainThreadQueries().build();

13.ContentProvider

1.service端代码 编写

为App存储内部数据提供统一的外部接口,让不同的外部应用之间得以共享数据

<provider
          android:name=".provider.UserinfoProvider"
          android:authorities="com.richuff.server.provider.UserinfoProvider"
          android:enabled="true"
          android:exported="true" />

uri 通用资源标识符 代表数据操作的地址 每一个ContentProvider都有一个唯一的值。ContentProvider使用的Uri的语法结构如下

在 Android 中,ContentProvider使用的Uri(统一资源标识符)有一个标准的语法结构。一般形式为content://authority/path

conten://是一个固定的前缀,用于表明这是一个内容提供者(ContentProvider)相关的Uri,所有通过ContentResolver访问ContentProviderUri都以这个前缀开头。

关键部分之一,它用于唯一标识一个ContentProvider。通常,authority是一个字符串,格式类似于一个包名或者反向域名(例如com.example.app.provider)。在AndroidManifest.xml文件中,当注册ContentProvider时,android:authorities属性的值就是这个authority。

<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.myapp.provider"
    android:exported="true"/>

应用通过这个authority来定位要访问的ContentProvider。不同的ContentProvider应该有不同的authority,以避免冲突。

path是可选的,它用于在一个ContentProvider内部进一步划分资源或者数据集合。例如,一个ContentProvider可能管理用户信息和订单信息,那么可以通过不同的path来区分访问用户信息(如content://com.example.myapp.provider/users)和访问订单信息(如content://com.example.myapp.provider/orders)。

path可以包含多层路径,例如content://com.example.myapp.provider/users/profile,可以用于更精细地划分数据访问路径,这里可能表示访问用户信息中的用户资料部分。

public class UserinfoProvider extends ContentProvider {
    private UserinfoDatabase userinfoDatabase;
    public UserinfoProvider() {

    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    //添加
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        if ("/user".equals(uri.getPath())) {
            SQLiteDatabase db = userinfoDatabase.getWritableDatabase();
            db.insert(UserinfoDatabase.TABLE_NAME, null, values);
        }
        return uri;
    }

    @Override
    public boolean onCreate() {
        userinfoDatabase = UserinfoDatabase.getInstance(getContext());
        return true;
    }

    //查询
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = userinfoDatabase.getReadableDatabase();
        return db.query(UserinfoDatabase.TABLE_NAME, projection, selection, selectionArgs, null, null, null);
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

2.client端代码

通过ContentResolver访问数据

ContentValues values = new ContentValues();
values.put(UserinfoContent.PASSWORD,"test");
values.put(UserinfoContent.PHONE,"11111");
values.put(UserinfoContent.ISCHECKED,1);
getContentResolver().insert(UserinfoContent.CONTENT_URI,values);

使用getContentResolver()获取数据

public void onClick(View v) {
        if (v.getId() == R.id.read){
            //查询
            Cursor cursor = getContentResolver().query(UserinfoContent.CONTENT_URI,null,null,null,null,null);
            if (cursor != null){
                while (cursor.moveToNext()){
                    //接收数据
                    User info = new User();
                    info.id = cursor.getInt(cursor.getColumnIndex(UserinfoContent._ID));
                    info.name = cursor.getString(cursor.getColumnIndex(UserinfoContent.NAME));

                    info.height = cursor.getLong(cursor.getColumnIndex(UserinfoContent.HEIGHT));
                    info.age = cursor.getInt(cursor.getColumnIndex(UserinfoContent.AGE));
                    info.weight = cursor.getFloat(cursor.getColumnIndex(UserinfoContent.WEIGHT));
				
                    Log.d("mytag", info.toString());
                }
                //关闭cursor
                cursor.close();
            }

        }else if (v.getId() == R.id.write){
            //保存
            ContentValues values = new ContentValues();
            values.put(UserinfoContent.NAME,name.getText().toString());
            values.put(UserinfoContent.AGE,age.getText().toString());
            values.put(UserinfoContent.HEIGHT,height.getText().toString());
            values.put(UserinfoContent.WEIGHT,weight.getText().toString());
            //插入数据
            getContentResolver().insert(UserinfoContent.CONTENT_URI,values);
            //弹窗提示
            Toastshow.show(this,"保存成功");
        }
    }

在android11之后要提前声明
AndroidManifest.xml文件中声明需要访问的其他软件包

<queries>
    <provider android:authorities="com.richuff.server.provider.UserinfoProvider"/>
</queries>

提供contentprovider的uri

public class UserinfoContent {
    public static final String AUTHORITIES = "com.richuff.server.provider.UserinfoProvider"; //服务端的具体类名
    //访问内容器的URI
    public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITIES+"/user");

    public static final String HEIGHT = "height";
    public static final String NAME = "name";

    public static final String WEIGHT = "weight";
    public static final String AGE = "age";
}

3.进行数据的删除

在provider中修改delete函数

public int delete(Uri uri, String selection, String[] selectionArgs) {
    int count = 0;
    //删除多行
    if (URI_MATCHER.match(uri) == USERS){
        SQLiteDatabase db = UserinfoDatabase.mhelper.getWritableDatabase();
        count = db.delete(UserinfoDatabase.TABLE_NAME,selection,selectionArgs);
        db.close();
    } 
    //删除单行
    else if (URI_MATCHER.match(uri) == USER) {
        String id = uri.getLastPathSegment();
        SQLiteDatabase db = UserinfoDatabase.mhelper.getWritableDatabase();
        count = db.delete(UserinfoDatabase.TABLE_NAME,"_id=?",new String[]{id});
        db.close();
    }
    return count;
}

使用provider

//删除多行
int count = getContentResolver().delete(UserinfoContent.CONTENT_URI, "name=?", new String[]{"rc"});
if (count > 0){
    Toastshow.show(this,"删除成功");
}
//删除单行
Uri uri = ContentUris.withAppendedId(UserinfoContent.CONTENT_URI,2); //在uri后面附加一个/2
int count = getContentResolver().delete(uri, "name=?", new String[]{"rc"});
if (count > 0){
    Toastshow.show(this,"删除成功");
}

在provider中选择路径匹配

private static final UriMatcher URI_MATCHER= new UriMatcher(UriMatcher.NO_MATCH);

private static final int USERS = 1;
private static final int USER = 2;

static {
    URI_MATCHER.addURI(UserinfoContent.AUTHORITIES,"/user",USERS);
    URI_MATCHER.addURI(UserinfoContent.AUTHORITIES,"/user/#",USER);
}

14.运行时动态申请权限

提前声明需要的权限

<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>

1.检查App是否开启了某个权限

private static final String[] PERMISSION_CONTACTS = new String[]{
    Manifest.permission.READ_CONTACTS,
    Manifest.permission.WRITE_CONTACTS
};

private static final String[] PERMISSION_SMS = new String[]{
    Manifest.permission.READ_SMS,
    Manifest.permission.SEND_SMS
};
private static final int REQUEST_CODE_CONTACTS = 1;
private static final int REQUEST_CODE_SMS = 2;

2.请求系统弹窗,以便用户选择是否开启权限

public static boolean checkPermission(Activity act,String[] permissions,int requestCode){
    int check = PackageManager.PERMISSION_GRANTED;
    for (String permission:permissions){
        check = ContextCompat.checkSelfPermission(act,permission);
        if (check != PackageManager.PERMISSION_GRANTED){
            break;
        }
    }
    if (check != PackageManager.PERMISSION_GRANTED){
        ActivityCompat.requestPermissions(act, permissions, requestCode);
        return false;
    }

    return true;
}

3.判断用户的权限选择结果

public static boolean checkGrant(int[] grantResults) {
    if (grantResults != null) {
        for (int grantResult : grantResults) {
            if (grantResult != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }
    return false;
}

跳转到setting页面

public void goToSetting(){
    Intent intent = new Intent();
    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    intent.setData(Uri.fromParts("package",getPackageName(),null));
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

15.通讯录

1.添加联系人

private void addContact(ContentResolver contentResolver, Contact contact) {
    ContentValues values = new ContentValues();

    Uri uri = contentResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
    long rawContentID = ContentUris.parseId(uri);
    ContentValues name = new ContentValues();
    //关联联系人编号
    name.put(ContactsContract.Data.RAW_CONTACT_ID,rawContentID);
    //姓名的数据类型
    name.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
    //添加的类型 1表示家庭 2表示工作
    name.put(ContactsContract.Data.DATA2,contact.name);
    //联系人的姓名
    contentResolver.insert(ContactsContract.Data.CONTENT_URI,name);

    ContentValues phone = new ContentValues();
    //关联联系人编号
    phone.put(ContactsContract.Data.RAW_CONTACT_ID,rawContentID);
    //姓名的数据类型
    phone.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
    //添加的类型 1表示家庭 2表示工作
    phone.put(ContactsContract.Data.DATA1,contact.phone);
    phone.put(ContactsContract.Data.DATA2,ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
    //联系人的手机号
    contentResolver.insert(ContactsContract.Data.CONTENT_URI,phone);

    ContentValues email = new ContentValues();
    //关联联系人编号
    email.put(ContactsContract.Data.RAW_CONTACT_ID,rawContentID);
    //姓名的数据类型
    email.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
    //添加的类型 1表示家庭 2表示工作
    email.put(ContactsContract.Data.DATA1,contact.email);
    email.put(ContactsContract.Data.DATA2,ContactsContract.CommonDataKinds.Email.TYPE_WORK);
    //联系人的手机号
    contentResolver.insert(ContactsContract.Data.CONTENT_URI,email);
}

2.批量添加联系人

public void addFullContacts(ContentResolver contentResolver, Contact contact){
    ContentProviderOperation op_main = ContentProviderOperation.
        newInsert(ContactsContract.Data.CONTENT_URI).withValue(ContactsContract.RawContacts.ACCOUNT_NAME,null).
        build();

    ContentProviderOperation op_name = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).
        //将第0个操作的id,即raw_contracts的id作为data表中的raw_contract_id
        withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,0).
        withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
        .withValue(ContactsContract.Data.DATA2,contact.name)
        .build();

    ContentProviderOperation op_phone = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).
        //将第0个操作的id,即raw_contracts的id作为data表中的raw_contract_id
        withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,0).
        withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
        .withValue(ContactsContract.Data.DATA1,contact.phone)
        .withValue(ContactsContract.Data.DATA2,ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
        .build();

    ContentProviderOperation op_email = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).
        //将第0个操作的id,即raw_contracts的id作为data表中的raw_contract_id
        withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID,0).
        withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
        .withValue(ContactsContract.Data.DATA1,contact.email)
        .withValue(ContactsContract.Data.DATA2,ContactsContract.CommonDataKinds.Email.TYPE_WORK)
        .build();

    ArrayList<ContentProviderOperation> operations = new ArrayList<>();
    operations.add(op_main);
    operations.add(op_name);
    operations.add(op_phone);
    operations.add(op_email);

    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY,operations);
    } catch (OperationApplicationException e) {
        throw new RuntimeException(e);
    } catch (RemoteException e) {
        throw new RuntimeException(e);
    }
}

3.查询联系人

@SuppressLint("Range")
public void readPhoneContacts(ContentResolver resolver){
    Cursor cursor = resolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null, null,null);
    while (cursor.moveToNext()){
        int rawContactId = cursor.getInt(0);
        Uri uri = Uri.parse("content://com.android.contacts/contacts/" + rawContactId + "/dataa");
        Cursor query = resolver.query(uri, new String[]{ContactsContract.Data.MIMETYPE, ContactsContract.Data.DATA1, ContactsContract.Data.DATA2}, null, null, null);

        Contact contact = new Contact();
        while (query.moveToNext()){
            String data1 = query.getString(query.getColumnIndex(ContactsContract.Data.DATA1));
            String mimeType = query.getString(query.getColumnIndex(ContactsContract.Data.MIMETYPE));
            //查询姓名
            if (mimeType.equals(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)){
                contact.name = data1;
            }//查询电话号
            else if (mimeType.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)){
                contact.phone = data1;
            }//查询邮箱
            else if (mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)){
                contact.email = data1;
            }
        }
        query.close();
    }
    cursor.close();
}

16.短信

public class MainActivity4 extends AppCompatActivity {

    public SmsGetObserver mObserver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main4);

        //给指定的uri注册内容监听器,一旦发生数据变化,就触发观察器的onChange方法
        Uri uri = Uri.parse("content://sms");
        mObserver = new SmsGetObserver(this);
        //notifyForDescendants为false表示uri的精确匹配,为true表示可以匹配其派生路径
        getContentResolver().registerContentObserver(uri,true,mObserver);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getContentResolver().unregisterContentObserver(mObserver);
    }

    private static class SmsGetObserver extends ContentObserver {

        private final Context mContext;
        public SmsGetObserver(Context context) {
            super(new Handler(Looper.getMainLooper()));
            this.mContext = context;
        }

        @SuppressLint("Range")
        @Override
        public void onChange(boolean selfChange, @Nullable Uri uri) {
            super.onChange(selfChange, uri);

            if (uri == null) {
                return;
            }
            if (uri.toString().contains("content://sms/raw") || uri.toString().equals("content://sms")){
                return;
            }
            Cursor cursor = mContext.getContentResolver().query(uri, new String[]{"address", "body", "date"}, null, null, "date DESC");
            if (cursor.moveToNext()){
                 String sender = cursor.getString(cursor.getColumnIndex("address"));
                String content = cursor.getString(cursor.getColumnIndex("body"));

                Log.d("mytag", String.format("sender:%s,content:%s",sender,content));
            }
            cursor.close();
        }
    }
}

选择图片并且显示

public class MainActivity5 extends AppCompatActivity implements View.OnClickListener {
    private ImageView view;
    private  ActivityResultLauncher<Intent> register;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main5);

        view = findViewById(R.id.e_image);
        view.setOnClickListener(this);

        //跳转到系统相册,并且返回
        register = registerForActivityResult(new StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult o) {
                if (o.getResultCode() == RESULT_OK){
                    Intent intent = o.getData();
                    //选择图片的路径
                    Uri uri = intent.getData();
                    if (uri != null){
                        view.setImageURI(uri);
                        Log.d("mytag", uri.toString());
                    }
                }
            }
        });
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.e_image){
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

            intent.setType("image/*");
            register.launch(intent);
        }
    }
}

跳转到短信,并且发送短信

public class MainActivity5 extends AppCompatActivity implements View.OnClickListener {
    private EditText title;
    private EditText phone;
    private ImageView view;
    private EditText content;
    private  ActivityResultLauncher<Intent> register;

    private Uri uri;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        view = findViewById(R.id.e_image);
        view.setOnClickListener(this);
        findViewById(R.id.e_send).setOnClickListener(this);

        phone = findViewById(R.id.e_phone);
        title = findViewById(R.id.e_title);
        content = findViewById(R.id.e_content);

    @Override
    public void onClick(View v) {
		if (v.getId() == R.id.e_send) {
            sendMsm(phone.getText().toString(),
                    title.getText().toString(),
                    content.getText().toString());
        }
    }

    private void sendMsm(String phone, String title, String message) {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        //intent的接收者将被准许读取携带的uri数据
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.putExtra("address",phone);
        intent.putExtra("title",title);
        intent.putExtra("sms_body",message);
        //图片 文件流
        intent.putExtra(Intent.EXTRA_STREAM,uri);
        //附件的类型
        intent.setType("image/*");
        //系统会弹出窗口让你选择
        startActivity(intent);
    }
}

通过MediaStore查询图片

PermissionUtil

public class PermissionUtil {
    public static boolean checkPermission(Activity act,String[] permissions,int requestCode){
        int check = PackageManager.PERMISSION_GRANTED;
        for (String permission:permissions){
            check = ContextCompat.checkSelfPermission(act,permission);
            if (check != PackageManager.PERMISSION_GRANTED){
                break;
            }
        }
        if (check != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(act, permissions, requestCode);
            return false;
        }

        return true;
    }

    public static boolean checkGrant(int[] grantResults) {
        if (grantResults != null) {
            for (int grantResult : grantResults) {
                if (grantResult != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
}

Activity

权限

<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
public class MainActivity6 extends AppCompatActivity {

    private static final String[] PERMISSION = new String[]{
            Manifest.permission.READ_MEDIA_IMAGES
    };

    private static final int REQUEST_CODE_ALL = 1;

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_ALL && PermissionUtil.checkGrant(grantResults)){
            LoadImageList();
            ShowImageGrid();
        }
    }

    private ArrayList<ImageInfo> mImageInfoList = new ArrayList<>();
    private GridLayout gl;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        gl = findViewById(R.id.e_grid);
        //手动让MediaStore扫描入库
        MediaScannerConnection.scanFile(this,new String[]{
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString()
        },null,null);

        //引发系统弹窗
        if (PermissionUtil.checkPermission(this,PERMISSION,REQUEST_CODE_ALL)){
            LoadImageList();
            ShowImageGrid();
        }
    }

    private void ShowImageGrid(){
        gl.removeAllViews();

        for (ImageInfo imageInfo:mImageInfoList){
            ImageView iv = new ImageView(this);
            Bitmap bitmap = BitmapFactory.decodeFile(imageInfo.getPath());
            iv.setImageBitmap(bitmap);
            iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
            int px = diptopx.dip(this,110);
            ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(px,px);
            iv.setLayoutParams(layoutParams);
            int padding = diptopx.dip(this,5);
            iv.setPadding(padding,padding,padding,padding);
            iv.setOnClickListener(v->{

            });

            gl.addView(iv);
        }
    }

    @SuppressLint("Range")
    private void LoadImageList() {
        String[] column = new String[]{
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.TITLE,
                MediaStore.Images.Media.SIZE,
                MediaStore.Images.Media.DATA,
        };

        Cursor cursor = getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                column,
                "_size < 307200", null, "_size DESC"
        );
        int count = 0;
        while (cursor.moveToNext() && count < 6){
            long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));
            String title = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.TITLE));
            long size = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media.SIZE));
            String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));

            ImageInfo imageInfo = new ImageInfo(id, title, size, path);
            count++;
            mImageInfoList.add(imageInfo);
        }
    }
}

17FileProvider

权限

<provider
          android:authorities="@string/file_provider"
          android:name="androidx.core.content.FileProvider"
          android:exported="false"
          android:grantUriPermissions="true">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/>
</provider>

xml/file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_storage_pictures"
        path="DCIM/Camera" />
    <external-path
        name="external_storage_pictures"
        path="Pictures" />
</paths>

检测文件是否是指定fileProvider路径下的

public static boolean CheckFileUri(Context ctx,String path){
    File file = new File(path);
    if (!file.exists() || !file.isFile() || file.length() <= 0){
        return false;
    }
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
            //通过FileProvider获得文件路径
            FileProvider.getUriForFile(ctx,ctx.getString(R.string.file_provider),file);
        }
    }catch (Exception e){
        e.printStackTrace();
        return false;
    }
    return true;
}

18.安装应用

package com.richuff.client;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
import android.view.View;

import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.richuff.client.utils.PermissionUtil;

import java.io.File;

public class MainActivity7 extends AppCompatActivity implements View.OnClickListener {

    //兼容安卓11之前的版本
    private static final String[] PERMISSION = new String[]{
            Manifest.permission.READ_MEDIA_IMAGES
    };

    private static final int REQUEST_CODE_ALL = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        findViewById(R.id.down).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.down){
            //版本兼容
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                CheckAndinstallApk();
            }else{
                if (PermissionUtil.checkPermission(this,PERMISSION,REQUEST_CODE_ALL)){
                    installApk();
                }
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_ALL && PermissionUtil.checkGrant(grantResults)){
            installApk();
        }
    }

    private void CheckAndinstallApk() {
        //检查是否有MANAGE_EXTERNAL_STORAGE权限,没有则跳转到设置页面
        if (!Environment.isExternalStorageEmulated()){
            Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setData(Uri.fromParts("package",getPackageName(),null));
        }else {
            installApk();
        }
    }

    public void installApk(){
        String apkPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString() + "/.apk";
        Log.d("mytag", "apkPath"+apkPath);
        PackageManager pm = getPackageManager();
        PackageInfo pi;
        try {
            pi = pm.getPackageInfo(apkPath, PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        if (pi == null){
            Toastshow.show(this,"文件已损坏");
        }
        Uri uri = Uri.parse(apkPath);

        uri = FileProvider.getUriForFile(this,getString(R.string.file_provider),new File(apkPath));
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        //设置uri的类型为apk文件
        intent.setDataAndType(uri,"application/vnd.android.package-archive");
        //启动
        startActivity(intent);
    }
}

需要的权限

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

18.高级控件

1.Spinner

<Spinner
         android:id="@+id/drop_down"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:spinnerMode="dropdown"/>
private Spinner sp;

private final static String[] startArray = {"水星","火星","金星","木星","木星"};

@SuppressLint("ResourceType")
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    EdgeToEdge.enable(this);
    setContentView(R.layout.activity_test);

    sp = findViewById(R.id.drop_down);
    sp.setAdapter(new ArrayAdapter<String>(this,R.id.drop_down,startArray));
    //默认选择第一项
    sp.setSelection(0);
    sp.setOnItemClickListener(this);
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    Toastshow.show(this,"选择"+startArray[position]);
}