一.基本布局
二.常用控件
三.RecyclerView
四.自定义视图
〇.绪论
View:最最基础的UI组件,一块屏幕上的矩形区域,可响应该区域的各种事件。所有其他控件均继承于View,添加了各自特有的功能。
ViewGroup:特殊的View,用于放控件和布局的容器,可以包含多个 子View 和 子ViewGroup。
tips:
- 所有的view都可以设置点击事件
- 除了在xml中设置view的属性,还可以在java代码中设置属性。如
textView.setText("xxx")
- xml中的字符串,通常要在 strings.xml 中声明,通过@String/xxx调用
一.Layout 基本布局
Android中有四大基本布局:
LinearLayout、RelativeLayout、FrameLayout、ConstraintLayout
1.LinearLayout 线性布局
线性排列控件的布局;
LinearLayout 用于使所有子View在单个方向(垂直或水平)保持对齐。
优点:layout_weight属性在屏幕适配时很好用。
缺点:界面较复杂时,需嵌套多层LinearLayout,会降低UI Render效率,还可能引发stackoverflow。
重要属性:
android:orientation:指定布局方向(horizontal or vertiacal)
android:layout_weight:LinearLayout独有,按权重分配屏幕。
例:屏幕只有AB两控件,分别置A控件和B控件的layout_weight为3和2,则A和B按照3:2平分屏幕
指定LinearLayout中layout_weight属性 | 效果 |
---|---|
2.RelativeLayout 相对布局
让控件按照相对位置放置的布局(如:以父容器、兄弟控件为参考+margin+padding就可以设置控件位置)
所有子控件默认在左上角堆叠,最后写入的控件位于最上方显示。
优点:原先需多层LinearLayout才能完成的布局,使用相对布局可能仅需一层就可完成。
参考文档:超全解析:RelativeLayout(相对布局)
两种相对方式:
- 相对父布局定位(可指定多个属性)
android:layout_alignParentTop="true" 在父布局的上面
android:layout_alignParentBottom="true" 在父布局的下面
android:layout_alignParentLeft="true" 在父布局的左边
android:layout_alignParentRight="true" 在父布局的右边
android:layout_CenterInParent="true" 父布局中心
- 相对控件定位
被引用的布局必须先定义(例子中的button_2必须在之前定义)
android:layout_above="@id/button_2" 在按钮2的上方
android:layout_below="@id/button_2" 在按钮2的下方
android:layout_toLeftOf="@id/button_2" 在按钮2的左边
android:layout_toRightOf="@id/button_2" 在按钮2的右方
android:layout_alignLeft="@id/button_2" 左边缘与按钮2左边缘对齐
android:layout_alignRight="@id/button_2" 右边缘与按钮2右边缘对齐
android:layout_alignTop="@id/button_2" 上边缘与按钮2上边缘对齐
android:layout_alignBottom="@id/button_2" 下边缘与按钮2下边缘对齐
相对布局示例 | 展示效果 |
---|---|
3.FrameLayout 帧布局
较为简单,所有控件都默认放在左上角。
控件的位置完全取决于layout_gravity属性。
4.ConstraintLayout 约束布局
2016年新增的功能;ConstraintLayout使用可视化的拖拽方式绘制界面,AS自动生成xml代码。
优点:使用约束的方式指定控件的位置和关系(更强大版的RelativeLayout),解决复杂布局过多嵌套的问题。
二.widget 常用控件
UI控件,各个控件组合起一个界面。
本节介绍了以下控件: TextView、Button、EditText、ImageView、ProgressBar、Dialog
0.控件的通用属性
android : layout_height 该控件在布局中的高度
android : layout_width 该控件在布局中的宽度
android : id 为该控件定义一个id,同一个布局中不可以有相同id
android : background 为控件设置背景色或者背景图片
android : padding 设置控件的内间距,即控件内容与控件边界的距离
android : layout_margin 设置控件的外边距,即该控件与其他控件的距离
android : gravity 控件内容在控件中的对齐方式(控件内部)
android : layout_gravity 控件在父布局中的对齐方式(控件外部)
android : alpha 设置该控件的透明度
android : onClick 为控件的单击事件绑定监听器
android : visibility 设置该控件是否可见{"visible":默认,"inVisible":不可见但占着位置,"gone":不可见}
描述大小的单位:
- dp:dip(device independent pixels设备独立像素),不依赖像素,成比例
- px:pixels像素
- sp:scaled pixels放大像素,用于描述字体大小
1.TextView
文本框,显示文本信息。
TextView的特有属性:
android:text 设置文本内容,一般写在string.xml中,通过@String/xxx调用
android:textColor 设置字体颜色,一般写在colors.xml中
android:textStyle 设置字体风格 {"normal":无效果,"bold":加粗,"italic":斜体}
android:textSize 设置字体大小,单位sp
可以给TextView设置复杂样式,如边框。
步骤:在shapeDrawable中写边框样式的xml文件;将xml中TextView的background属性设置为该边框样式。
边框样式的xml | xml中写TextView | 实现效果 |
---|---|---|
菜鸟教程 TextView详解
TextView官方API(英)
2.Button
按钮,继承自TextView
public class Button extends TextView
Button的特有属性:
android:clickable 是否允许点击;代码中可用setClickable(false)设置
android:onClick 设置点击事件;代码中可用setOnClickListener(OnClickListener I)设置
静态设置监听事件的小例子:
xml布局 | MainActivity.java | 实现效果 |
---|---|---|
动态注册监听事件的两种方法:
匿名类方式 | 实现View.OnClickListener接口方式 |
---|---|
更多资料:
Button详解
菜鸟教程|Button & ImageButton
Button官方API
3.EditText
继承自TextView,可编辑的文本框,用以获取用户的输入数据。
参考资料:
EditText属性详解 强推!
Android EditText的属性和方法介绍使用及值得注意的点
EditText官方API
布局 | 逻辑 | 显示效果 |
---|---|---|
EditText特有属性:
android:hint 默认提示文本
android:textColorHint 提示的颜色
android:selectAllOnFocus="true" 获得焦点(点击EditText)后全选组件内所有文本内容
android:inputType {"phone":拨号键盘,"textPassword":密码格式,“textVisiblePassword”:密码可见格式...}
android:imeOptions 改变输入法中回车按钮的显示内容
....特别多属性....
补充知识:焦点focus
一个窗口中同一时间只能有一个具有焦点的控件;在非触摸屏设备(Android TV)应用的开发中极为重要。
android:focusableInTouchMode="true" 具有触摸获取焦点的能力
android:focusable="true" 具有普通获取焦点的能力(可以理解为物理键盘)
PS:focusableInTouchMode 比 focusable 更牛逼,前者为true,后者一定为true。
非触摸屏(只能电视)一般用键盘上下左右选中,那个框就是焦点;如果某控件android:focusable="true"
,则无论怎么上下左右按键都点不到该控件。
手机上大部分控件都不具有触摸获取焦点的能力,即android:focusableInTouchMode="false"
(比如Button、TextView、LinearLayout,触摸后他们直接响应点击事件;如果置它们的android:focusableInTouchMode="true"
,则触摸第一次获取焦点,触摸第二次才会响应点击事件)。EditText默认具有触摸获取焦点的能力。
4.ImageView
图片控件,可显示任意图像。
用法:
xml中声明ImageView控件
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@mipmap/icon_check"/>
Activity中通过findViewById()获取
public class MainActivity extends RxAppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = (ImageView) findViewById(R.id.image);
//可以设置点击事件、切换图片等...
}
}
ImageView中 src和background属性的区别
- background表示背景,src表示内容
- src填入图片,按照图片原大小填充,不拉伸;
background填入图片,根据ImageView给定的宽度拉伸。
ImageView中adjustViewBounds属性 缩放时是否保持长宽比
以下三件套一起出现才可使用
android:adjustViewBounds="true" 缩放时是否保持长宽比
android:maxHeight="200px" ImageView的最大高度
android:maxWidth="200px" ImageView的最大宽度
ImageView中scaleType属性 设置缩放类型
android:scaleType 缩放类型 Java中通过imageView.setScaleType(ImageView.ScaleType.CENTER);
设置
scaleType有很多可选值。
"center":显示在ImageView中心,保持原图大小。如果原图过大,超过部分裁剪处理
"matrix":默认值,显示在ImageView的左上角,超过部分裁剪处理。
"fitCenter":缩放后显示在ImageView的中间。
...
菜鸟教程|ImageView (属性的例子较多)
ImageView官方API
5.ProgressBar
进度条,系统提供 转圆圈(默认) 和 带进度的长方形 两种进度条。
转圆圈进度条(默认)实现:
xml | 实现效果 |
---|---|
带进度的进度条实现:
xml | java中设置 |
---|---|
实现效果 |
---|
由于自定义的ProgressBar有点丑,通常都会实现自定义的ProgressBar…
菜鸟教程|ProgressBar (含自定义ProgressBar)
ProgressBar官方API
6.Dialog
6.1AlertDialog
在当前界面里弹出一个对话框。该对话框置于最顶部,其他控件都无法交互,通常用于提示重要信息(删除信息前弹出AlertDialog)
java代码 | 显示效果 |
---|---|
AlertDialog可以弹出很多种对话框。包括但不限于下图(代码实现):
参考资料:
菜鸟教程|AlertDialog
几种AlertDialog及其实现
AlertDialog官方API
三.RecyclerView
0.ListView
Adapter:适配器。(思想参照设计模式中的适配器模式,适配器类似三相转两相的转接器)
ListView中适配器用作 ListView 和 真实数据 的转接器,通过重载不同参数类型的构造函数,可以实现 真实数据-> ListView 的传递。
ListView的适配器,未优化的自定义适配器的代码。
FruitAdapter:
上述ListView的性能问题:每个子项滚动至屏幕内都会调用getView(),getView()中都执行一遍 加载布局 和 实例化控件。
优化方案:
- 引入convertView:避免重复加载布局。
加载过的布局存在convertView中,这样加载过的布局就不用再被加载一遍了。
- 引入viewHolder:避免重复加载布局。
实例化过的控件对象存在viewHolder中,findViewById只有第一次才会被调用,实例化过的控件对象不用重复实例化了。
1.RecyclerView
RecyclerView,顾名思义带有 回收复用 功能的ListView,更强大的滚动控件,Android官方推荐使用。
优点:
- 回收复用的功能。封装ViewHolder的回收复用,可直接面向ViewHolder编写Adapter
- 插拔式的体验。高度解藕,针对 布局、动画、分割线都抽象出对应的类,用哪个set哪个。
1.1 RecyclerView 要素
ViewHolder:RecyclerView封装了RecyclerView.ViewHolder(实现了ViewHolder的标准化),写Adapter时直接面向ViewHolder,复用逻辑被封装,写起来更简单。
在RecyclerView.ViewHolder 的三个方法 onCreateViewHolder()、onBindViewHolder()、getItemCount()的重写中写复杂逻辑。
LayoutManager:RecyclerList设置布局管理器LayoutManager以控制item的布局方式。LayoutManager是一个抽象类,他有三个实现类:通过 recyclerView.setLayoutManager(new XxxxLayoutManager(this))
的方式设置。
- LinearLayoutManager:线性布局,支持横向或纵向
- GridLayoutManager:网格布局
- StaggeredGridLayoutManager 瀑布流布局
ItemAnimator(可选):RecyclerView提供的动画控制类,控制Item增减动画。
ItemDecoration(可选):设置Item的间隔样式
1.2 RecyclerView 的点击事件
RecyclerView没有类似 ListView 的 setOnItemClickListener()之类的注册监听器方法,需要我们自己给子项的具体View去注册点击事件。xxxView.setOnClickListener(..
Q:为什么? ListView 的 setOnItemClickListener()之类注册监听器看起来更方便呀?
A:ListView的 setOnItemClickListener() 只实现对子项的点击事件,想实现子项中的某个按钮的点击事件就比较麻烦;RecyclerView的注册方式更为灵活,可实现对子项其中具体某个View的点击事件。
1.3 RecyclerView的demo
1. build.gradle 中添加依赖
implementation 'androidx.recyclerview:recyclerview:1.1.0'
2. MeiziAdapter.java
public class MeiziAdapter extends RecyclerView.Adapter<MeiziAdapter.ViewHolder> {
List<Meizi> mData;
Context context;
public MeiziAdapter(Context context, List<Meizi> meiziList){
this.context = context;
this.mData = meiziList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.meizi_item,parent,false); //引入item的布局
final ViewHolder holder = new ViewHolder(itemView);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();//通过viewHolder获得position
Meizi meizi = mData.get(position); //获得Adapter中对应position的数据
Toast toast = Toast.makeText(view.getContext(),"您已点击 妹子"+position,Toast.LENGTH_SHORT);
toast.show();
//更新列表项
meizi.setContent(meizi.getContent()+"已点击");
notifyItemChanged(position); //通知recyclerView指定position已更改
}
});
return holder;
}
//写滚动到指定item时,对控件的行为
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
Meizi meizi = mData.get(position);
holder.textView.setText(meizi.getContent());
}
@Override
public int getItemCount() {
return mData.size();
}
ViewHolder内部类 实例化控件
class ViewHolder extends RecyclerView.ViewHolder{
View itemView;
ImageView imageView;
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
this.itemView = itemView;
imageView = itemView.findViewById(R.id.imageView);
textView = itemView.findViewById(R.id.comment);
}
}
}
3. 数据类 Meizi.java
public class Meizi {
private String content;
public Meizi(String content){
this.content = content;
}
public String getContent(){
return content;
}
public void setContent(String content){
this.content = content;
}
}
4. 每一项的布局 meizi_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="157dp"
android:layout_height="125dp"
android:layout_marginStart="8dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.073"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/meizi"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
5. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--加了个RecyclerView,具体布局meizi_item.xml在java代码中指定-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
6.MainActivity.java
public class MainActivity extends AppCompatActivity {
private List<Meizi> meiziList = new ArrayList<Meizi>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
MeiziAdapter adapter = new MeiziAdapter(this,meiziList); //数据传入Adapter
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView recyclerView = findViewById(R.id.recyclerView); //拿到recyclerview
//给recyclerView设置setxxx()各种
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(layoutManager);
}
private void initData(){
for(int i=0;i<100;++i){
meiziList.add(new Meizi("妹子"+i));
}
}
}
效果:
滑动后,点击每个item,弹出toast “您已点击妹子几”,同时改变RecyclerView中文字的显示。
四.View 自定义视图
知识很多,很重要的部分,还需深入学习。
0.背景知识
Activity: 控制器,只是控制生命周期和处理事件,控制视图的实际上是Window。一个Activity包含了一个Window,Window才是真正代表一个窗口。Activity就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window、以及View进行交互。
Window:(视图的)承载器,Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow。PhoneWindow内部持有一个 DecorView。
DecorView:顶级view,所有view的最外层布局。DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView包含标题栏和内容栏。
ViewRoot:连接器,负责WindowManagerService与DecorView之间的通信。对应ViewRootImpl类,View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。
负责交互,Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRoot进行分发的。
用户点击屏幕产生一个触摸行为,触摸行为 通过硬件捕获 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity
参考文章:Window、Activity、DecorView、ViewRoot之间的关系
1.Android自定义View
单纯用系统的控件和布局不能满足要求,我们可以自定义View去实现界面。(系统控件也是人写的,别人能写我也能写)
必读:
Android自定义View全解
View测量、布局及绘制原理
选读:
Android自定义View的各种姿势
Android创建自定义的View类
《第一行代码》的demo1:创建一个自定义的标题栏(引入自定义布局)
1.新建一个布局title.xml
左边一个Back,中间Tile,右边Edit
2.在activity_main.xml中使用该标题栏
3.在MainActivity中隐藏系统自带的标题栏
效果:
《第一行代码》的demo2:创建自定义的控件(使用自定义控件)
新建自定义标题栏控件TitleLayout继承自LinearLayout
修改activity_main.xml中代码,添加自定义控件
通过这种方式实现各个界面可以复用一个TitleLayout。实现效果如下: