比如实现这样一个场景:

“在屏幕宽度的1/4的地方放置一个View”

使用传统布局时,实现按照屏幕的宽度(高度),或者相对两个View之间距离的一个比例来进行布局,就显得非常麻烦,但是当使用ConstraintLayout时,就可以很简单地实现这样的需求。

Bias

Bias就是为了实现这种需求而设计出来的。

我们来举例说明,看下图:

-w299

当前我们是将这个按钮相对屏幕宽度居中显示。
那么我们如何将该按钮放到宽度1/4的地方呢?

其实非常简单,我们看右侧的属性栏:

-w609

这里有个滑动条,就是偏差的调整(Bias),我们将这里拖到25的位置,也就意味着当前宽度的25%,此时当前View就便宜屏幕的1/4处了(注意View本身的宽度)。

其它说明

  • 本例是水平方向的调整。当垂直方向上下都有约束时,也可以进行垂直方向偏差的调整。

  • 上一篇我们也讲到了相对其它View的约束。相对其它View约束的调整其实与父View约束的情况一样,也可以任意进行水平和垂直方向偏差调整。

总结

Bias调整在很多时候非常方便,我们在保持整体布局间接的同时,也能实现传统布局难以实现的界面。
下一篇:Android开发 - 使用ConstraintLayout(六)链条(Chains)我们介绍链条(Chains)的使用。

本文地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

上一篇我们介绍了编辑器的基本使用,本文我们介绍创建基本的约束

“约束”表示View之间的位置关系。当我们在ConstraintLayout布局中创建View时,如果我们没有添加任何约束,虽然在设计视图我们可以拖动它们到任意位置,但是运行后都会在左上角的原点位置,同时代码中也会给出警告:

-w589

所以我们必须确保我们的View都被添加了适当的约束。
我们常用的约束通常有两种,一种是相对父View的约束,一种是相对其它View的约束。

相对父布局的约束

创建相对布局的约束时比较简单,将对象方位的锚点拖动到对应方位的布局上即可。

-w590

这里我将这个按钮放到左上角,距父布局的左方和上方为50dp,在红色方框处可以调整具体的数值。

相对其它布局的约束

创建相对其它View的布局的形式就比较多样化了,结合相对父布局的约束,可以实现出非常复杂的界面。

当创建一个相对其它View的约束时,将锚点拖动到其它View上面的锚点即可。

-w606

创建父View约束时将锚点拖到父View边缘即可,创建与其它View约束时是将锚点拖到其它View的锚点上。

我们来举个应用的例子:

比如我们想要一个View相对另一个View居中显示,使用ConstraintLayout来实现就非常简单:

-w288

我们只需要将一个View(上图中的TextView)的左边锚点拖到目标View(上图中的Button)的左侧锚点,右边的锚点拖动到目标View的右侧锚点即可以实现。

总结

以前使用传统布局时基本上不愿意使用设计视图,布局时也基本上靠码代码。但是使用了ConstraintLayout后反而很少使用代码视图了,因为只需拖拖拽拽就可创建约束,实现复杂的布局,效率也相应地大大提升了。
下一篇:Android开发 - 使用ConstraintLayout(五)偏差(Bias)我们介绍偏差(Bias)的使用。

本文地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

从本篇博客开始我们开始介绍如何使用ConstraintLayout。
既然ConstraintLayout叫约束布局,首先我们先介绍什么叫约束(Constraints):

约束(Constraints)

一个约束表示View之间的”布局约束”关系,以及约束的位置,类似RelativeLayout的”相对”概念。

编辑器介绍

在工程中我们新建一个布局activity_main.xml,整个界面如下:

-w1280

这是一个编辑器整体的界面,下面我们来介绍一些常用的功能:

  • 设计/文本视图

-w233

左下角有视图切换的选项,通过这个选项我们可以切换设计/(代码)文本视图,传统在开发的过程中可能觉得设计视图并不常用,但是在使用ConstraintLayout时它确实很强大,反而可能很少用代码视图了。

  • 设计/蓝图

-w702

这里我们可以看到左上角的按钮,这个是用来切换设计界面和蓝图界面的,通常我们需要两个界面都展示,这样我们可以更加清晰地看到各个View的约束。

  • 设备与分辨率适配

-w361

这个功能我们可以切换横竖屏等UI的模式,这个功能也可以使我们方便的进行各种UI模式的适配。

-w509

在这里我们可以预览当前布局在不同的分辨率下的效果,这个功能在适配的时候非常方便,这里提一下下面的Custom选项,这个功能允许我们将当前布局拖拽成任意的大小:

-w536

  • 自动约束

-w700

这个功能允许编辑器自动进行约束,当我们拖拽一个空间到视图中后,编辑器会自动为我们创建一个约束,可能很多时候我们并不需要自动约束,我们可以点击把它关闭。

  • 约束推断

-w700

这个按钮类似PS的魔棒,它的功能是进行约束的推断,如图我拖拽了两个按钮到布局中,一开始并没有任何约束,当我点击了这个按钮后,自动为我生成了如图的约束。

代码视图

我们切换到代码视图:

-w836

此时我们发现界面的哪些约束在代码中也仅仅增加了几行代码,用来表示当前的约束,仔细观察后我们发现其实都是toStartOf,toEndOf,toTopOf…,这些都是指定的当前View与其它View(或Parent)之间的关系,对比一下RelativeLayout,我们很容易就能明白。

如果当前没有任何约束的时候,我们观察代码:

-w826

注意这些tools:xxx,只是表示View在编辑器的绝对位置,是用来我们设计(拖拽)时使用的。如果不添加约束,运行后只是显示在屏幕的左上角,重叠在一起。

总结

本文我们主要讲解了ConstraintLayout编辑器常用功能的使用。这些功能灵活且方便地帮助我们布局页面。下一篇:Android开发 - 使用ConstraintLayout(四)创建基本约束我们将介绍使用基本的约束。

本文地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

介绍

发布时间

ConstraintLayout是在2016的Google I/O大会上发布的,经过这么长时间的更新,现在已经非常稳定。

支持Android 2.3(API 9)+

目前的Android设置几乎没有低于Android4.4(Api 19)的,所以ConstraintLayout可以支持所有的设备。

单独的依赖包

ConstraintLayout并不是Android SDK的一部分,而是单独的依赖包,所以我们不需要担心不同的设备,不同的ROM之间的兼容性问题。

使用ConstraintLayout需要先在SDK Manager下载安装这个依赖包:

-w324

然后加入依赖:

1
implementation 'com.android.support.constraint:constraint-layout:1.1.3'

写本文的时候最新的版本是1.1.3,引入的时候将版本号修改成最新版即可。

原理

ConstraintLayout的实现基于食火鸟算法(Cassowary Algorithm),它是一个高效的约束解决方案。

如果想详细了解这个算法,可以参考这篇论文

-w298

优势

使用ConstraintLayout可以解决上一篇博客中所提到的使用传统布局时存在的种种问题,并且使用起来也非常的简单高效。

从下一篇文章:Android开发 - 使用ConstraintLayout(三)编辑器开始我们正式介绍ConstraintLayout的使用。

本文地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

在传统的Android开发中,页面布局占用了我们很多的开发时间,而且面对复杂页面的时候,传统的一些布局会显得非常复杂,每种布局都有特定的应用场景,我们通常需要各种布局结合起来使用来实现复杂的页面。随着ConstraintLayout的推出,这种现象有了很大的改善,而且它可以实现很多传统布局难以实现的功能。

本系列我们就一起来学习ConstraintLayout的使用,来大幅提高我们的生产力。

传统布局

在ConstraintLayout退出之前,我们经常使用FrameLayout, LinearLayout, RelativeLayout, ***Layout等来布局页面。这些布局当然有其方便的地方,但是其方便地同时也限制了我们的使用场景,在写一些复杂页面的时候就显得力不从心,我们先做一下简单地回顾:

  • FrameLayout:帧布局。最常用并且简单的布局,通常用于错误页面的显示,蒙层的显示等。
  • LinearLayout:线性布局。当我们绘制需要顺序排列的内容时,我们使用使用此布局。
  • RelativeLayout:相对布局。相当于ConstraintLayout的低级版本。顾名思义,可以各个View之间相对地指定位置进行布局。

存在的问题

以上布局并不是完美的,比如我们要布局这个页面:

-w540

我们如果使用传统的布局,可能会导致布局的层级多层嵌套:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<RelativeLayout>
<ImageView/>
<ImageView/>
<RelativeLayout>
<TextView/>
<LinearLayout>
<TextView/>
<RelativeLayout>
<EditText/>
</RelativeLayout>
</LinearLayout>
...
</RelativeLayout>
...
</RelativeLayout>
  • 布局复杂
    使用传统的布局虽然可以实现我们的需求,但是在代码实现中有非常多的嵌套,而且要结合许多布局的特性来实现,这样复杂的布局让我们难以维护。

  • 效率底下
    当布局层级越深的时候,系统的绘制效率越低,当子View.invalidate()的时候,也会导致其父View进行重新绘制。
    又比如这些布局中相对灵活的RelativeLayout,它会被测量至少两次,已确定最终渲染时的位置,也同样会影响效率。

  • 难以创建复杂动画
    Android在属性动画(ObjectAnimator)推出之前,执行动画的原理其实只是在绘制的时候执行,并不是真正的改变了布局,在属性推出之后,虽然确实可以改变其真实的布局属性,但是由于布局特性的约束以及各个View之间的约束,创建复杂的动画也并非易事。

ConstraintLayout登场!

下面我们进入本系列的主角:ConstraintLayout!

使用ConstraintLayout可以解决以上传统布局存在的种种问题,而且Android Studio也提供了强大而且简单易用的编辑器,使用它可以让我们的开发效率大大增加。

下一篇我们将介绍它:Android开发 - 使用ConstraintLayout(二)介绍

如有更多疑问,请参考我的其它Android相关博客:我的博客地址

我的上一篇文章:设置DialogFragment全屏显示 可以设置对话框的内容全屏显示,但是存在在某些机型上顶部的View被状态栏遮住的问题。经过测试,发现了一种解决办法,在DialogFragment的onCreateView()中添加一个布局监听器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
//此处rootView是对话框的顶层View
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int[] location = new int[2];
rootView.getLocationOnScreen(location);
int y = location[1];
if (y == 0) {
//此处的topMarginView是被状态栏覆盖的View
ViewGroup.MarginLayoutParams params
= (ViewGroup.MarginLayoutParams)topMarginView.getLayoutParams();
params.topMargin += BarUtils.getStatusBarHeight();
topMarginView.setLayoutParams(params);
rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
}

这种方法是通过监听对话框内容布局顶层View在屏幕中的位置来解决的,如果顶层View在屏幕中的y位置为0,则表示其已经被状态栏所遮住,然后将被遮住的View向下移动状态栏的高度即可。

这种方式显然不够优雅,如果读者能有更好的方法,欢迎留言。

本文地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

默认的DialogFragment并不是全屏,但有些需求需要我们将对话框设置为全屏(内容全屏),Android并没有提供直接的API,通过其它不同的方法设置全屏在不同的机型上总有一些诡异的问题,经过测试,下面的方法可以实现各个机型的全屏。
测试 SDK Version = 28

覆写Fragment的onStart()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null) {
Window window = dialog.getWindow();
if (window != null) {
int width = ViewGroup.LayoutParams.MATCH_PARENT;
int height = ViewGroup.LayoutParams.MATCH_PARENT;
window.setLayout(width, height);
}
}
}
}

