Android开发学习笔记(二) —— 了解UI控件

一、前言

Android的UI开发一般采用XML代码编写和可视化编辑器(更方便)。这一部分就好比是html+css+js三剑客。

二、控件

2.1 TextView

文本框

基本属性

属性 说明
layout_width 设置控件的宽度,常用的有”wrap_content”(根据控件内容适配)、”match_parent”(与父容器同宽),以及确切数字,如200dp
layout_height 设置控件的高度,同上。
id 格式为”@+id/xxx”用于在代码中获取该对象,比如java代码通过findViewById()方法获取
text 设置文字内容
textColor 设置文字颜色,格式可以为”#xxxxxxxx”,前两位代表颜色的透明度(00透明,FF不透明),后面代表RGB
textStyle 设置文字风格,如normal(无效果),bold(加粗),italic(斜体)
textSize 设置文字大小,单位sp
background 设置控件背景颜色,”#xxxxxxxx”的格式同textColor一样,也可以是图片背景
gravity 设置控件中内容的对齐方向,如center、left、right、bottom、top等
autoLink 当文字内容出现URL,E-Mail,电话号码等,通过设置autoLink属性,可以使其成为链接,常用类别有 web、email、phone、map等

其中 text、textColor、background 属性的内容在开发中并不直接在控件中写明,而是写在res\values\目录下的 colors.xml、strings.xml中,然后被控件调用。例如:

1
2
3
4
5
//在strings.xml中声明:
<string name="aaa">我被调用了</string>
//在控件中引用:
<TextView
android:text="@String/aaa"/>

跑马灯案例

涉及到的属性

属性 说明
singleLine 内容是否单行显示
focusable 控件是否可以获取焦点
focusableInTouchMode 用于控件在触摸模式下是否可以获取焦点
ellipsize 省略文本的模式
marqueeRepeatLimit 字母动画重复的次数
clickable 控件是否可以被点击

上述属性设置如下:

1
2
3
4
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:focusable="true"

实现跑马灯有以下方式:

  1. 额外设置属性值

    1
    2
    android:focusableInTouchMode="true"
    android:clickable="true"

    点击TextView后,获得焦点,开始跑马灯效果。

  2. 添加额外属型值

    1
    android:focusableInTouchMode="true"

    并且在同一个LinearLayout下添加

    1
    <requestFocus/>

    它用于请求将焦点设置到指定的元素上。

  3. 创建类继承TextView类,重写isFocused方法,使其永远返回true

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

    public MyTextView(Context context) {
    super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean isFocused() {
    return true;
    }
    }

2.2 Button

按钮。Button继承于TextView,所以TextView上很多属性也可以应用到Button 上。

基本属性

属性 说明
drawable 引用的Drawable位图
state_focused 是否获得焦点
state_window_focused 是否获得窗口焦点
state_enabled 控件是否可用
state_checkable 控件可否被勾选
state_checked 控件是否被勾选
state_selected 控件是否被选择,针对有滚轮的情况
state_pressed 控件是否被按下
state_active 控件是否处于活动状态
state_single 控件包含多个子控件时,确定是否只显示一个子控件
state_first、state_middle、state_last 控件包含多个子控件时,确定哪个子控件是否处于显示状态

以上这些属性并不是在<button><android.widget.Button>下声明的,而是在\res\drawable\\res\color\目录下创建xml文件进行声明(color这个目录自己创建,名字只能是color,因为<selector>只能在animator\drawable\color\下的xml文件中使用)。

按钮点击转化案例

1
2
3
4
5
6
7
8
9
<android.widget.Button
android:layout_width="200dp"
android:layout_height="100dp"
android:text="我是按钮"
android:textColor="#FF00FF00"
android:textSize="32dp"
android:background="@drawable/bt_image"
android:backgroundTint="@color/btn_color"
/>

如果使用<Button/>标签,则backgroundbackgroundTint属性设置颜色无效!推荐使用**<android.widget.Button/>**来解决这些问题。其中background属性设置的@drawable/bt_imagebackgroundTint属性设置的@color/btn_color均是自己创建的。

drawable/bt_image.xml如下:

1
2
3
4
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/baseline_airplanemode_active_24" android:state_pressed="true"/>
<item android:drawable="@drawable/baseline_airplanemode_inactive_24"/>
</selector>

第一<item/>设置了state_pressed="true",表示Button被点击的状态对应的图案,那么显然第二个<item/>则表示的是没有被点击状态下的图案。至于这些drawable的属性值,则是一些矢量图。

color/btn_color.xml如下

1
2
3
4
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/black" android:state_pressed="true"/>
<item android:color="#FFFF0000"/>
</selector>

drawable/bt_image.xml的解释类似,不过颜色设置的是图标的。

Button的事件处理

点击事件、长按事件、触摸事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Button btn1 = findViewById(R.id.btn1);

//点击事件
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "onClick: ");;
}
});
//长按事件
btn1.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
Log.d(TAG, "onLongClick: ");
return false;
}
});
//触摸事件
btn1.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.d(TAG, "onTouch: " + motionEvent.getAction());
return false;
}
});

长按按钮并松开(不移动),测试结果如下

1
2
3
4
onTouch: 0  
onLongClick:
onTouch: 1
onClick:

Touch事件触发了两次。分别是按下动作ACTION_DOWN (0)、松开动作ACTION_UP (1)。如果按住不松开并且移动,则会触发移动动作ACTION_MOVE (2)

从上面的结果可以看出,这三个事件触发的顺序依次Touch、LongClick、Click。

在这些回调函数中,返回值为false表示不拦截点击事件消息(click动作在最后触发,没有返回值)。倘若将返回值修改为true,表示拦截点击事件消息,那么其他监听器就不会收到这个消息,因此也就不会执行相应回调函数。(从这三个事件的触发顺序是可以猜测出修改返回值后的触发情况)

onclick事件的处理函数可以在button的onClick属性处绑定。

