安卓笔记(二)

一、 Activity

1. Activity 的启动和结束

从当前页面跳到新页面,跳转代码如下:

  • startActivity(new Intent(源页面.this, 目标页面.class));

从当前页面回到上一个页面,相当于关闭当前页面,返回代码如下:

  • finish(); // 结束当前的活动页面

第一个页面XML:

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

    <Button
        android:id="@+id/btn_act_next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳到下个页面" />

</LinearLayout>

第一个页面Java:

import androidx.appcompat.app.AppCompatActivity;

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

public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "ning";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "ActStartActivity onCreate");
        setContentView(R.layout.activity_act_start);
        findViewById(R.id.btn_act_next).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
		startActivity(new Intent(this,ActFinishActivity.class));
    }

}

第二个页面XML:

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

    <ImageView
        android:id="@+id/iv_back"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:padding="5dp"
        android:src="@drawable/ic_back" />

    <Button
        android:id="@+id/btn_finish"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="完成" />

</LinearLayout>

第二个页面Java:

import androidx.appcompat.app.AppCompatActivity;

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

public class ActFinishActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_finish);
        findViewById(R.id.iv_back).setOnClickListener(this);
        findViewById(R.id.btn_finish).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {
            // 结束当前的活动页面
            finish();
        }
    }
}

实现效果:点击第一个页面的按钮时,跳转到第二个页面,按返回键,或者点击第二个页面的左上角的箭头图标,或者点击第二个页面的上面的完成按钮,均可关闭第二个页面的、返回第一个页面。

2. Activity 的生命周期

在这里插入图片描述

onCreate:创建活动。把页面布局加载进内存,进入了初始状态。

onStart:开始活动。把活动页面显示在屏幕上,进入了就绪状态。

onResume:恢复活动。活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等等。

onPause:暂停活动。页面进入暂停状态,无法与用户正常交互。

onStop:停止活动。页面将不在屏幕上显示。

onDestroy:销毁活动。回收活动占用的系统资源,把页面从内存中清除。

onRestart:重启活动。重新加载内存中的页面数据。

onNewIntent:重用已有的活动实例。

打开新页面的方法调用顺序为:

  • onCreate→onStart→onResume

关闭旧页面的方法调用顺序为:

  • onPause→onStop→onDestroy

在这里插入图片描述

3. Activity 的启动模式

某App先后打开两个活动,此时活动栈的变动情况如下图所示。

在这里插入图片描述

依次结束已打开的两个活动,此时活动栈的变动情况如下图所示。

在这里插入图片描述

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

    launchMode属性的取值说明见下表:

在这里插入图片描述

  1. 在代码里面设置启动标志

    启动标志的取值说明如下:

    • 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:跳转到新页面时,栈中的原有实例都被清空

4. 显式Intent和隐式Intent

Intent是各个组件之间信息沟通的桥梁,它用于Android各组件之间的通信,主要完成下列工作:

  • 标明本次通信请求从哪里来、到哪里去、要怎么走。
  • 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
  • 发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。

Intent的组成部分:

在这里插入图片描述

显式Intent,直接指定来源活动与目标活动,属于精确匹配。它有三种构建方式:

  1. 在Intent的构造函数中指定。
  2. 调用意图对象的setClass方法指定。
  3. 调用意图对象的setComponent方法指定。
// 1.在Intent的构造函数中指定
Intent intent = new Intent(this, ActFinishActivity.class);

// 2.调用意图对象的setClass方法指定
Intent intent = new Intent();
intent.setClass(this,ActFinishActivity.class);

// 3.调用意图对象的setComponent方法指定
ComponentName component = new ComponentName(this, ActFinishActivity.class);
intent.setComponent(component);
startActivity(intent);

隐式Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配。动作名称既可以通过setAction方法指定,也可以通过构造函数Intent(String action)直接生成意图对象。常见的系统动作如下表:

在这里插入图片描述

// 1. 设置意图动作为准备拨号             
intent.setAction(Intent.ACTION_DIAL);
startActivity(intent);

// 2. 设置意图动作跳转到自己的activity
intent.setAction("android.intent.action.my");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);

跳转到自己的activity时添加:

<android:exported="true"/>

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

5. 向下一个Activity发送数据

Intent使用Bundle对象存放待传递的数据信息。

Bundle对象操作各类型数据的读写方法说明见下表:

在这里插入图片描述

  • 在代码中发送消息包裹,调用意图对象的putExtras方法,即可存入消息包裹。
  • 在代码中接收消息包裹,调用意图对象的getExtras方法,即可取出消息包裹。

存入消息Activity的XML:

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

    <TextView
        android:id="@+id/tv_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="今天的天气真不错" />

    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="发送以上文字" />

</LinearLayout>

存入消息Activity的Java:

import androidx.appcompat.app.AppCompatActivity;

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

import com.dongnaoedu.chapter04.util.DateUtil;

public class ActSendActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_send;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_send);
        tv_send = findViewById(R.id.tv_send);
        findViewById(R.id.btn_send).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, ActReceiveActivity.class);
        // 创建一个新包裹
        Bundle bundle = new Bundle();
        bundle.putString("request_time", DateUtil.getNowTime());
        bundle.putString("request_content", tv_send.getText().toString());
        intent.putExtras(bundle);
        startActivity(intent);
    }
}

取出消息Activity的XML:

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

    <TextView
        android:id="@+id/tv_receive"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

取出消息Activity的Java:

import androidx.appcompat.app.AppCompatActivity;

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

public class ActReceiveActivity extends AppCompatActivity {

    private TextView tv_receive;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_receive);
        tv_receive = findViewById(R.id.tv_receive);
        // 从上一个页面传来的意图中获取快递包裹
        Bundle bundle = getIntent().getExtras();
        String request_time = bundle.getString("request_time");
        String request_content = bundle.getString("request_content");
        String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
        tv_receive.setText(desc);
    }
}

第一个Activity:

在这里插入图片描述

点击发送以上文字后:

在这里插入图片描述

6. 向上一个Activity返回数据

处理下一个页面的应答数据,详细步骤说明如下:

  1. 上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作;
  2. 下一个页面接收并解析请求数据,进行相应处理 ;
  3. 下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据包裹;
  4. 上一个页面重写方法onActivityResult,解析获得下一个页面的返回数据。

不过startActivityForResult已经废弃,现在使用registerForActivityResult

第一个Activity的XML:

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

    <TextView
        android:id="@+id/tv_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="传送请求数据" />

    <TextView
        android:id="@+id/tv_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

第一个Activity的Java:

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 android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.dongnaoedu.chapter04.util.DateUtil;

public class ActRequestActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String mRequest = "你睡了吗?来我家睡吧";
    private ActivityResultLauncher<Intent> register;
    private TextView tv_response;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_request);
        TextView tv_request = findViewById(R.id.tv_request);
        tv_request.setText("待发送的消息为:" + mRequest);

        tv_response = findViewById(R.id.tv_response);

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

        register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result != null) {
                Intent intent = result.getData();
                if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
                    Bundle bundle = intent.getExtras();
                    String response_time = bundle.getString("response_time");
                    String response_content = bundle.getString("response_content");
                    String desc = String.format("收到返回消息:\n应答时间为%s\n应答内容为%s", response_time, response_content);
                    // 把返回消息的详情显示在文本视图上
                    tv_response.setText(desc);
                }
            }
        });
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, ActResponseActivity.class);
        // 创建一个新包裹
        Bundle bundle = new Bundle();
        bundle.putString("request_time", DateUtil.getNowTime());
        bundle.putString("request_content", mRequest);
        intent.putExtras(bundle);
        register.launch(intent);
    }
}

第二个Activity的XML:

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

    <TextView
        android:id="@+id/tv_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="返回应答数据" />

    <TextView
        android:id="@+id/tv_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

第二个Activity的Java:

import androidx.appcompat.app.AppCompatActivity;

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

import com.dongnaoedu.chapter04.util.DateUtil;