上面的方法可以成功设置Dialog为全屏,但是如果在全屏的Dialog顶部有View的情况下,在某些机型View会被状态栏遮住(比如三星S9),这时需要一些特殊的设置来处理,可以参考我的文章:解决DialogFragment在全屏时View被状态栏遮住的问题

本文原始地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

为了确保数据传输的安全,现在越来越多的应用使用Https的方式来进行数据传输,使用https有很多有点,比如:

  • HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
  • HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。

但是即使使用HTTPS有很多有点,但是购买一个认证的HTTPS证书却价格不菲,增加了初创企业和小团队的开发成本。而且如上面所说,使用HTTPS并非绝对的安全,传输的数据并非没有办法获取到,我的之前一篇博客所使用的方法已成功截取HTTPS的数据传输。有兴趣的可以参考:使用Charles对Android App的https请求进行抓包

为了节约成本,我们可以选择使用自签名的HTTPS。
这种方式我们可以自己生成证书,不需要购买证书,但是使用这种方式的域名如果在浏览器中访问的话,会有一个不安全的标识。但是如果用在客户端上,就不会有这个问题,现在一些应用商店也要求APP上架时需要使用HTTPS的网络请求(比如AppStore),使用这种自签名的HTTPS同样也能过审。

本文我们介绍在Android上使用网络请求框架Retrofit 2来请求自签名的API,关于如何生成HTTPS证书,我们将在另一篇博客中进行说明,敬请期待。或者您可以自行查找相关资料。

配置证书

比如我们生成的SLL证书文件为”api_ssl_debug.cer”,我们将其放到assets目录下。这样在配置Retrofit的时候就可以读取该证书并用于API请求中。

配置Retrofit