1
2
3
4
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="dealClick"/>
1
2
3
public void dealClick(View view) {
Toast.makeText(this, "可以的", Toast.LENGTH_SHORT).show();
}

案例

计数器,如果长按则提示信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//点击事件
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "onClick: ");
Integer cnt = Integer.parseInt(btn1.getText().toString());
cnt++;
btn1.setText(cnt.toString());
}
});
//长按事件
btn1.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
Log.d(TAG, "onLongClick: ");
Toast.makeText(MainActivity.this, "请不要长按按钮", Toast.LENGTH_SHORT).show();
return false;
}
});

2.3 EditText

输入框。EditText继承与TextView。

基本属性

属性 说明
hint 提示信息
textColorHint 提示文字的颜色
inputType 输入类型,如日期、电话、邮件等
drawableLeft, drawableRight等 在输入框的指定位置添加图片
drawablePadding 设置图片和输入内容的间距
paddingLeft, paddingRight等 设置内容与边框的间距
selectAllOnFocus 获得焦点后全选组件内所有文本内容
minLines, maxLines 设置最小、最大的行数,当输入内容超过最大行数,文字会自动向上滚动

登录案例

xml的声明如下,声明了账号框、密码框、登录按钮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<EditText
android:id="@+id/zhanghao"
android:layout_width="200dp"
android:layout_height="100dp"
android:inputType="text"
android:hint="请输入用户名"
android:textColorHint="#89827f"
android:drawableLeft="@drawable/baseline_account_box_24"
android:drawablePadding="20dp"
android:padding="20dp"
/>
<EditText
android:id="@+id/mima"
android:layout_width="200dp"
android:layout_height="100dp"
android:inputType="textPassword"
android:hint="请输入密码"
android:textColorHint="#89827f"
android:drawableLeft="@drawable/baseline_password_24"
android:drawablePadding="20dp"
android:padding="20dp"
/>
<android.widget.Button
android:layout_width="100dp"
android:layout_height="50dp"
android:id="@+id/btn"
android:text="登录"/>
</LinearLayout>

按钮设置click监听,然后获取账号密码,对比从而进行不同操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
EditText account = findViewById(R.id.zhanghao);
EditText password = findViewById(R.id.mima);

btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String ac = account.getText().toString();
String pwd = password.getText().toString();
if (ac.equals("admin") && pwd.equals("123456")){
Toast.makeText(MainActivity.this, "管理员,欢迎你", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(MainActivity.this, "账号或密码错误", Toast.LENGTH_SHORT).show();
account.setText("");
password.setText("");
}
}
});
}

2.4 ImageView

图片视图。

基本属性

属性 说明
src 设置图片资源
scaleType 设置图片缩放类型,如fitStart, fitCenter, fitEnd(等比缩放,位置放置不同)等
maxHeight 最大高度
maxWidth 最大宽度
adjustViewBounds 调整View的界限

maxHeight、maxWidth需要和adjustViewBounds一起使用,等比缩放直到一边达到最大值。

2.5 RadioButton

单选框。需要创建<RadioGroup>标签将多个<RadioButton>聚集为一组,确保一次只选择一个单选按钮。

要为每个RadioButton添加一个id,不然单选功能会生效!

基本属性

属性 说明
checked 默认是否勾选
drawableXXX(Left, Right) 文字与选择框的相对位置。需要与button=”@null”一起使用。
paddingXxx 文字与选择框的距离
button 设置为@null表示不使用默认的选择框

案例

性别选择,通过提交按钮显示你选择的性别或者在改变时提示选择的性别。

xml配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请选择你的性别:"
android:textSize="24dp"/>

<RadioGroup
android:id="@+id/radiogroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<RadioButton
android:id="@+id/rbtnman"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="man"
android:textSize="24dp"
android:checked="true"/>
<RadioButton
android:id="@+id/rbtnwoman"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="woman"
android:textSize="24dp"/>
</RadioGroup>
<Button
android:id="@+id/btnpost"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="提交"
/>

MainActivity如下

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

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

//方法一:为RadioGroup设置一个事件监听器setOnCheckChangeListener
//改动的时候会弹出提示
// RadioGroup rg = findViewById(R.id.radiogroup);
// rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
// @Override
// public void onCheckedChanged(RadioGroup radioGroup, int i) {
// RadioButton rbtn = findViewById(i);
// Toast.makeText(MainActivity.this, "你选者了" + rbtn.getText().toString(), Toast.LENGTH_SHORT).show();
// }
// });
//方法二:通过单击其他按钮获取选中单选按钮的值
RadioGroup rg = findViewById(R.id.radiogroup);
Button btnpost = findViewById(R.id.btnpost);
btnpost.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
for (int i = 0; i<rg.getChildCount(); i++){
RadioButton childbtn = (RadioButton) rg.getChildAt(i);
if (childbtn.isChecked()){
Toast.makeText(MainActivity.this, "你选者了" + childbtn.getText().toString(), Toast.LENGTH_SHORT).show();
}

}
}
});

}

}

方法一是通过给RadioGroup设置CheckedChange监听,直接获取被选择的RadioButton的id,通过id找到该控件。

方法二是通过给按钮设置Click监听,遍历RadioGroup下的RadioButton,查看RadioButton是否被选择。

2.6 CheckBox

复选框。对于每一个复选框,使用<CheckBox>单独创建即可。

基本属性

属性同RadioButton。

案例

同RadioButton。

一种方法是给所有CheckBox设置CheckedChanged监听,使用isChecked()方法判断是否被选中。

1
2
3
4
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if(compoundButton.isChecked())
Toast.makeText(this,compoundButton.getText().toString(),Toast.LENGTH_SHORT).show();
}

另一种方法是给提交按钮设置click监听,然后遍历所有CheckBox,使用isChecked()方法判断是否被选中。由于CheckBox是独立存在的,并不像RadioButton一样有父标签RadioGroup,所以不能直接使用for循环判断。

2.7 ProgressBar

进度条。

基本属性

属性 说明
max 进度条最大值
progress 进度条已经完成进度值
indeterminate 如果设置成true,则进度条不精确显示进度
progressDrawable 设置轨道对应的Drawable对象
secondaryProgress 二级进度条,类似于视频播放的一条是当前播放进度,一条是缓冲进度,前者通过progress属性进行设置
style 进度条样式

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--系统提供的圆形进度条-->
<ProgressBar
android:layout_width="100dp"
android:layout_height="100dp"
/>
<!--系统提供的水平进度条-->
<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="300dp"
android:layout_height="wrap_content"
/>

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

其中条形进度条设置indeterminate为true后,和圆形进度条类似,在不断地流动。

由于系统提供的比较简陋,在开发中一般都用动画(Animation)来代替或者自定义进度条(继承ProgressBar或者view)。

2.8 Notification

状态栏通知。

要想实现通知,需要获得两个对象:

  • NotificationMannager

    NotificationManager类是一个通知管理器类,这个对象是由系统维护的服务,是以单例模式的方式获得,所以一般并不直接实例化这个对象。在Activity中,可以使用Activity.getSystemService(String)方法获取NotificationManager对象,这里传递Context.NOTIFICATION_SERVICE即可。

    1
    NotificationManager notification_manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
  • Notification

    使用NotificationCompat类的Builder构造器来创建Notification对象,可以保证程序在所有的版本上都能正常工作。Android8.0新增了通知渠道这个概念,如果没有设置,则通知无法在Android8.0的机
    器上显示。

    1
    2
    3
    4
    5
    6
    //创建builder对象
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "nid001");
    //builder的成员设置,如setContentTitle("xxx")等
    ......
    //创建notification对象
    Notification notification = builder.build();

    由于通知渠道的引进,我们要创建通知渠道并与通知管理服务绑定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //Android 8.0引入了通知渠道,所以根据版本选择是否创建通知渠道
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
    /* @param id: 表示通知渠道id,即下方创建notification时的channelid,两者要一至
    * @param name: 任意
    * @param importance: 通知重要性
    */
    //创建通知渠道对象
    NotificationChannel channel = new NotificationChannel("nid001","测试通知", NotificationManager.IMPORTANCE_HIGH);
    //通知渠道对象与通知管理服务对象绑定
    notification_manager.createNotificationChannel(channel);
    }

    需要注意的是Builder创建时的参数id和Channel创建时的参数id要一致

    NotificationChannel的Importance参数如下:

    类型 说明
    IMPORTANCE_NONE 关闭通知
    IMPORTANCE_MIN 开启通知,不会弹出,但没有提示音,状态栏中无显示
    IMPGRTANCE_LOW 开启通知,不会弹出,不发出提示音,状态栏中显示
    IMPORTANCE_DEFAULT 开启通知,不会弹出,发出提示音,状态栏中显示
    IMPORTANCE_HIGH 开启通知,会弹出,发出提示音,状态栏中显示

NotificationCompat.Builder的基本方法

方法 说明
setContentTitle(String string) 设置标题
setContentText(String string) 设置文本内容
setSmallIcon(int icon) 设置小图标,只是用alpha图层,即图标不能带颜色
setLargeIcon(int argb) 设置小图标颜色
setContentIntent(PendingIntent intent) 设置点击通知后的跳转意图
setAutoCancel(boolean bool) 设置点击通知后自动清除通知
setWhen(long when) 设置通知的创建事件

其中前三个方法是必须的,否则程序无法正常工作。

案例

通过按钮创建和清楚通知,并且点击通知会跳转到另一个页面(activity)。

xml声明如下。通过onClick属性绑定点击事件处理方法。

1
2
3
4
5
6
7
8
9
10
<Button
android:layout_width="165dp"
android:layout_height="100dp"
android:onClick="sentNotification"
android:text="发送通知" />
<Button
android:layout_width="165dp"
android:layout_height="100dp"
android:onClick="cacelNotification"
android:text="取消通知" />

MainActivity方法如下。获取通知管理服务(NotificationManager),创建通信渠道(channel)并与通知管理服务绑定。创建PendingIntent设置跳转意图。创建Notification对象,最后实现点击事件处理方法。

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

private static final String TAG = "";
private NotificationManager notification_manager;
private Notification notification;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取通知管理服务
notification_manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//Android 8.0引入了通知渠道,所以根据版本选择是否创建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
/* @param id: 表示通知渠道id,即下方创建notification时的channelid,两者要一至
* @param name: 任意
* @param importance: 通知重要性
*/
//创建通知渠道对象
NotificationChannel channel = new NotificationChannel("nid001","测试通知",
NotificationManager.IMPORTANCE_HIGH);
//通知渠道对象与通知管理服务对象绑定
notification_manager.createNotificationChannel(channel);
}

//创建Intent
Intent intent = new Intent(this, NotificationActivity.class);

//创建PendingIntent
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

//创建notification对象并设置基本属性
notification = new NotificationCompat.Builder(this,"nid001")
.setContentTitle("叶华")
.setContentText("喂喂喂")
.setSmallIcon(R.drawable.baseline_account_box_24)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.wallhaven))
.setColor(Color.parseColor("#FFFF0000"))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build();
}

public void sentNotification(View view){
/* @param id: 任意
* @param notification: 创建好的通知
*/
//将创建好的通知发送出去
notification_manager.notify(111,notification);
}

public void cacelNotification(View view){
//清除通知,id与notify方法的id一致
notification_manager.cancel(111);
}

}

其中NotificationActivity方法如下。只是简单的在Logcat处输出信息:

1
2
3
4
5
6
7
8
9
//需要将该Acitivity注册到AndroidManifest.xml中,否则否则系统将不识别也不执行该Activity
public class NotificationActivity extends Activity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("","点击消息进入到了NotificatoinActivity");
}
}