public class ActResponseActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String mReponse = "我还没睡,我爸妈不在家。";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_act_response);
        TextView tv_request = findViewById(R.id.tv_request);
        // 从上一个页面传来的意图中获取快递包裹
        Bundle bundle = getIntent().getExtras();
        String request_time = bundle.getString("request_time");
        String request_content = bundle.getString("request_content");
        String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
        // 把请求消息的详情显示在文本视图上
        tv_request.setText(desc);

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

        TextView tv_response = findViewById(R.id.tv_response);
        tv_response.setText("待返回的消息为:" + mReponse);
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        Bundle bundle = new Bundle();
        bundle.putString("response_time", DateUtil.getNowTime());
        bundle.putString("response_content", mReponse);
        intent.putExtras(bundle);
        // 携带意图返回上一个页面。RESULT_OK表示处理成功
        setResult(Activity.RESULT_OK, intent);
        // 结束当前的活动页面
        finish();
    }
}

第一个Activity:

在这里插入图片描述

点击传送请求数据按钮后:

在这里插入图片描述

7. 利用资源文件配置字符串

res\values\strings.xml 可用来配置字符串形式的参数。

在活动页面的Java代码中,调用getString方法即可根据“R.string.参数名称”获得指定参数的字符串值。

在res\values\strings.xml中:

<resources>
    <string name="weather_str">晴天</string>
</resources>

Activity的XML:

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

    <TextView
        android:id="@+id/tv_resource"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Activity的Java:

import androidx.appcompat.app.AppCompatActivity;

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

public class ReadStringActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_read_string);
        TextView tv_resource = findViewById(R.id.tv_resource);
        // 从strings.xml获取名叫weather_str的字符串值
        String value = getString(R.string.weather_str);
        tv_resource.setText(value);
    }
}

效果如下:

在这里插入图片描述

8. 利用元数据传递配置信息

元数据是一种描述其他数据的数据,它相当于描述固定活动的参数信息。

在activity节点内部添加meta-data标签,通过属性name指定元数据的名称,通过属性value指定元数据的值。

在Java代码中,获取元数据信息的步骤分为下列三步:

  1. 调用getPackageManager方法获得当前应用的包管理器;
  2. 调用包管理器的getActivityInfo方法获得当前活动的信息对象;
  3. 活动信息对象的metaData是Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值。

在AndroidManifest.xml中:

<activity
          android:name=".MetaDataActivity"
          android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <meta-data
               android:name="weather"
               android:value="阴天" />
</activity>

XML:

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

    <TextView
        android:id="@+id/tv_meta"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.TextView;

public class MetaDataActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_meta_data);
        TextView tv_meta = findViewById(R.id.tv_meta);
        // 获取应用包管理器
        PackageManager pm = getPackageManager();
        try {
            // 从应用包管理器中获取当前的活动信息
            ActivityInfo info = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            // 获取活动附加的元数据信息
            Bundle bundle = info.metaData;
            String weather = bundle.getString("weather");
            tv_meta.setText(weather);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

效果如下:

在这里插入图片描述

二、中级控件

1. 图形Drawable

Drawable 类型表达了各种各样的图形,包括图片、色块、画板、背景等。

包含图片在内的图形文件放在res目录的各个drawable目录下,其中drawable目录一般保存描述性的XML文件,而图片文件一般放在具体分辨率的drawable目录下。

各视图的background属性、ImageView 和 ImageButton的src属性、TextView和Button四个方向的drawable系列属性都可以引用图形文件。

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

形状图形的定义文件是以shape标签为根节点的XML描述文件,它支持四种类型的形状:

  • rectangle:矩形。默认值
  • oval:椭圆。此时corners节点会失效
  • line:直线。此时必须设置stroke节点,不然会报错
  • ring:圆环

除了根节点shape标签,形状图形还拥有下列规格标签:

  • size(尺寸),它描述了形状图形的宽高尺寸。

  • stroke(描边),它描述了形状图形的描边规格。

  • corners(圆角),它描述了形状图形的圆角大小。

  • solid(填充),它描述了形状图形的填充色彩。

  • padding(间隔),它描述了形状图形与周围边界的间隔。

  • gradient(渐变),它描述了形状图形的颜色渐变。

在res/drawable下定义圆角矩形的XML:

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

    <!-- 指定了形状内部的填充颜色 -->
    <solid android:color="#ffdd66" />
    <!-- 指定了形状轮廓的粗细与颜色 -->
    <stroke
        android:width="1dp"
        android:color="#aaaaaa" />
    <!-- 指定了形状四个圆角的半径 -->
    <corners android:radius="10dp" />

</shape>

在res/drawable下定义椭圆的XML:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">

    <!-- 指定了形状内部的填充颜色 -->
    <solid android:color="#ff66aa" />

    <!-- 指定了形状轮廓的粗细与颜色 -->
    <stroke
        android:width="1dp"
        android:color="#aaaaaa" />

</shape>

页面XML:

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

    <View
        android:id="@+id/v_content"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp" />

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

        <Button
            android:id="@+id/btn_rect"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="圆角矩形背景" />

        <Button
            android:id="@+id/btn_oval"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="椭圆背景" />

    </LinearLayout>

</LinearLayout>

Java代码:

import androidx.appcompat.app.AppCompatActivity;

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

public class DrawableShapeActivity extends AppCompatActivity implements View.OnClickListener {

    private View v_content;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drawable_shape);
        v_content = findViewById(R.id.v_content);
        findViewById(R.id.btn_rect).setOnClickListener(this);
        findViewById(R.id.btn_oval).setOnClickListener(this);
        // v_content的背景设置为圆角矩形
        v_content.setBackgroundResource(R.drawable.shape_rect_gold);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_rect:
                v_content.setBackgroundResource(R.drawable.shape_rect_gold);
                break;
            case R.id.btn_oval:
                v_content.setBackgroundResource(R.drawable.shape_oval_rose);
                break;
        }
    }
}