本节我们介绍SSL证书在Retrofit中的配置,首先我们要定义两个方法用来读取证书,然后讲该证书用于Retrofit中。

  1. 读取证书内容到KeyStore中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static KeyStore getKeyStore(String fileName) {
KeyStore keyStore = null;
try {
AssetManager assetManager = Utils.getApp().getAssets();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = assetManager.open(fileName);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
Log.d("SslUtils", "ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}

String keyStoreType = KeyStore.getDefaultType();
keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
} catch (Exception e) {
Log.e("SslUtils", "Error during getting keystore", e);
}
return keyStore;
}
  1. 生成SSLContext以便用于Retrofit的配置中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static SSLContext getSslContextForCertificateFile(String fileName) {
try {
KeyStore keyStore = SslUtils.getKeyStore(fileName);
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
String msg = "Error during creating SslContext for certificate from assets";
Log.e("SslUtils", msg, e);
throw new RuntimeException(msg);
}
}
  1. 配置Retrofit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SSLContext sslContext = SslUtils.getSslContextForCertificateFile("api_ssl_debug.cer");

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

OkHttpClient.Builder builder = new OkHttpClient.Builder()
...
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.hostnameVerifier((hostname, session) -> true)
...

mRetrofit = new Retrofit.Builder()
...
build();

以上的省略号为Retrofit的其它配置,可以根据工程需要进行配置。

总结

至此,使用Retrofit就可以进行自签名的HTTPS的网络请求了,当然,服务器端的API配置也需要进行HTTPS的配置,我将在服务端相关的博客进行介绍,还有一点,如果请求的域名指定了端口,要将端口设置为443。如果没有指定端口,请求时也会自动走443端口。

本文原始地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

为了确保数据传输的安全,现在越来越多的应用使用Https的方式来进行数据传输,使用https有很多有点,比如:

  • HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
  • HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。

但是即使使用HTTPS有很多有点,但是购买一个认证的HTTPS证书却价格不菲,增加了初创企业和小团队的开发成本。而且如上面所说,使用HTTPS并非绝对的安全,传输的数据并非没有办法获取到,我的之前一篇博客所使用的方法已成功截取HTTPS的数据传输。有兴趣的可以参考:使用Charles对Android App的https请求进行抓包

为了节约成本,我们可以选择使用自签名的HTTPS。
这种方式我们可以自己生成证书,不需要购买证书,但是使用这种方式的域名如果在浏览器中访问的话,会有一个不安全的标识。但是如果用在客户端上,就不会有这个问题,现在一些应用商店也要求APP上架时需要使用HTTPS的网络请求(比如AppStore),使用这种自签名的HTTPS同样也能过审。

本文我们介绍在Android上使用网络请求框架Retrofit 2来请求自签名的API,关于如何生成HTTPS证书,我们将在另一篇博客中进行说明,敬请期待。或者您可以自行查找相关资料。

配置证书

比如我们生成的SLL证书文件为”api_ssl_debug.cer”,我们将其放到assets目录下。这样在配置Retrofit的时候就可以读取该证书并用于API请求中。

配置Retrofit

本节我们介绍SSL证书在Retrofit中的配置,首先我们要定义两个方法用来读取证书,然后讲该证书用于Retrofit中。

  1. 读取证书内容到KeyStore中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static KeyStore getKeyStore(String fileName) {
KeyStore keyStore = null;
try {
AssetManager assetManager = Utils.getApp().getAssets();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = assetManager.open(fileName);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
Log.d("SslUtils", "ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}

String keyStoreType = KeyStore.getDefaultType();
keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
} catch (Exception e) {
Log.e("SslUtils", "Error during getting keystore", e);
}
return keyStore;
}
  1. 生成SSLContext以便用于Retrofit的配置中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static SSLContext getSslContextForCertificateFile(String fileName) {
try {
KeyStore keyStore = SslUtils.getKeyStore(fileName);
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext;
} catch (Exception e) {
String msg = "Error during creating SslContext for certificate from assets";
Log.e("SslUtils", msg, e);
throw new RuntimeException(msg);
}
}
  1. 配置Retrofit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SSLContext sslContext = SslUtils.getSslContextForCertificateFile("api_ssl_debug.cer");

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

OkHttpClient.Builder builder = new OkHttpClient.Builder()
...
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.hostnameVerifier((hostname, session) -> true)
...

mRetrofit = new Retrofit.Builder()
...
build();

以上的省略号为Retrofit的其它配置,可以根据工程需要进行配置。

总结

至此,使用Retrofit就可以进行自签名的HTTPS的网络请求了,当然,服务器端的API配置也需要进行HTTPS的配置,我将在服务端相关的博客进行介绍,还有一点,如果请求的域名指定了端口,要将端口设置为443。如果没有指定端口,请求时也会自动走443端口。

本文原始地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址

在Android6.0以后,很多权限需要动态申请,只有在用户点同意后,我们才能使用对应API,因此,正确申请权限就显得很重要。

常用方式

通常我们使用这种方式来判断权限状态:

1
2
3
4
5
private static boolean isGranted(final String permission) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|| PackageManager.PERMISSION_GRANTED
== ContextCompat.checkSelfPermission(Utils.getApp(), permission);
}

但是用这种方式我们会碰到一个比较棘手的情况,当用户同意了某个权限后,然后用户又在手机的系统设置的应用权限中关闭了这个权限,此时我们并不能正确获取到正确的权限状态。

解决办法

有可能Android团队意识到了这个问题,所以他们发布了一个新的权限检查类:android.support.v4.content.PermissionChecker;

用这个类进行权限的检查就会正确返回,所以我们把权限校验的方法修改如下:

1
2
3
4
5
6
7
import android.support.v4.content.PermissionChecker;

private static boolean isGranted(final String permission) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|| PackageManager.PERMISSION_GRANTED
== PermissionChecker.checkCallingOrSelfPermission(Utils.getApp(), permission);
}

这样就会解决手机设置与用户授权不一致的问题。

本文原始地址,如有更多疑问,请参考我的其它Android相关博客:我的博客地址