2.9 Toolbar

工具栏。

基本属性

属性 说明
layout_width、layout_height 宽高,layout_height一般设置为“?attr/actionBarSize”
background 背景色或背景图
navigationIcon 导航图标,比如返回图标
title 标题
subtitle 子标题
logo 图标

案例

1
2
3
4
5
6
7
8
9
10
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#00FFFF"
app:title="大标题"
app:subtitle="小标题"
app:titleMarginStart="100dp"
app:navigationIcon="@drawable/baseline_arrow_back_24"
app:logo="@mipmap/ic_launcher"
/>

一般标题都是居中显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#00FFFF"
app:navigationIcon="@drawable/baseline_arrow_back_24"
app:logo="@mipmap/ic_launcher"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题"
android:layout_gravity="center"
/>
</androidx.appcompat.widget.Toolbar>

</LinearLayout>

在TextView中设置了标题就不要在<androidx.appcompat.widget.Toolbar>里面再设置标题了。由于是androidx里面的Toolbar,与android里面的Toolbar不同,需要注意在java代码中使用该对象时不要导错包(import androidx.appcompat.widget.Toolbar)。

2.10 AlertDialog

对话框。

AlertDialog.Builder的基本方法

方法 说明
setIcon(int iconId) 设置图标
setTitle(Charsequence title) 添加标题
setMessage(CharSequence message) 添加消息
setView(View view) 设置自定义布局
setPositiveButton 确定按钮
setNegativeButton 取消按钮
setNeutralButton 中间按钮
create() 创建对话框
show() 显示对话框

案例

实现以上功能。

触发按钮的xml设置如下:

1
2
3
4
5
6
7
<Button
android:layout_width="wrap_content"
android:layout_height="50dp"
android:id="@+id/btn1"
android:text="点击显示窗口"
android:onClick="ClickWindow"
/>

绑定了ClickWindow方法。

MainActivity方法如下

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

private static final String TAG = "";
private AlertDialog dialog;

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

// View dialogview = getLayoutInflater().inflate(R.layout.dialog_view, null);

//创建AlertDialog.Builder对象
AlertDialog.Builder builder = new AlertDialog.Builder(this);
//设置基本属性
dialog = builder.setIcon(R.drawable.baseline_account_box_24)
.setTitle("我是对话框")
.setMessage("阿巴阿巴......")
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Log.d("","点击确认");
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Log.d("","点击取消");
}
})
.setNeutralButton("中立", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Log.d("","点击中立");
}
})
.setView(R.layout.dialog_view)
.create();

}

public void ClickWindow(View view) {
dialog.show();
}
}

显然,首先要创建的是AlertDialog.Builder对象,然后通过一系列方法设置属性。其中setView方法的参数是自己创建的一个布局的xml文件。最后实现ClickWindow方法来处理点击事件。

2.11 PopupWindow

悬浮框。

PopupWindow弹出后,所有的触屏和物理按键都由PopupWindows 处理。其他任何事件的响应都必须发生在PopupWindow消失之后,(home 等系统层面的事件除外)。

基本方法

方法 说明
PopupWindow(View contentView, int width, int height, boolean focusable) 多个构造方法,这里拿一个来说明。参数contentView指PopupWindow的布局,参数width和height一般设置ViewGroup.LayoutParams.WRAP_CONTENT,参数focusable指是否显示焦点,即点击该布局之外的地方可以退出。
setContentView(View contentView) 设置PopupWindow显示的View
getContentView() 获得PopupWindow显示的View
showAsDropDown(View anchor) 相对某个控件的位置(正左下方),无偏移
showAsDropDown(View anchor, int xoff, int yoff) 相对某个控件的位置,有偏移
setFocusable(boolean focusable) 设置焦点
setBackgroundDrawable(Drawable background) 设置背景
setAnimationStyle(int animationStyle) 设置动画效果
dismiss() 关闭弹窗

案例

xml配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!--主活动的layout的xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="50dp"
android:id="@+id/btn1"
android:text="点击显示PopupWindow"
android:onClick="ClickButton"
/>
</LinearLayout>
<!--popupwindow的layout的xml-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
>
<Button
android:id="@+id/haha"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="哈哈哈哈哈"/>

<Button
android:id="@+id/xixi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="嘻嘻嘻嘻"/>
</LinearLayout>

MainActivity如下

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

private static final String TAG = "";
private PopupWindow popupWindow;
private View popupwindow_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//获取view资源的对象
popupwindow_view = getLayoutInflater().inflate(R.layout.popupwindow_view, null);
//创建popupwindow对象
popupWindow = new PopupWindow(popupwindow_view,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true);
//设置背景
popupWindow.setBackgroundDrawable(getResources().getDrawable(R.drawable.wallhaven));

//设置popupwindow中按钮的点击事件处理方法
Button haha = popupwindow_view.findViewById(R.id.haha);
Button xixi = popupwindow_view.findViewById(R.id.xixi);
haha.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "你点击了哈哈哈", Toast.LENGTH_SHORT).show();
popupWindow.dismiss();
}
});
xixi.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "你点击了嘻嘻嘻", Toast.LENGTH_SHORT).show();
popupWindow.dismiss();
}
});

}
//显示popupwindow
public void ClickButton(View view) {
popupWindow.showAsDropDown(view);
}
}

2.12 ListView

列表。列表中的每一项(item)都有自己的布局。item的布局单独创建一个layout的xml文件,并对该布局创建一个类,同时也要创建对应的Adapter类,用来给item填充数据等。

什么是Adapter?

答:

Adapter(适配器)是一个用于将数据与视图进行绑定的桥梁。简单点说,就是将各种数据以合适的形式显示到view上,提供给用户看。(个人的理解是Adapter将数据从布局中分离开来)

常用的几个Adapter类

  • BaseAdapter:是一个抽象类,提供了一个基本的Adapter实现,可以用于自定义Adapter的开发。
  • ArrayAdapter:是BaseAdapter的子类,用于将数组或List数据绑定到ListView中。
  • SimpleAdapter:是BaseAdapter的子类,用于将键值对数据绑定到ListView中的多个TextView组合的布局中。
  • CursorAdapter:是BaseAdapter的子类,用于将数据库Cursor中的数据绑定到ListView中。
  • RecyclerView.Adapter:用于RecyclerView组件的适配器,支持更强大的列表项定制和动画效果。

基本属性

属性 说明
divider 设置表项之间的分隔条,可以用颜色分割,也可以用drawable资源分割
dividerHeight 设置表项之间分割线的高度
dividerPadding 设置表项之间分割线的左右内边距
listSelector 设置表项被选中时的背景样式
scrollbars 设置滚动条的显示方式,如”vertical”(垂直滚动条)、”horizontal”(水平滚动条)和”none”(不显示滚动条)
headerDividersEnabled, footerDividersEnabled 是否显示列表头部和尾部的分割线,默认为true

案例

实现类似QQ的消息列表。(目的在于学会使用Adapte)

首先activity_main.xml中创建一个ListView就行了。

1
2
3
4
5
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>

另外创建一个xml文件用于列表中item的布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">


<ImageView
android:id="@+id/imageView"
android:layout_width="135dp"
android:layout_height="128dp"
app:layout_constraintBottom_toBottomOf="@+id/textView3"
app:layout_constraintEnd_toStartOf="@+id/textView3"
tools:srcCompat="@tools:sample/avatars" />

<TextView
android:id="@+id/textView2"
android:layout_width="275dp"
android:layout_height="40dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent" />

<TextView
android:id="@+id/textView3"
android:layout_width="275dp"
android:layout_height="94dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>

这里使用ConstraintLayout布局很方便,只要拖拽组件,连线就可以制作好。

因为信息不存放在xml文件中,所以创建一个类来描述item。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//列表中item所需要展示的东西
public class Person {
//网名
private String name;
//消息
private String text;
//头像
private int pIcon;

public Person() {
}

public Person(String name, String text, int pIcon) {
this.name = name;
this.text = text;
this.pIcon = pIcon;
}

public String getName() {
return name;
}

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

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public int getpIcon() {
return pIcon;
}

public void setpIcon(int pIcon) {
this.pIcon = pIcon;
}

}

再写个类继承BaseAdapter用来将数据显示在页面中。

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

private ArrayList<Person> data;
private Context context;

public PersonAdapter() {
}

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

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

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

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

@Override
//getView给列表中的每一项进行设置并返回到页面上
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;

if (view == null){
view = LayoutInflater.from(context).inflate(R.layout.item_view, viewGroup, false);

viewHolder = new ViewHolder();
viewHolder.Icon = view.findViewById(R.id.imageView);
viewHolder.Name = view.findViewById(R.id.textView2);
viewHolder.Message = view.findViewById(R.id.textView3);

view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}

Person person = data.get(i);

viewHolder.Icon.setBackgroundResource(person.getpIcon());
viewHolder.Name.setText(person.getName());
viewHolder.Message.setText(person.getText());

return view;
}

//优化
private final class ViewHolder{
ImageView Icon;
TextView Name;
TextView Message;
}

}

对于getView方法,有多少个item就会调用多少次getView方法,里面的inflate方法和findViewById方法会被调用多次,这是非常耗时的。

但是getView方法中的参数view是系统提供的视图缓存对象。所以通过if语句的判断view是否已经创建,就不会执行多次inflate方法。同时创建ViewHolder类存放item的组件,通过view.setTag(viewHolder)放入view中,相当于也缓存item中的控件引用,以便后续复用(比如屏幕上下滑动,同一个item多次出现、隐藏的情况)。

最后MainActivity.java如下

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

private ArrayList<Person> PersonArrayList;

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

//随便造点数据
PersonArrayList = new ArrayList<Person>();
PersonArrayList.add(new Person("送之", "在吗?", R.drawable.wallhaven));
PersonArrayList.add(new Person("无阿", "Vivo50", R.drawable.wallhaven));
PersonArrayList.add(new Person("吉拉", "6", R.drawable.wallhaven));
PersonArrayList.add(new Person("阿三", "?", R.drawable.wallhaven));
PersonArrayList.add(new Person("骗子", "我是秦始皇", R.drawable.baseline_account_box_24));

//找到ListView
ListView listviewByid = findViewById(R.id.lv);
//需要配合Adapter才能将数据放入
listviewByid.setAdapter(new PersonAdapter(PersonArrayList, this));

}

}

2.13 RecyclerView

RecyclerView比ListView更加灵活,RecyclerView在继承RecyclerView.Adapter时会强制让我们实现ViewHolder,同时也提供了多个布局可供选择。

直接上案例。

案例

同ListView的案例。主要是继承RecyclerView.Adapter处有区别。

PersonAdapter.java如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//RecyclerView.Adapter<VH>,在继承RecyclerView.Adapter的时候就让我们强制性创建ViewHolder来优化
public class PersonAdapter extends RecyclerView.Adapter<PersonAdapter.ViewHolder> {

private ArrayList<Person> data;
private Context context;

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

//用于创建ViewHolder对象
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//就是BaseAdapter类中getView()方法的第二个参数--缓冲对象view
View view = View.inflate(context, R.layout.item_view, null);
//创建viewholder并返回
return new ViewHolder(view);
}

//用于给item的控件设置数据
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Person person = data.get(position);

holder.Icon.setBackgroundResource(person.getpIcon());
holder.Name.setText(person.getName());
holder.Message.setText(person.getText());
}

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

//ViewHolder需要继承RecyclerView.ViewHolder类
public class ViewHolder extends RecyclerView.ViewHolder {
//item的控件
private ImageView Icon;
private TextView Name;
private TextView Message;

public ViewHolder(@NonNull View itemView) {
super(itemView);
//获取控件,调用的时候通过ViewHolder.xxx
Icon = itemView.findViewById(R.id.imageView);
Name = itemView.findViewById(R.id.textView2);
Message = itemView.findViewById(R.id.textView3);

}
}

}

看似那么多方法,但做的事情并没有变:通过inflate方法获取view,创建ViewHolder对象,通过findViewById方法获取item中的组件,通过setxxx方法设置数据。只不过是将这些工作细化为多个方法了。

MainActivity.java如下

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

private ArrayList<Person> PersonArrayList;

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

//随便造点数据
......

//找到ListView
RecyclerView recyclerView = findViewById(R.id.rv);
//创建Adapter
PersonAdapter personAdapter = new PersonAdapter(PersonArrayList, this);
//需要配合Adapter才能将数据放入
recyclerView.setAdapter(personAdapter);
//除此之外,还需要给recyclerView布局。ListView默认垂直布局,而recyclerView将布局抽离出来,使得布局更加灵活
recyclerView.setLayoutManager(new LinearLayoutManager(this));//示例使用线性布局管理器
//recyclerView.setLayoutManager(new GridLayoutManager(this,3));//示例使用网格布局管理器
//recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3,LinearLayout.HORIZONTAL));//示例使用网格布局管理器

}
}

以上基本就实现了RecyclerView。

然后再给item来个点击事件的监听和处理。

这部分主要在PersonAdapter.java中额外添加一下方法

1
2
3
4
5
6
7
8
9
10
11
12
//外部设置的监听对象	
private OnRecyclerItemOnClickListener monRecyclerItemOnClickListener;

//item的点击事件处理方法的接口
public interface OnRecyclerItemOnClickListener{
void onRecyclerItemClick(int postion);
}

//外部调用该方法来设置item监听
public void setRecyclerItemOnClickListener(OnRecyclerItemOnClickListener listener){
monRecyclerItemOnClickListener = listener;
}

同时还需要在继承了RecyclerView.ViewHolder类的ViewHolder类的ViewHolder方法中给item设置监听

1
2
3
4
5
6
7
8
9
//设置监听
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (monRecyclerItemOnClickListener != null){
monRecyclerItemOnClickListener.onRecyclerItemClick(getAdapterPosition());
}
}
});

最后在MainActivity.java中给Adapter创建监听并实现接口。

1
2
3
4
5
6
personAdapter.setRecyclerItemOnClickListener(new PersonAdapter.OnRecyclerItemOnClickListener() {
@Override
public void onRecyclerItemClick(int postion) {
Log.d("leo", "点击了第"+postion+"个item");
}
});

咋一看,怎么设置了两次监听?其实在MainActivity.java的调用的setRecyclerItemOnClickListener方法实际上是为了给外部提供一个接口,让外部有机会设置自己的监听器monRecyclerItemOnClickListener来处理item的点击事件。(并不是继承View.OnClickListener重写onClick方法)

而给item设置的监听,才会在我们点击页面中的列表项时触发。

(可以尝试理解为item在页面中有对应控件,是可以点击到的,而Adapter在页面中并不没有。当然这样理解并不是很正确)

(不妨可以直接将OnRecyclerItemOnClickListener接口写成方法试试,但一般不将处理函数写死)

2.14 ViewPager

ViewPager是一个页面切换组件,常用于创建轮播图、图片浏览、导航页面等需要左右滑动切换内容的场景。使用<androidx.viewpager.widget.ViewPager>或者<androidx.viewpager2.widget.ViewPager2>标签。需要PagerAdapter创建Adapter对象与ViewPager对象绑定。

在开发中经常与Fragment搭配使用,官方给我们提供了两个专门用于Fragment的Adapter:FragmentPageAdapterFragmentStatePagerAdapter

  • FragmentPageAdapter:和PagerAdapter一样,只会缓存当前的Fragment以及左边一个,右边 一个,即总共会缓存3个Fragment。
  • FragmentStatePagerAdapter:当Fragment对用户不可见时,整个Fragment会被销毁, 只会保存Fragment的状态。而在页面需要重新显示的时候,会生成新的页面。

(与viewpager2搭配使用!)

基本方法

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

案例

页面切换。

先随便在layout文件夹下创建三个布局。

然后创建一个继承PagerAdapter的类

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

//存放要展示的viewpage
private List<View> viewList;

public ViewAdapter(List<View> viewList) {
this.viewList = viewList;
}

public int getCount() {
return viewList == null? 0 : viewList.size();
}

//判断是否是当前页面视图
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}

//创建页面视图
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(viewList.get(position), 0);
return viewList.get(position);
}
//摧毁页面视图
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(viewList.get(position));
}

}

MainActivity.java如下

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

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

//创建解析器
LayoutInflater inflater = getLayoutInflater().from(this);
//解析viewpage页面
View vp1 = inflater.inflate(R.layout.viewpage1,null);
View vp2 = inflater.inflate(R.layout.viewpage2,null);
View vp3 = inflater.inflate(R.layout.viewpage3,null);

List<View> viewList = new ArrayList<>();

viewList.add(vp1);
viewList.add(vp2);
viewList.add(vp3);
//通过id找ViewPager
ViewPager viewPager = findViewById(R.id.vp);
//创建ViewAdapter
ViewAdapter viewAdapter = new ViewAdapter(viewList);
//绑定ViewAdapter
viewPager.setAdapter(viewAdapter);

}

}

三、布局

也就是放置控件、布局的容器,可以多层嵌套。

布局中的所有元素均使用 ViewViewGroup 对象的层次结构进行构建。View 通常用于绘制用户可看到并与之交互的内容。ViewGroup 则是不可见的容器。

View 对象通常称为“微件”,可以是多个子类之一,例如 ButtonTextViewViewGroup 对象通常称为“布局”,可以是提供不同布局结构的众多类型之一,例如 LinearLayoutConstraintLayout

3.1 LinearLayout

线性布局

基本属性

属性 说明
orientation 布局中组件的排列方式,如horizontal(水平摆放)、vertical(垂直拜访,即一个控件占一整行)
gravity 控制组件或布局所包含的子元素的对齐方式,可以多个组合,以 | 分隔,如 bottom|left表示位置在左下方
layout_gravity 控制当前组件在父容器的对齐方式
background 为当前组件设置背景(图片、颜色)
divider 分割线。与showDividers搭配使用
showDividers 设置分割线所在的位置,如none(无)、beginning(开始)、end(结束)、middle(每两个组件间)
dividerPadding 设置分隔线与左右两边的间距
layout_weight 等比例划分剩余区域

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/wallhaven"
android:divider="@drawable/baseline_password_24"
android:showDividers="middle"
android:dividerPadding="100dp"
>

<LinearLayout
android:layout_width="300dp"
android:layout_height="100dp"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LinearLayout嵌套LinearLayout"
/>
</LinearLayout>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请选择你喜欢吃的水果:"
android:layout_gravity="left"
android:textSize="24dp"/>

<RadioButton
android:id="@+id/apple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="苹果"
android:textSize="24dp"
android:layout_gravity="right"
/>
<RadioButton
android:id="@+id/banana"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="香蕉"
android:textSize="24dp"
/>
<RadioButton
android:id="@+id/pear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="梨"
android:textSize="24dp"
/>
<RadioButton
android:id="@+id/Cantaloupe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="哈密瓜"
android:textSize="24dp"
/>

<Button
android:id="@+id/btnpost"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="提交"
android:onClick="postByclick"
/>

</LinearLayout>

3.2 RelativeLayout

相对布局。

基本属性

属性 说明
gravity 控制组件或布局所包含的子元素的对齐方式
ignoreGravity 设置为true将不受gravity属性的影响
layout_alignParentXXXX(Left, Right等) 根据父容器定位。左(右)对齐
layout_centerXXXX(Horizontal, Vertical, InParent) 根据父容器定位。水平居中,垂直居中,中间位置
layout_XXX(toLeftOf, toRightOf, above, below, alignTop, alignBottom, alignLeft, alignRight) 根据兄弟组件定位。根据id来设置。其中toLeftOf, toRightOf, above, below是参考组件的左、右、上、下边,而alignTop, alignBottom, alignLeft, alignRight则是对齐参考组件的上、下、左、右边界。
layout_XXX(margin, marginLeft, marginRight, marginTop, marginBottom) 设置组件与父容器的边距(偏移)
padding, paddingXXX(Left, Right, Top, Bottom) 设置组件内部元素间的边距

案例

实现梅花布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<Button
android:id="@+id/btn_center"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerInParent="true" />
<Button
android:id="@+id/btn_left"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/btn_center"
/>
<Button
android:id="@+id/btn_right"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/btn_center"
/>
<Button
android:id="@+id/btn_top"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerHorizontal="true"
android:layout_above="@id/btn_center"
/>

<Button
android:id="@+id/btn_bottom"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_below="@id/btn_center"
android:layout_centerInParent="true" />
</RelativeLayout>

3.3 FrameLayout

帧布局。从父容器左上角开始绘制,按组件或布局的定义顺序依次绘制,造成覆盖效果。

基本属性

属性 说明
foreground 设置前景图像
foregroundGravity 设置前景图像显示的位置

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
>

<android.widget.Button
android:layout_width="400dp"
android:layout_height="400dp"
android:background="#FFFF0000"
/>
<android.widget.Button
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#FF00FF00"
/>
<android.widget.Button
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#FF0000FF"
/>
<android.widget.Button
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#FFFFFF00"
android:foreground="@drawable/baseline_airplanemode_active_24"
android:foregroundGravity="center"
/>

</FrameLayout>

3.4 TableLayout

表格布局。

<TableLayout>表格标签,内嵌<TableRow>表行标签

基本属性

属性 说明
collapseColumns 设置需要被隐藏的列的序号,序号从0开始。可以设置多个,用逗号隔开,比如”0,2”,如果是所有列都生效,则用”*“号即可
stretchColumns 设置允许被拉伸的列的列序号,序号从0开始。设置同上。只要存在一行是满的,设置就对所有行无效。无法控制被拉伸的控件的长度
shrinkColumns 设置允许被收缩的列的列序号,序号从0开始。设置同上。只要存在一行有空隙,设置就对所有行无效。无法控制被收缩的控件的长度
layout_column 设置子控件显示在第几列
layout_span 设置子控件横向跨几列

3.5 GridLayout

网格布局。

与TableLayout有点类似,但比它更好用,功能(属性)更多。

基本属性

属性 说明
orientation 设置水平显示还是垂直显示
columnCount 设置行的显示个数。
rowCount 设置列的显示个数。
layout_row, layout_column 设置子控件显示在第几行第几列。从0开始,可以覆盖。后面没有定义行列的控件会跟随当前控件移动
layout_rowSpan, layout_columnSpan 设置子控件横跨几行几列。需要设置android:layout_gravity = “fill”才能生效
layout_gravity 设置子控件在网格中的显示位置,如left, right等
layout_columnWeight, layout_rowWeight 设置子控件的横向(或纵向)剩余空间的分配权重。与LinearLayout布局的属性layout_weight相同

案例

实现计算器布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:columnCount="4"
android:rowCount="7"
>
<TextView
android:text="9+1"
android:textSize="24sp"
android:background="#fecccb"
android:layout_margin="5dp"
android:layout_gravity="fill"
android:layout_columnSpan="4"
/>
<android.widget.Button
android:text="回退"
android:layout_columnSpan="2"
android:layout_gravity="fill"
/>
<android.widget.Button
android:text="归零"
android:layout_columnSpan="2"
android:layout_gravity="fill"
/>

<android.widget.Button android:text="7"/>