运行效果:

在这里插入图片描述

点击椭圆背景按钮后:

在这里插入图片描述

2. 状态列表图形selector

使用selector标签为根节点的XML描述文件,实现如下效果:

在这里插入图片描述

res/drawable下的XML(自定义按钮样式):

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

    <item android:drawable="@drawable/button_pressed" android:state_pressed="true" />
    <item android:drawable="@drawable/button_normal" />

</selector>

页面的XML:

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

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/btn_nine_selector"
        android:text="定制样式的按钮" />

</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class DrawableStateActivity extends AppCompatActivity {

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

状态列表图形不仅用于按钮控件,还可用于其他拥有多种状态的控件。

在这里插入图片描述

3. 复选框CheckBox

CompoundButton类是抽象的复合按钮,由它派生而来的子类包括:复选框CheckBox、单选按钮RadioButton以及开关按钮Switch。 下图描述了复合按钮的继承关系:

在这里插入图片描述

CompoundButton 在 XML 文件中主要使用下面两个属性。

  • checked:指定按钮的勾选状态,true表示勾选,false表示未勾选。默认未勾选。
  • button:指定左侧勾选图标的图形资源。如果不指定就使用系统的默认图标。

CompoundButton 在 Java 代码中主要使用下列4种方法:

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

res/drawable下的XML(自定义复选框样式):

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

页面的XML:

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

    <CheckBox
        android:id="@+id/ck_system"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="这是系统的CheckBox" />

    <CheckBox
        android:id="@+id/ck_custom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:button="@drawable/checkbox_selector"
        android:checked="true"
        android:padding="5dp"
        android:text="这个CheckBox换了图标" />

</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;

public class CheckBoxActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_check_box);
        CheckBox ck_system = findViewById(R.id.ck_system);
        CheckBox ck_custom = findViewById(R.id.ck_custom);

        ck_system.setOnCheckedChangeListener(this);
        ck_custom.setOnCheckedChangeListener(this);
    }

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

运行效果:

在这里插入图片描述

取消勾选后:

在这里插入图片描述

4. 开关按钮Switch

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

Switch 控件新添加的XML属性说明如下:

  • textOn:设置右侧开启时的文本。
  • textOff:设置左侧关闭时的文本。
  • track:设置开关轨道的背景。
  • thumb:设置开关标识的图标。

系统默认的switch开关:

在这里插入图片描述

仿iOS的开关按钮:

  1. 借助状态列表图形StateListDrawable,分别定义已选中时候的“开”图形,以及未选中时候的“关”图形。
  2. 然后把CheckBox控件的background属性设置为该状态图形。

res/drawable下的XML(自定义开关按钮样式):

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

    <item android:drawable="@drawable/switch_on" android:state_checked="true" />
    <item android:drawable="@drawable/switch_off" />

</selector>

页面的XML:

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

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

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="start"
            android:layout_weight="1"
            android:padding="5dp"
            android:text="Switch开关:" />

        <CheckBox
            android:id="@+id/ck_status"
            android:layout_width="60dp"
            android:layout_height="30dp"
            android:layout_gravity="end"
            android:background="@drawable/switch_selector"
            android:button="@null" />

    </LinearLayout>

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="start" />
</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;

public class SwitchIOSActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {

    private TextView tv_result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_switch_iosactivity);
        CheckBox ck_status = findViewById(R.id.ck_status);
        tv_result = findViewById(R.id.tv_result);
        ck_status.setOnCheckedChangeListener(this);
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        String desc = String.format("仿iOS开关的状态是%s", isChecked ? "开" : "关");
        tv_result.setText(desc);
    }
}

效果如下:

在这里插入图片描述

点击switch开关后:

在这里插入图片描述

5. 单选按钮RadioButton

单选按钮要在一组按钮中选择其中一项,并且不能多选,这要求有个容器确定这组按钮的范围,这个容器便是单选组RadioGroup。RadioGroup实质上是个布局,同一组RadioButton都要放在同一个RadioGroup节点下。除了RadioButton,也允许放置其他控件。

判断选中了哪个单选按钮,通常不是监听某个单选按钮,而是监听单选组的选中事件。 下面是RadioGroup常用的3个方法:

  • check:选中指定资源编号的单选按钮。
  • getCheckedRadioButtonId:获取选中状态单选按钮的资源编号。
  • setOnCheckedChangeListener:设置单选按钮勾选变化的监听器。

XML:

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="请选择您的性别" />

    <RadioGroup
        android:id="@+id/rg_gender"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

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

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

    </RadioGroup>

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp" />
</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

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

public class RadioHorizontalActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {

    private TextView tv_result;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_radio_horizontal);
        RadioGroup rb_gender = findViewById(R.id.rg_gender);
        tv_result = findViewById(R.id.tv_result);
        rb_gender.setOnCheckedChangeListener(this);
    }

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId){
            case R.id.rb_male:
                tv_result.setText("哇哦,你是个帅气的男孩");
                break;
            case R.id.rb_female:
                tv_result.setText("哇哦,你是个漂亮的女孩");
                break;
        }
    }
}

演示效果:

在这里插入图片描述

6. 编辑框EditText

EditText文本编辑框,用户可在此输入文本等信息。

EditText 的常用属性说明如下:

  • inputType:指定输入的文本类型。若同时使用多种文本类型,则可使用竖线“|”把多种文本类型拼接起来。
  • maxLength:指定文本允许输入的最大长度。
  • hint:指定提示文本的内容。
  • textColorHint:指定提示文本的颜色。

inputType的取值说明:

在这里插入图片描述

