Android四大组件
Android 开发的四大组件分别是:活动(activity),用于表现功能;服务(service),后台运行服务,不提供界面呈现;广播接受者(Broadcast Receive),用于接收广播;内容提供者(Content Provider),支持多个应用中存储和读取数据,相当于数据库。
活动(Activity)
定义:
Activity是Android的四大组件之一。是用户操作的可视化界面;它为用户提供了一个完成操作指令的窗口。当我们创建完毕Activity之后,需要调用setContentView()方法来完成界面的显示;以此来为用户提供交互的入口。在Android App 中只要能看见的几乎都要依托于Activity,所以Activity是在开发中使用最频繁的一种组件。
一个Activity通常就是一个单独的屏幕(窗口)。
Activity之间通过Intent进行通信。
android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity。在android stdio会自动生成,但eclipse需要自己手动添加。
Activity的生命周期
在Android中会维持一个Activity Stack(Activity栈),当一个新的Activity创建时,它就会放到栈顶,这个Activity就处于运行状态。当再有一个新的Activity被创建后,会重新压入栈顶,而之前的Activity则会在这个新的Activity底下,就像枪梭压入子弹一样。而且之前的Activity就会进入后台。
一个Activity实质上有四种状态:
-
运行中(Running/Active):这时Activity位于栈顶,是可见的,并且可以用户交互。
-
暂停(Paused):当Activity失去焦点,不能跟用户交互了,但依然可见,就处于暂停状态。当一个新的非全屏的Activity或者一个透明的Activity放置在栈顶,Activity就处于暂停状态;这个时候Activity的各种数据还被保持着;只有在系统内存在极低的状态下,系统才会自动的去销毁Activity。
-
停止(Stoped):当一个Activity被另一个Activity完全覆盖,或者点击HOME键退入了后台,这时候Activity处于停止状态。这里有些是跟暂停状态相似的:这个时候Activity的各种数据还被保持着;当系统的别的地方需要用到内容时,系统会自动的去销毁Activity。
-
销毁(Detroyed):当我们点击返回键或者系统在内存不够用的情况下就会把Activity从栈里移除销毁,被系统回收,这时候,Activity处于销毁状态。
四种启动模式
Standard 模式 : standard 模式是android 的默认启动模式,在这种模式下,activity可以有多个实例,每次启动Activity,无论任务栈中是否已经存在这个activity的实例,系统都会创建一个新的activity实例。
SingleTop 模式: 栈顶模式,当一个singleTop模式的activity 已经位于栈顶时,再去启动它时,不在创建实例,如果不在栈顶,就会创建实例。
SingleTask 模式 : 单任务模式,如果启动的activity 已经存在于 任务栈中,则会将activity移动到栈顶,并将上面的activity出栈,否则创建新的实例
SingleInstance 模式 :单实例模式,一个activity 一个栈。
三种跳转方式
显示启动:
intrent内部直接声明要启动的Activity所对应的class。
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intnet);
隐式启动:
进行三个匹配,分别是Activity、Category、Data,全部或者部分匹配,应用于广播原理。
清单文件里配置Activity属性,Activity的名字要和跳转内容一样。
<activity
android:name="com.exanple.android.tst.secondActivity"
android:label = @string/title>
<intent=filter>
<action android:name="com.exanple.android.tst.secondActivity/>
<category android:name="android.intent.category.DEFAULT"/>
<intent-filter/>
</activity>
需要跳转的地方
Intent intent = new Intent("com.example.android.tst.secondActivity");
startActivity(intnet);
跳转后再返回,能获取返回值
Intent in = new Intent(MainActivity.this,OtehrActivity.class);
in.putExtra("a",a);
startActivityForResult(in,1000);
在OtherActivity中设置返回值
Intent int = new Intent();
int.putExtra("c",c);
setResult(1001,int);
finish();
在MainActivity中获取返回值
@Override
protected void onActivityResult(int requestCode, int resultCode,Intent data) {
super.onActivityResult(requestCode,resultCode,data);
if(requestCode == 1000){
if(resultCode == 1001){
int c = data.getExtra("c",0);
}
}
}
服务(Service)
service(服务)是安卓中的四大组件之一,它通常用作在后台处理耗时的逻辑,与Activity一样,它存在自己的生命周期,也需要在AndroidManifest.xml配置相关信息。
服务(Service)是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
不过需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程。与某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。另外,也不要被服务的后台概念所迷惑,实际上服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。
service用于在后台完成用户指定的操作。
service分为两种:
- started(启动):当应用程序组件(如activity)调用startService()方法启动服务时,服务处于started状态。
- bound(绑定):当应用程序组件调用bindService()方法绑定到服务时,服务处于bound状态。
startService()与bindService()区别:
- started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
- 使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
开发人员需要在应用程序配置文件中声明全部的service,使用<service></service>标签。
Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
定义
Service是一个专门在后台处理长时间任务的Android组件,它没有UI。他有两种启动方式,startService和bindService。
启动方式的区别
startService
只是启动Service,启动它的组件(如Activity)和Service没有关联,只有当Service调用stopSelf或者其他组件调用stopService服务才会终止。
bindService
启动Service,其他组件可以通过回调获取Service的代理对象和Service交互,而这两方也进行了绑定,当启动方销毁时,Service也会自动进行unBind操作,当发现所有绑定都进行了unBind是才会销毁Service。
Service和onCreate回调函数不可以做耗时操作,因为Service的onCreate是在主线程(Activity Thread)中调用的,耗时操作会阻塞UI。
线程和Handler方式可以做耗时的操作。
intentService
IntentService相比父类Service而言,最大特点是其回调函数onHandleIntent中可以直接进行耗时操作,不必再开线程。其原理是IntentService的成员变量 Handler在初始化时已属于工作线程,之后handleMessage,包括onHandleIntent等函数都运行在工作线程中。
如果对IntentService的了解仅限于此,会有种IntentService很鸡肋的观点,因为在Service中开线程进行耗时操作也不麻烦。我当初也是这个观点,所以很少用IntentService。
但是IntentService还有一个特点,就是多次调用onHandleIntent函数(也就是有多个耗时任务要执行),多个耗时任务会按顺序依次执行。原理是其内置的Handler关联了任务队列,Handler通过looper取任务执行是顺序执行的。
这个特点就能解决多个耗时任务需要顺序依次执行的问题。而如果仅用service,开多个线程去执行耗时操作,就很难管理。
使用IntentService
服务中的代码都默认运行在主线程中,如果直接在服务中执行耗时操作很容易出现ANR(Application not Responding)。
所以这个时候需要用到Android多线程编程技术,我们应该在服务的每个具体的方法里启动一个子线程,然后在这里去处理那些耗时的操作:
public class MyService extends Service{
...
@Override
public int onStartCommand(Intent intent , int flags, int startId){
new Thread(new Runnable(){
public void run(){
//处理具体的逻辑
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
}
但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来,所以,如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写:
public class MySerivce extends Servcie{
...
@Override
public int onStartCommand(Intent intent, int flats , int startId){
new Thread(new Runnable(){
public void run(){
//处理具体的逻辑
stopSelf();
}
});
}
}
虽说这样的写法并不复杂,但是总会有一些程序员忘记开启线程或者忘记调用stopSelf() 方法。为了简单创建一个异步、会自动停止的服务。Android专门提供了一个IntentService类。
public class MyIntentService extends IntentService{
public MyIntentService(){
super("MyIntentService"); //调用父类的有参构造方法
}
@Override
protected void onHandleIntent(Intent intent){
//打印当前的线程ID
Log.e("mylog","Thread id is” + Thread.cuttentThread().getId();
}
@Override
public void onDestory(){
super.onDestory();
Log.e("mylog","on Destory executed");
}
}
首先这里提供一个无参的构造方法,并且必须在其内部调用父类的有参构造方法。然后要在子类中去实现onHandleIntent() 这个抽象方法,在这个方法中可以去处理一些逻辑,而且不用担心ANR,因为这个方法已经是在子线程中运行了。
IntentService线程的调用:
Intent intent = new Intent(this, MyIntentService.class);
startServcie(intent);
如此,线程就会自动启动并执行逻辑,执行完毕后自动关闭。这就是IntentService 的好处,能够自动开启和关闭;
定义一个Server
项目内Server包 右键 --> New --> Service --> Service 或者直接创建Class类,继承Service并重写IBinder方法。
public class MyService extends Service{
public MyService(){
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
}
}
重写Service的 onCreate()、onStartCommand()和onDestory()方法。其中 onCreate() 方法在服务创建的时候调用、onStartCommand() 方法会在每次服务启动的时候调用、onDestory() 方法会在服务销毁的时候调用。
通常情况下,如果我们希望服务一旦启动就立刻去执行任务,就可以将逻辑卸载onStartCommand() 方法里。
另外需要注意的是,每个服务都需要在Androidmanifest.xml 中进行注册才能生效。
<application
....>
...
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
启动和停止服务
启动服务:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); //启动服务
停止服务:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); //停止服务
使用前台服务
前台服务与普通服务的最大区别在于,它会一直有一个正在运行的图标在系统的状态栏中,下拉状态栏后可以看到更加详细的内容,非常类似于通知的效果。
public class MyService extends Service{
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0 , intent, 0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle(" this is content titile")
.setContentText("this is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher);
.setLargeIcon(BitmapFactory.decodeResource(getResource(),
R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
构造一个Notification对象后并没有使用NotificationManager 来讲通知显示出来,而是调用了startForeground()方法,该方法会将MyService变成一个前台服务,并在系统状态栏中显示出来。
广播接受者(Broadcast Receive)
定义
android 广播分为两个角色:广播发送者、广播接收者
android 广播:
-
用于不同组件间的通信(含:应用内/不同应用之间)
-
用于多线程通信
-
与android系统的通信
在Android中,广播是一种广泛运用的在应用程序之间传输信息的机制。而广播接收器是对发送出来的广播进行过滤接受并响应的一类组件。可以使用广播接收器来让应用对一个外部时间做出响应。例如,当电话呼入这个外部事件到来时,可以利用广播接收器进行处理。当下载一个程序成功完成时,仍然可以利用广播接收器进行处理。广播接收器用NotificationManager来通知用户这些事情发生了。广播接收器既可以在AndroidManifest.xml中注册,也可以在运行时的代码中使用Context.registerReceive()进行注册。只要是注册了,当事件来临时,即使程序没有启动,系统也在需要的时候启动程序。各种应用还可以通过使用Context.sendBroadcast()将它们自己的Intent广播给其他应用程序。
应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
自定义广播接受者
-
继承BroadcastReceive基类。
-
必须重写抽象方法onReceive()方法。
广播接收器收到相应广播后,会自动调用onReceive()方法。
一般情况下,onReceive方法会会涉及与其他组件之间的交互,如,发送Notiotification,启动server等。
默认情况下,广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作,否则将导致ANR。
广播接收者的注册
有两种方法,分别是程序动态注册(在运行时的代码中使用Context.registerReceive()进行注册)和AndroidManifest文件中进行静态注册。
静态注册
注册方式:在AndroidManifest.xml 里通过<receiver 标签声明。
属性说明:
<receiver
android:enable="true"/"false"
//此broadcastReceiver 是否接受其他应用发出的广播
//默认值时由receiver 中d有无inter-filter决定,如果有,默认true,否则默认false
android:exported="true"/"false"
android:icon="drawable resource"
android:label="string resource"
//继承BroadcastReceiver子类的类名
android:name=".mBroadcastReceiver"
//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
android:permission="string"
//BroadcastReceiver运行所处的进程
//默认为app的进程,可以指定独立的进程
//注:Android四大基本组件都可以通过此属性指定自己的独立进程
android:process="string" >
//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
注册示例:
<receiver
//此广播接收者类是mBroadcastReceiver
android:name=".mBroadcastReceiver" >
//用于接收网络状态改变时发出的广播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
当此APP首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。
动态注册
注册方式:在代码中调用Context.registerReceiver()方法。
具体代码如下:
// 1. 实例化BroadcastReceiver子类 & IntentFilter
mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
// 2. 设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
// 3. 动态注册:调用Context的registerReceiver()方法
registerReceiver(mBroadcastReceiver, intentFilter);
//动态注册广播后,需要在相应位置记得销毁广播
unregisterReceiver(mBroadcastReceiver);
特别注意:
动态广播最好在onResume中注册,onPause注销。
原因:
-
对于动态广播,有注册必然得有注销,否则会导致内存泄漏。
-
onPause在App在死亡前一定会被执行,从而保证App死之前一定会被注销,从而防止内存泄漏。
区别
动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用。
注册方式 | 区别 | ||
使用方式 | 特点 | 应用场景 | |
静态注册(常驻广播) | 在AndroidManifest.xml 里通过<receiver 标签声明 |
| 需要实时监听广播 |
动态注册(非常驻广播) | 在代码中调用Contxt.regisReceiver()方法 | 非常驻,灵活,跟随组价生命周期变化(组件结束=广播结束,在组件结束前,必须移除广播接收器) | 需要特定时刻监听广播 |
广播的发送
广播的发送 = 广播发送者 将此广播的意图(intent)通过 sendBroasdcast() 方法发送出去。
广播的类型
-
普通广播
-
是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此他们之间没有任何先后顺序可言。例如系统广播。
-
特点:
-
广播的发送者不会知道有哪些接收者接收到了广播消息,也无法影响接收者的执行顺序。
-
接收者无法中断或取消广播的传递。
-
广播的发送和接收是完全异步的,发送者和接收者之间没有直接的交互。
-
-
-
系统广播
-
有序广播
-
是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所有此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。
-
特点:
-
有序广播允许接收者按照优先级顺序处理广播消息。接收者可以通过设置优先级来控制自己在广播链中的执行顺序。
-
每个接收者可以中断广播的传递,以防止其他接收者继续接收广播消息。
-
有序广播的发送者可以获取接收者处理广播的结果,因为每个接收者可以返回一个结果代码。
-
-
适用情况:
-
需要按照特定的顺序处理广播消息,确保接收者按照预期的顺序执行。
-
需要控制广播的传递,允许接收者中断广播的传递。
-
需要接收者之间进行通信和协作,通过设置结果代码来返回结果给广播发送者。
-
-
-
粘性广播
-
App应用内广播
特别注意:
对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:
-
对于静态注册(全局+应用内广播),回调onReceive(context,intent)中的context返回值是:ReceiverRestrictedContext;
-
对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
-
对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context,intent)中的context返回值是:Application Context。
-
对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context,intent)中的context返回值是:Activity Context;
内容提供者(Content Provider)
android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
使用
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver 类,可以通过Context中的getContentResolver() 方法获取该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,其中insert() 方法用于添加数据,update() 方法用于更新数据,delete() 方法用于删除数据,query() 方法用于查询数据。
不同于SQLiteDatabase,ContentResolver 中的增删改查都是接收一个URl参数,这个参数被称为内容URL。内容URL给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority 和 path 。authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式进行命名。path则是用于对同一应用程序中不同的表做区分,通常都会添加到authority后面:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在使用内容URL作为参数的时候,需要将URL转换成URL对象:
Uri uri = Uri.parse("content://com.example.app.provider/table1")
然后使用uri对象来查询table表中数据:
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder
);
对应参数的解释:
query()方法参数 | 对应SQL部分 | 描述 |
uri | from table_name | 指定查询某个应用程序下的某个表 |
projection | select column1, column2 | 指定查询的列名 |
selection | where column=value | 指定where约束条件 |
selectArgs | - | 为where中的占位符提供具体的值 |
sortOrder | order by column1, column2 | 指定查询结果的排序方式 |
查询后,可以从中取值:
if(cursor != null){
while(cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
增删改查
添加数据
ContentValues values = new ContentValues();
values.put(“column1”, "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
更新数据
ContentValues valuse = new ContentValues();
valuse.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[]{"text", 1});
删除数据
getContentResolver().delete(uri , "column2 = ?", new String[]{ "1"});
读取系统联系人
读取系统联系人需要声明权限,如果系统是6.0以后的,需要申请运行时权限。
if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
}else {
readContacts(); //读取联系人
}
private void readContacts(){
Cursor cursor = null;
try{
//查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
if(cursor!=null){
while(cursor.moveToNext()){
//获取联系人姓名
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人电话号码
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
list.add(name+"\n"+number);
}
}
}catch(Exception e){
e.printStackTrace()
}finally{
if(cursor != null){
cursor.close();
}
}
}
@Override
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults){
switch(requestCode){
case 1:
if(grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
readContacts();
}else {
//您拒绝了权限
}
}
}
创建内容提供器
创建的内容提供器,需要去继承 ContentProvider 类,ContentProvider 类中有6个抽象方法,在使用子类继承它的时候,需要将这6个方法全部重写。
public class MyProvider extends ContentProvider{
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, Stirng selection, String[] selectionArgs, String sortOrder){
return null;
}
@Overrride
public Uri insert(Uri uri , ContentValues values){
return null;
}
@Override
public int update(Uri uri, ContentValuse values, String selection, String[] selectionArgs){
return 0;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs){
return 0;
}
@Override
public String getType(Uri uri){
return null;
}
}
URI主要格式
content://com.example.app.provider/table1
content://com.example.app.provider/table1/1
* : 表示匹配任意长度的任意字符
# : 表示匹配任意长度的数字
//一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*
//一个能够匹配表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#
Android七大布局
七大布局分别为:
线性布局(LinearLayout)
相对布局(RelativeLayout)
帧布局(FrameLayout)
表格布局(TableLayout)
绝对布局(AbsoluteLayout)
网格布局(GridLayout)
约束布局(ConstraintLayout)
基本概念
把界面中的控件按照某种规律摆放在制定的位置,解决应用程序在不同机器上的显示问题,其实现主要有两种方式:
-
代码:布局文件对应的Java类都是ViewGroup类的子类。
-
xml配置文件:所有的布局文件都是放在res/layout文件夹中。
基本属性
layout_width与layout_height
设置布局文件的长度和宽度,常用取值为:wrap_content、fill_parent、match_parent。
单位 | 含义 |
dp | 逻辑长度单位,在160dpi屏幕上,1dp=1px=1/160英寸。随着密度变化,对应的像素数量也变化,但并没有直接的变化比例。 dpi:屏幕上有多少个像素点。 |
dip | 与dp相同,多用于google的实例中 |
sp | 与dp类似,但是可以根据用户的字体大小首选项进行缩放 |
pt | 对象放其容器右侧 |
mm | 毫米,屏幕的物理尺寸 |
in | 英寸,屏幕的物理尺寸。每英寸等于2.54厘米 |
px | 像素 |
gravity与layout_gravity
gravity设置控件中内容相对于该控件的对齐方式,layout_gravity设置该控件相对于父控件的对齐方式。
指令 | 含义 |
top | 对象放其容器顶部 |
bottom | 对象放其容器底部 |
left | 对象放其容器左侧 |
right | 对象放其容器右侧 |
center | 对象居中 |
center_vertical | 对象纵向居中 |
center_horizontal | 对象横向居中 |
fill | 对象整体填充 |
fill_vertical | 对象纵向填充 |
fill_horizontal | 对象横向填充 |
layout_marginTop与layout_marginBottom
当前视图上、下边缘到某条基线的距离,只能取固定值。
线性布局(LinearLayout)
线性布局控制其中的控件按照横向或纵向方式排列,并且线性布局不会换行,当控件排列到窗体边缘,后面的控件就被隐藏,不会被显示出来。
线性布局在开发中使用最多,具有垂直方向与水平方向的布局方式,通过设置属性“android:orientation”控制方向,属性值垂直(vertical)和水平(horizontal),默认水平方向。
horizontal | 水平(默认) |
vertical | 垂直 |
android:gravity:内部控件对齐方式,常用属性值有center、center_vertical、center_horizontal、top、bottom、left、right等。
这个属性在布局组件RelativeLayout、TableLayout中也有使用,FrameLayout、AbsoluteLayout则没有这个属性。
center:居中显示,这里并不是表示显示在LinearLayout的中心,当LinearLayout线性方向为垂直方向时,center表示水平居中,但是并不能垂直居中,此时等同于center_horizontal的作用;同样当线性方向为水平方向时,center表示垂直居中,等同于center_vertical。
top、bottom、left、right顾名思义为内部控件居顶、低、左、右布局。
这里要与android:layout_gravity区分开,layout_gravity是用来设置自身相对于父元素的布局。
android:layout_weight:权重,用来分配当前控件在剩余空间的大小。
使用权重一般要把分配该权重方向的长度设置为零,比如在水平方向分配权重,就把width设置为零。
设置线性布局的线性方向。
layout_weight
指定父控件剩余空间分配比例的权重。
注意:
-
水平方向的线性布局中使用weight时需要注意将宽度设置为0dp,垂直方向的线性布局中使用weight时需要将高度设置为0dp。
-
当只给一个控件配置权重时,可以使控件将剩余空间全部填充,使控件撑开。
相对布局(RelativeLayout)
相对布局是通过相对定位的方式让控件出现在布局的任意位置;在相对布局中如果不指定控件摆放的位置,那么控件都会默认放在RelativeLayout的左上角。因此要先指定第一个控件的位置,再根据一个控件去给其他控件布局。
相对布局可以让子控件相对于兄弟控件或父控件进行布局,可以设置子控件相对于兄弟控件或父控件进行上下左右对齐。
RelativeLayout能替换一些嵌套视图,当我们用LinearLayout来实现一个简单的布局但又使用了过多的嵌套时,就可以考虑使用RelativeLayout重新布局。
相对布局就是一定要加Id才能管理。
相对于父元素给控件布局
layout_centerHrizontal | 水平居中 |
layout_centerVertical | 垂直居中 |
layout_centerInparent | 相对于父元素完全居中 |
layout_alignParentBottom | 位于父元素的下边缘 |
layout_alignParentLeft | 位于父元素的左边缘 |
layout_alignParentRight | 位于父元素的右边缘 |
layout_alignParentTop | 位于父元素的上边缘 |
layout_alignWithParentIfMissing | 如果对应的兄弟元素找不到的话就以父元素做参照物 |
相对于兄弟元素给控件布局(id的引用名"@id/id-name")
layout_toRightof | 控件的左边缘与给定id控件的右边缘对齐 |
layout_toLeftof | 控件的右边缘与给定id控件的左边缘对齐 |
layout_below | 控件的上边缘与给定id控件的下边缘对齐 |
layout_alignRight | 控件的右边缘与给定id控件的右边缘对齐 |
layout_alignLeft | 控件的左边缘与给定id控件的左边缘对齐 |
layout_alignTop | 控件的上边缘与给定id控件的上边缘对齐 |
layout_alignBottom | 控件的下边缘与给定id控件的下边缘对齐 |
RelativeLayout中子控件常用属性:
1、相对于父控件,例如:
android:layout_alignParentTop=“true”
android:layout_alignParentTop 控件的顶部与父控件的顶部对齐;
android:layout_alignParentBottom 控件的底部与父控件的底部对齐;
android:layout_alignParentLeft 控件的左部与父控件的左部对齐;
android:layout_alignParentRight 控件的右部与父控件的右部对齐;
2、相对给定Id控件,例如:
android:layout_above=“@id/**”
android:layout_above 控件的底部置于给定ID的控件之上;
android:layout_below 控件的底部置于给定ID的控件之下;
android:layout_toLeftOf 控件的右边缘与给定ID的控件左边缘对齐;
android:layout_toRightOf 控件的左边缘与给定ID的控件右边缘对齐;
android:layout_alignBaseline 控件的baseline与给定ID的baseline对齐;
android:layout_alignTop 控件的顶部边缘与给定ID的顶部边缘对齐;
android:layout_alignBottom 控件的底部边缘与给定ID的底部边缘对齐;
android:layout_alignLeft 控件的左边缘与给定ID的左边缘对齐;
android:layout_alignRight 控件的右边缘与给定ID的右边缘对齐;
3、居中,例如:
android:layout_centerInParent=“true”
android:layout_centerHorizontal 水平居中;
android:layout_centerVertical 垂直居中;
android:layout_centerInParent 父控件的中央;
表格布局(TableLayout)
表格布局可以将视图按行、列进行排列,一个表格由一个TableLayout标签和若干个TableRow标签组成。
表格布局,适用于多行多列的布局格式,每个TableLayout是由多个TableRow组成,一个TableRow就表示TableLayout中的每一行,这一行可以由多个子元素组成。实际上TableLayout和TableRow都是LineLayout线性布局的子类。但是TableRow的参数android:orientation属性值固定为horizontal,且android:layout_width=MATCH_PARENT,android:layout_height=WRAP_CONTENT。所以TableRow实际是一个横向的线性布局,且所以子元素宽度和高度一致。
注意:在TableLayout中,单元格可以为空,但是不能跨列,意思是只能不能有相邻的单元格为空。
TableLayout常用属性:
android:shrinkColumns:设置可收缩的列,内容过多就收缩显示到第二行
android:stretchColumns:设置可伸展的列,将空白区域填充满整个列
android:collapseColumns:设置要隐藏的列
列的索引从0开始,shrinkColumns和stretchColumns可以同时设置。
子控件常用属性:
android:layout_column:第几列
android:layout_span:占据列数
收缩
xml属性:
shrinkColumns
方法:setColumnsShrinkAble(int,boolean)
拉伸
xml属性:
stretchColumns
方法:setColumnsStretchAble(int,boolean)
隐藏
xml属性:
collapseColumns
方法:setColumnsCollapsed(int,boolean)
合并单元格
xml属性:
layout_span=“3”
帧布局(FrameLayout)
帧布局或叫层布局,从屏幕左上角按照层次堆叠方式布局,后面的控件覆盖前面的控件。
该布局在开发中设计地图经常用到,因为是按层次方式布局,我们需要实现层面显示的样式时就可以
采用这种布局方式,比如我们要实现一个类似百度地图的布局,我们移动的标志是在一个图层的上面。
在普通功能的软件设计中用得也不多。层布局主要应用就是地图方面。
为每个加入其中的组件创建单独的帧,看上去像是组件叠加在一起。
绝对布局(AbsoluteLayout)
绝对布局中将所有的子元素通过设置android:layout_x 和 android:layout_y属性,将子元素的坐标位置固定下来,即坐标(android:layout_x, android:layout_y) ,layout_x用来表示横坐标,layout_y用来表示纵坐标。屏幕左上角为坐标(0,0),横向往右为正方,纵向往下为正方。实际应用中,这种布局用的比较少,因为Android终端一般机型比较多,各自的屏幕大小。分辨率等可能都不一样,如果用绝对布局,可能导致在有的终端上显示不全等。所有基本不会使用。
网格布局(GridLayout)-Android4.0新布局
使用的时候需要注意兼容,引入依赖: compile ‘com.android.support:gridlayout-v7:22.+’
作为android 4.0 后新增的一个布局,与前面介绍过的TableLayout(表格布局)其实大同小异。
①跟LinearLayout(线性布局)一样,他可以设置容器中组件的对齐方式
②容器中的组件可以跨多行也可以跨多列(相比TableLayout直接放组件,占一行相比较)
因为是android 4.0新增的,API Level 14,在这个版本以前的sdk都需要导入项目。这里不解释。
常用属性:
排列对齐:
①设置组件的排列方式:
android:orientation="" vertical(竖直,默认)或者horizontal(水平)
②设置组件的对齐方式:
android:layout_gravity="" center,left,right,buttom
设置布局为几行几列:
①设置有多少行:
android:rowCount="4" //设置网格布局有4行
②设置有多少列:
android:columnCount="4" //设置网格布局有4列
设置某个组件位于几行几列
注:都是从0开始算的!
①组件在第几行:
android:layout_row = "1" //设置组件位于第二行
②组件在第几列:
android:layout_column = "2" //设置该组件位于第三列
设置某个组件横跨几行几列(合并):
横跨几行:
android:layout_rowSpan = "2" //纵向横跨2行
横跨几列:
android:layout_columnSpan = "3" //横向横跨2列
android:layout_gravity="fill"填充
GridLayout属性:
columnCount | 最大列数 |
rowCount | 最大行数 |
orientation | 子元素中的布局方向 |
alignmentMode | alignBounds(值=0):对齐子视图边界。alignMargins(值=1):对齐子视图边距 |
columnOrderPreserved | 使列边界显示的顺序和列索引的顺序相同,默认true |
rowOrderPreserved | 使行边界显示的顺序和行索引的顺序相同,默认是true |
useDefaultMargins | 没有指定视图的布局参数时使用默认的边距,默认值是false |
item属性:
layout_column | 表明控件从第几列开始 |
layout_row | 表明控件从第几行开始 |
layout_columnSpan | 表明控件跨越多少列 |
layout_rowSpan | 表明控件跨越多少行 |
layout_gravity | 指定该单元格在容器中的位置 |
layout_columnWeight | 列权重 |
layout_rowWeight | 行权重 |
布局的属性:
(1)layout_margin
用于设置控件边缘相对于父控件的边距
android:layout_marginLeft
android:layout_marginRight
android:layout_marginTop
android:layout_marginBottom
(2) layout_padding
用于设置控件内容相对于控件边缘的边距
android:layout_paddingLeft
android:layout_paddingRight
android:layout_paddingTop
android:layout_paddingBottom
(3) layout_width/height
用于设置控件的高度和宽度
wrap_content 内容包裹,表示这个控件的里面文字大小填充
fill_parent 跟随父窗口
match_parent
(4) gravity
用于设置View组件里面内容的对齐方式
top bottom left right center等
约束布局(ConstraintLayout)
Android Studio2.2推出的新布局,并从2.3版本开始成为默认布局。为了解决复杂的布局,嵌套过多布局问题。
ConstraintLayout使用起来比RelativeLayout更灵活,性能更出色,可以按照比例约束控件位置和尺寸,更好适配屏幕大小的不同机型。
layout_constraintLeft_toLeftOf | 该控件左侧与另一个控件的左侧对齐 |
layout_constraintLeft_toRightOf | 该控件左侧与另一个控件的右侧对齐 |
layout_constraintRight_toLeftOf | 该控件右侧与另一个控件的左侧对齐 |
layout_constraintRight_toRightOf | 该控件右侧与另一个控件的右侧对齐 |
layout_constraintTop_toTopOf | 该控件顶部与另一个控件的顶部对齐 |
layout_constraintTop_toBottomOf | 该控件顶部与另一个控件的底部对齐 |
layout_constraintBottom_toTopOf | 该控件底部与另一个控件的顶部对齐 |
layout_constraintBottom_toBottomOf | 该控件底部与另一个控件的底部对齐 |
layout_constraintBaseline_toBaselineOf | 文本基线对齐 |