<android.widget.Button android:text="8"/>

<android.widget.Button android:text="9"/>

<android.widget.Button android:text="+"/>

<android.widget.Button android:text="4"/>

<android.widget.Button android:text="5"/>

<android.widget.Button android:text="6"/>

<android.widget.Button android:text="-"/>

<android.widget.Button android:text="1"/>

<android.widget.Button android:text="2"/>

<android.widget.Button android:text="3"/>

<android.widget.Button android:text="*"/>

<android.widget.Button android:text="0"/>

<android.widget.Button android:text="."/>

<android.widget.Button android:text="="/>

<android.widget.Button android:text="/"/>

</GridLayout>

3.6 ConstraintLayout

约束布局。

<androidx.constraintlayout.widget.ConstraintLayout>标签。

可以在Design模式下,拖动控件进行布局,更加方便,进行可视化布局,Infer Constraints功能可以直接帮你设置好约束(比如距离控件或父容器的各种设置),当然也可以在Attributes处进行更详细的设置。

四、动画

4.1 逐帧动画

把多张图片快速播放形成的动画。

使用方法:

<animation-list>作为大标签,内部设置多个<item>小标签(对应多张图片),<item>标签设置图片资源(drawable)和图片显示的时间(duration)。

基本属性

属性 说明
drawable 图片资源
duration 图片显示的时间,单位为ms。每张图片显示的时间可以单独设置

基本方法

方法 说明
start() 播放动画
stop() 停止动画

案例

动画xml如下

1
2
3
4
5
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/baseline_airplanemode_active_24" android:duration="120"/>
<item android:drawable="@drawable/baseline_airplanemode_inactive_24" android:duration="120"/>
<item android:drawable="@drawable/wallhaven" android:duration="120"/>
</animation-list>

然后在activity_main.xml中引用。

但是并不会播放,需要通过调用start()方法来播放动画。

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

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

LinearLayout linearlayout = findViewById(R.id.ll);
AnimationDrawable animation = (AnimationDrawable)linearlayout.getBackground();
animation.start();

}

}

4.2 补间动画

在指定的时间段内,通过不断地改变视图的属性值(如位置、尺寸、旋转角度、透明度等),来实现视图的平滑动画效果。

主要有以下几种类型的补间动画:

  1. 位移动画(Translate Animation):通过改变视图的位置实现移动效果。
  2. 缩放动画(Scale Animation):通过改变视图的尺寸实现缩放效果。
  3. 旋转动画(Rotate Animation):通过改变视图的旋转角度实现旋转效果。
  4. 透明度动画(Alpha Animation):通过改变视图的透明度实现淡入淡出效果。

基本属性

位移动画

属性 说明
fromXDelta, fromYDelta 起始X、Y轴位置的偏移量
toXDelta, toYDelta 结束X、Y轴位置的偏移量
duration 动画时长

缩放动画

属性 说明
fromXScale, fromYScale 起始X、Y轴上的缩放比例
toXScale, toYScale 结束X、Y轴上的缩放比例
pivotX, pivotY 缩放中心点的X、Y轴坐标(被缩放的控件的左上角为0%,中心为50%)
duration 动画时长

旋转动画

属性 说明
fromDegrees 起始角度(0~360)
toDegrees 结束角度
pivotX, pivotY 旋转中心点的X、Y轴坐标
duration 动画时长

透明度动画

属性 说明
fromAlpha 起始透明度(0.0表示完全透明,1.0表示完全不透明)
toAlpha 结束透明度
duration 动画时长

基本方法

方法 说明
start() 开始执行动画
setRepeatMode(int repeatMode) 设置动画的重复模式
setRepeatCount(int repeatCount) 设置动画的重复次数
setFillEnabled(boolean fillEnabled) 设置是否保持动画结束时的状态
setDuration(int duration) 设置动画的持续时间,单位为毫秒
setInterpolator(Interpolator interpolator) 设置动画的插值器

案例

动画xml文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<set xmlns:android="http://schemas.android.com/apk/res/android">

<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:duration="2000"/>

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

<scale
android:fromXScale="0"
android:fromYScale="0"
android:toXScale="1"
android:toYScale="1"
android:pivotY="50%"
android:pivotX="50%"
android:duration="2000"
/>
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="400"
android:toYDelta="400"
android:duration="2000"
/>

</set>

加载并启动该动画

1
2
3
4
5
6
//通过id找到使用动画的控件
ImageView imageview = findViewById(R.id.imageV);
//加载动画的xml文件
Animation animation = AnimationUtils.loadAnimation(this, R.anim.bujian);
//通过使用该动画的控件来启动动画
imageview.startAnimation(animation);

4.3 属性动画

属性动画(Property Animation)是一种可以改变任意对象的属性值,并实现平滑动画效果的机制。这个过程是通过插值器和估值器来计算中间属性值的。插值器控制动画的时间进度,估值器则根据时间进度计算出中间的属性值。

基本方法

方法 说明
ObjectAnimator.ofXXX(target, propertyName, values, …) 创建一个针对指定目标对象、属性名和起始值/结束值的属性动画

其他方法同补间动画的方法一样。

案例

两种方法设置透明度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView textView = findViewById(R.id.tv);
//方法一
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(4000);
//添加动画数值更新监听器
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
float animatedValue = (float) valueAnimator.getAnimatedValue();
Log.d("leo", "onAnimationUpdate: " + animatedValue);
//根据动画数值修改控件的透明度
textView.setAlpha(animatedValue);
}
});
valueAnimator.start();
//方法二
// ObjectAnimator alpha = ObjectAnimator.ofFloat(textView, "alpha", 0f, 1f);
// alpha.setDuration(4000);
// alpha.start();
}

Android开发学习笔记(二) —— 了解UI控件
http://example.com/2023/08/12/Android安全/Android开发学习笔记(二) —— 了解UI控件/
作者
gla2xy
发布于
2023年8月12日
许可协议