XML:

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="下面是登录信息" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"
        android:inputType="text" />

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

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class EditSimpleActivity extends AppCompatActivity {

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

效果如下:

在这里插入图片描述

输入文字后:

在这里插入图片描述

焦点变更监听器

编辑框点击两次后才会触发点击事件,因为第一次点击只触发焦点变更事件,第二次点击才触发点击事件。

若要判断是否切换编辑框输入,应当监听焦点变更事件,而非监听点击事件。

调用编辑框对象的setOnFocusChangeListener方法,即可在光标切换之时(获得光标和失去光标)触发焦点变更事件。

演示:手机号码未输满11位,就点击密码框,此时校验不通过,一边弹出提示文字,一边把焦点拉回手机框。

XML:

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

    <EditText
        android:id="@+id/et_phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/editext_selector"
        android:hint="请输入11位手机号码"
        android:inputType="number"
        android:maxLength="11" />

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/editext_selector"
        android:hint="请输入6位密码"
        android:inputType="numberPassword"
        android:maxLength="6" />

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

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class EditFocusActivity extends AppCompatActivity implements View.OnFocusChangeListener {

    private EditText et_phone;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_edit_focus);
        et_phone = findViewById(R.id.et_phone);
        EditText et_password = findViewById(R.id.et_password);
        et_password.setOnFocusChangeListener(this);
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            String phone = et_phone.getText().toString();
            // 手机号码不足11位
            if (TextUtils.isEmpty(phone) || phone.length() < 11) {
                // 手机号码编辑框请求焦点,也就是把光标移回手机号码编辑框
                et_phone.requestFocus();
                Toast.makeText(this, "请输入11位手机号码", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

效果如下:

在这里插入图片描述

文本变化监听器

判断手机号输入满11位后自动关闭软键盘,或者密码输入满6位后自动关闭软键盘,此时要注册文本变化监听器。

达到指定位数便自动关闭键盘的功能,可以再分解为两个独立的功能点:

  • 如何关闭软键盘;
  • 如何判断已输入的文字达到指定位数

调用编辑框对象的 addTextChangedListener 方法即可注册文本监听器。 文本监听器的接口名称为 TextWatcher,该接口提供了3个监控方法,具体说明如下。

  • beforeTextChanged:在文本改变之前触发。
  • onTextChanged:在文本改变过程中触发。
  • afterTextChanged:在文本改变之后触发。

XML:

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

    <EditText
        android:id="@+id/et_phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="@drawable/editext_selector"
        android:hint="输入11位时自动隐藏输入法"
        android:inputType="text"
        android:maxLength="11" />

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="@drawable/editext_selector"
        android:hint="输入6位时自动隐藏输入法"
        android:inputType="textPassword"
        android:maxLength="6" />
</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;

import com.dongnaoedu.chapter05.util.ViewUtil;

public class EditHideActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_edit_hide);
        EditText et_phone = findViewById(R.id.et_phone);
        EditText et_password = findViewById(R.id.et_password);

        et_phone.addTextChangedListener(new HideTextWatcher(et_phone, 11));
        et_password.addTextChangedListener(new HideTextWatcher(et_password, 6));
    }

    // 定义一个编辑框监听器,在输入文本达到指定长度时自动隐藏输入法
    private class HideTextWatcher implements TextWatcher {
        // 声明一个编辑框对象
        private EditText mView;
        // 声明一个最大长度变量
        private int mMaxLength;

        public HideTextWatcher(EditText v, int maxLength) {
            this.mView = v;
            this.mMaxLength = 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 str = s.toString();
            // 输入文本达到11位(如手机号码),或者达到6位(如登录密码)时关闭输入法
            if (str.length() == mMaxLength) {
                // 隐藏输入法软键盘
                ViewUtil.hideOneInputMethod(EditHideActivity.this, mView);
            }
        }
    }
}

监听文本位数自动关闭软键盘的演示效果:

在这里插入图片描述

7. 提醒对话框AlertDialog

AlertDialog 可以完成常见的交互操作,例如提示、确认、选择等功能。AlertDialog借助建造器 AlertDialog.Builder 才能完成参数设置。

调用建造器的 create 方法生成对话框实例,再调用对话框实例的show方法,在页面上弹出提醒对话框。

XML:

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

    <Button
        android:id="@+id/btn_alert"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="弹出提醒对话框" />

    <TextView
        android:id="@+id/tv_alert"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp" />

</LinearLayout>

Java:

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

public class AlertDialogActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_alert;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_alert_dialog);
        findViewById(R.id.btn_alert).setOnClickListener(this);
        tv_alert = findViewById(R.id.tv_alert);
    }

    @Override
    public void onClick(View v) {
        // 创建提醒对话框的建造器
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        // 设置对话框的标题文本
        builder.setTitle("尊敬的用户");
        // 设置对话框的内容文本
        builder.setMessage("你真的要卸载我吗?");
        // 设置对话框的肯定按钮文本及其点击监听器
        builder.setPositiveButton("残忍卸载", (dialog, which) -> {
            tv_alert.setText("虽然依依不舍,但是只能离开了");
        });
        // 设置对话框的否定按钮文本及其点击监听器
        builder.setNegativeButton("我再想想", (dialog, which) -> {
            tv_alert.setText("让我再陪你三百六十五个日夜");
        });

        // 根据建造器构建提醒对话框对象
        AlertDialog dialog = builder.create();
        // 显示提醒对话框
        dialog.show();
    }
}

效果如下:

在这里插入图片描述

8. 日期对话框DatePickerDialog

日期选择器 DatePicker 可以让用户选择具体的年月日。 但 DatePicker 并非弹窗模式,而是在当前页面占据一块区域,并且不会自动关闭。

DatePickerDialog 相当于在 AlertDialog 上装载了 DatePicker,日期选择事件则由监听器OnDateSetListener负责响应,在该监听器的onDateSet方法中,开发者获取用户选择的具体日期,再做后续处理。

XML:

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

    <Button
        android:id="@+id/btn_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="请选择日期" />

    <DatePicker
        android:id="@+id/dp_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:calendarViewShown="false"
        android:datePickerMode="spinner" />

    <Button
        android:id="@+id/btn_ok"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="确 定" />

    <TextView
        android:id="@+id/tv_date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.app.DatePickerDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.DatePicker;
import android.widget.TextView;

import java.util.Calendar;

public class DatePickerActivity extends AppCompatActivity implements View.OnClickListener, DatePickerDialog.OnDateSetListener {

    private DatePicker dp_date;
    private TextView tv_date;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_date_picker);
        findViewById(R.id.btn_ok).setOnClickListener(this);
        findViewById(R.id.btn_date).setOnClickListener(this);
        tv_date = findViewById(R.id.tv_date);
        dp_date = findViewById(R.id.dp_date);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_ok:
                String desc = String.format("您选择的日期是%d年%d月%d日", dp_date.getYear(), dp_date.getMonth() + 1, dp_date.getDayOfMonth());
                tv_date.setText(desc);
                break;
            case R.id.btn_date:
                // 获取日历的一个实例,里面包含了当前的年月日
                /*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();
                break;
        }
    }

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

运行效果:

在这里插入图片描述

点击请选择日期按钮后:

在这里插入图片描述

9. 时间对话框TimePickerDialog

时间选择器 TimePicker 可以让用户选择具体的小时和分钟。TimePickerDialog 的用法类似 DatePickerDialog。

XML:

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

    <Button
        android:id="@+id/btn_time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="请选择时间" />

    <TimePicker
        android:id="@+id/tp_time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:timePickerMode="spinner" />

    <Button
        android:id="@+id/btn_ok"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="确 定" />

    <TextView
        android:id="@+id/tv_time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

Java:

import androidx.appcompat.app.AppCompatActivity;

import android.app.TimePickerDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.TimePicker;

import java.util.Calendar;

public class TimePickerActivity extends AppCompatActivity implements View.OnClickListener, TimePickerDialog.OnTimeSetListener {

    private TimePicker tp_time;
    private TextView tv_time;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_time_picker);
        findViewById(R.id.btn_ok).setOnClickListener(this);
        findViewById(R.id.btn_time).setOnClickListener(this);
        tp_time = findViewById(R.id.tp_time);
        tp_time.setIs24HourView(true);//设置24小时制
        tv_time = findViewById(R.id.tv_time);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_ok:
                String desc = String.format("您选择的时间是%d时%d分", tp_time.getHour(), tp_time.getMinute());
                tv_time.setText(desc);
                break;
            case R.id.btn_time:
                // 获取日历的一个实例,里面包含了当前的时分秒
                Calendar calendar = Calendar.getInstance();

                // 构建一个时间对话框,该对话框已经集成了时间选择器。
                TimePickerDialog dialog = new TimePickerDialog(this,this,
                        calendar.get(Calendar.HOUR_OF_DAY),
                        calendar.get(Calendar.MINUTE),
                        true); // true表示24小时制,false表示12小时制
                dialog.show();
                break;
        }
    }

    @Override
    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
        String desc = String.format("您选择的时间是%d时%d分", hourOfDay, minute);
        tv_time.setText(desc);
    }
}

效果如下:

在这里插入图片描述

点击请选择时间按钮后:

在这里插入图片描述

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值