先简单快速的介绍约束者布局实现屏幕适配。
为了实现屏幕适配,我们需要解决两个问题:
一、指定控件在父控件的百分比位置。
二、指定控件占父控件的百分比空间。
我们先解决第一个问题,指定控件水平方向在父控件的百分之四十,垂直方向在父控件百分之二十的位置,布局文件代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="test.constraintlayouttest.MainActivity">
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.4"
android:id="@+id/vertical_line_1"
/>
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.2"
android:id="@+id/horizontal_line_1"
/>
<ImageView
android:id="@+id/iv_top"
app:layout_constraintLeft_toLeftOf="@id/vertical_line_1"
app:layout_constraintTop_toTopOf="@id/horizontal_line_1"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#ff0000"
/>
</android.support.constraint.ConstraintLayout>
效果:
然后第二个问题,指定控件占父控件的百分比空间,宽度20%,高度60%。
我们分别给控件的左右上下四个方向都设置约束,左右约束的百分比差值就是控件的宽度,高度同理。
布局文件代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="test.constraintlayouttest.MainActivity">
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.4"
android:id="@+id/vertical_line_1"
/>
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.6"
android:id="@+id/vertical_line_2"
/>
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.2"
android:id="@+id/horizontal_line_1"
/>
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.8"
android:id="@+id/horizontal_line_2"
/>
<ImageView
android:id="@+id/iv_top"
app:layout_constraintLeft_toLeftOf="@id/vertical_line_1"
app:layout_constraintRight_toRightOf="@id/vertical_line_2"
app:layout_constraintTop_toTopOf="@id/horizontal_line_1"
app:layout_constraintBottom_toBottomOf="@id/horizontal_line_2"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#ff0000"
/>
</android.support.constraint.ConstraintLayout>
不同手机上的效果:
这样,约束者实现屏幕适配就已经完成了。感兴趣的读者可以接下来查看详细一些的屏幕适配方案。
目前市场上安卓设备多种多样,在安卓开发中,不可避免的要进行一些屏幕适配,使得我们的程序尽可能多的在各种屏幕的设备上都能有不错的展现效果。但是由于安卓设备分辨率,尺寸,像素密度等种类非常之多,所以屏幕适配又是一件极繁琐的事情,所以通常我们在做屏幕适配的时候,能够适配主流的机型就可以了。
现在网络上也有许多屏幕适配相关的文章,以下是比较具有参考价值的文章,感兴趣的朋友可以去阅读一下。
安卓屏幕适配方案
Android 百分比布局库(percent-support-lib) 解析与扩展
Android AutoLayout全新的适配方式 堪称适配终结者
Android屏幕适配全攻略(最权威的官方适配指导)
android 常见分辨率(mdpi、hdpi 、xhdpi、xxhdpi )及屏幕适配注意事项
为了了解屏幕适配的原理,这里先简单介绍几种屏幕适配的方式,详情请看上面的文章,先从简单的说起,屏幕适配的目的就是为了在不同的屏幕下展现相同的效果,所以在安卓开发中,我们在编写XML布局文件的时候,很少用px作为单位、或者绝对布局的方式来布局,因为这样做会把布局写死,但是不管怎样,一个控件最终在确定大小的时候还是会以px作为单位来确定大小。
dp适配。dp是我们写的比较多的一个单位,也是最常用的适配方式,它可以根据当前屏幕的像素密度在代码中等比例的转换为px单位,最终确定大小。但是它的局限性也很明显,只能适配设备独立像素数量相同的设备,也就是dp数量相同的设备。独立设备像素数量=分辨率*160/dpi,比如说dpi为320,分辨率为720*1280的手机,那么设备独立像素数量就为360dp*640dp,dpi为480,分辨率为1080*1920的手机,独立像素数量也为360dp*640dp。所以这两个手机的显示效果相同,但是如果dpi为420,分辨率还是1080*1920的手机,那么效果就不一样了。现在的手机,有dpi相同而分辨率不同的,也有分辨率相同而dpi不同的,dp适配,只适用于这两者之间的比例相同的手机。
dimen适配。针对不同的分辨率手机,编写多套values文件夹,针对该分辨率下的dimen值,将dimen值转换为对应的px值,类似于以下写法:
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">1.5px</dimen>
<dimen name="x2">3px</dimen>
<dimen name="x3">4.5px</dimen>
<dimen name="x720">1080px</dimen>
</resources>
在使用时通过@dimen/x1的形式编写布局。我们事先编写好的values-1280x720、values-1920x1080等文件夹就相当于Java的重载方法一样是values的重载文件夹,安卓系统在加载资源文件时会根据手机当前的分辨率去寻找对应资源文件夹下的资源,从而找到在这个分辨率下具体的dimen值对应的px值是多少,不同分辨率的手机会进入不同的values文件夹下寻找,而不同的values文件夹下同一个dimen值对应的px值是不一样的,所以就实现了屏幕适配的效果,但是这种方式局限也很明显,就是市面上不同分辨率的手机太多了,没办法事先把所有的分辨率都写一套对应的values文件,只能够适配主流的一些分辨率。顺便说一下,国际化也是这样做的,根据不同的语言,加载不同values资源。
百分比布局适配。在原有的相对布局和帧布局的基础上增加了百分比的属性,用法上并不难,原理就是根据手机屏幕的宽高乘以一定的百分比来确定大小。
AutoLayout适配。以一套既定的分辨率为基准,在布局中直接以这个基准分辨率来编写布局,最终程序运行时会以手机实际的分辨率和基准分辨率的比例和布局中的大小来确定最终的大小。相当于将布局中的数值等比例转换到实际分辨率下的数值大小。
多布局。针对不同的分辨率的设备,单独编写一套布局,原理和dimen适配一样,需要编写多个values文件夹,但是工作量更大,甚至可以说是繁琐,不过好处是适配得更精细,不仅能让不同的手机展示效果一样,也能够不一样。同一个页面在不同的设备上可以有多个版本,并且都是专门针对这个屏幕而设计的最适合这个屏幕的布局,就像是开了VIP会员一样,拥有私人定制的专属布局,每个会员还都不一样,并且还都是最适合这个会员的布局,别人都是吃大锅饭,给VIP会员开小灶,关键还是给每个会员单独开小灶,炒不同的菜。但是这种布局方式缺点也很明显,编写工作量大,而且会增加APK的大小,并且如果布局中控件功能或者数量、类型不一致的话,在代码中也要进行多种繁琐的判断,对应于不同布局执行不同的逻辑功能,一个页面干了多个页面的事。
好了,以上几种屏幕适配方式在上面的链接中都可以深入学习,下面介绍另一种屏幕适配方式,约束者布局适配。
约束者布局是去年谷歌IO大会推出来的,目前也是作为AndroidStudio的默认布局方式,可以看出来谷歌将要大力推行这一个布局,它的功能也非常强大,结合了相对布局和线性布局的优点,支持可视化拖拽的布局方式,不过我个人还是比较喜欢手打代码实现布局。
以下通过一个小案例来介绍屏幕适配。
准备素材如下张图
这两张图是在720x1280的图中切出来的,图中的尺寸已经标注好了,我们要实现的效果是使用第一张图作为背景图,第二张图使用ImageView显示,要时第二张图刚好嵌入背景图中的圆形区域。先创建一个项目,为了显示效果更加直观,我们将应用设置为全屏,在res/values/styles.xml文件中将应用主题样式AppTheme加入以下两行代码:
<item name="android:windowFullscreen">true</item>
<item name="windowNoTitle">true</item>
我们使用约束者布局作为最外层的布局,先使用dp适配,根据我们上面所说的,720*1280,dip为320的手机,一个dp对应(320dpi/160dpi=2)2个px,已知圆形区域距离左边和上边的边缘距离分别为30px和40px,那么我们就将ImageView的layout_marginLeft和layout_marginTop分别设置为对应的15dp和20dp,并且要给对应的方向添加约束,margin才会生效,添加的约束如下:
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
ImageView的宽高为107px/2=53.5dp
编写MainActivity的布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
android:background="@drawable/bg"
tools:context="test.constraintlayouttest.MainActivity">
<ImageView
android:id="@+id/iv_top"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="15dp"
android:layout_marginTop="30dp"
android:layout_width="53.5dp"
android:layout_height="53.5dp"
android:background="@drawable/top_icon"
/>
</android.support.constraint.ConstraintLayout>
然后分别运行到3个不同的手机上效果如下:
可以看出,3个手机上的显示效果是不一样的,这时候dp适配就不是那么理想了。
然后接下来,我们使用约束者布局适配:
1.先进行一波计算:
30/720=0.0416…(约0.042),也就是说ImageView的左边和父控件的左边距离是父控件宽度的0.042。
(30+107)/720=0.1902…(约0.190),也就是说ImageView的右边和父控件的左边距离是父控件宽度的0.190。
然后,我们可以新建两条竖直的参考线,位置就是父控件的0.042和0.190,修改布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
android:background="@drawable/bg"
tools:context="test.constraintlayouttest.MainActivity">
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.042"
android:id="@+id/vertical_line_1"
/>
<android.support.constraint.Guideline
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.190"
android:id="@+id/vertical_line_2"
/>
<ImageView
android:id="@+id/iv_top"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="15dp"
android:layout_marginTop="20dp"
android:layout_width="53.5dp"
android:layout_height="53.5dp"
android:background="@drawable/top_icon"
/>
</android.support.constraint.ConstraintLayout>
这里只是在约束者布局中加了两条竖直参考线,参考线的宽高并没有什么意义,反正这个控件默认不占位置,也不会显示出来,给0dp和match_parent的效果是一样的,orientation是参考线的方向,这里设置为竖直方向,layout_constraintGuide_percent属性设置这条参考线在父控件的百分比距离,比如0.344代表在父控件34.4%的位置,如果是竖直的参考线就是距离父控件的左边34.4%,如果是水平的参考线就是距离父控件上方34.4%,然后给参考线加一个id方便使用。
然后修改布局文件,将ImageView的左边约束设置为参考线1,右边约束设置参考线2,宽度设置为0dp,并且去掉左边的margin,代码如下:
<ImageView
android:id="@+id/iv_top"
app:layout_constraintLeft_toLeftOf="@id/vertical_line_1"
app:layout_constraintRight_toRightOf="@id/vertical_line_2"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"
android:layout_width="0dp"
android:layout_height="53.5dp"
android:background="@drawable/top_icon"
/>
然后分别运行到3个手机上,效果如下:
可以看出来,在3个手机上,控件宽度所占父控件宽度的比例已经一致了,宽度适配完毕。接下来按照同样的方式实现高度的适配,
计算参考线位置:
40/1280=0.03125
(40+107)/1280=0.11484375
最后布局文件代码如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
android:background="@drawable/bg"
tools:context="test.constraintlayouttest.MainActivity">
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.042"
android:id="@+id/vertical_line_1"
/>
<android.support.constraint.Guideline
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.190"
android:id="@+id/vertical_line_2"
/>
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.031"
android:id="@+id/horizontal_line_1"
/>
<android.support.constraint.Guideline
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.115"
android:id="@+id/horizontal_line_2"
/>
<ImageView
android:id="@+id/iv_top"
app:layout_constraintLeft_toLeftOf="@id/vertical_line_1"
app:layout_constraintRight_toRightOf="@id/vertical_line_2"
app:layout_constraintTop_toTopOf="@id/horizontal_line_1"
app:layout_constraintBottom_toBottomOf="@id/horizontal_line_2"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/top_icon"
/>
</android.support.constraint.ConstraintLayout>
同样运行到3个手机上,效果如下:
这样就使用约束者布局实现了屏幕适配。不过虽然适配完成了,但是可以看到,不同的手机展现效果还是有点不一样,因为圆形的头像变形了。如果要保持原样,可以这样做:
不要将控件的4边约束都设置死,只设置3边就行了,同时设置控件的宽高比,修改ImgaView的代码如下:
<ImageView
android:id="@+id/iv_top"
app:layout_constraintLeft_toLeftOf="@id/vertical_line_1"
app:layout_constraintRight_toRightOf="@id/vertical_line_2"
app:layout_constraintTop_toTopOf="@id/horizontal_line_1"
app:layout_constraintDimensionRatio="1:1"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/top_icon"
/>
去掉了下方的约束,同时设置layout_constraintDimensionRatio属性为“1:1”,也就是宽高比为1:1。效果如下:
由于宽度是按照屏幕百分比计算出来的,而高度又是根据宽高比计算出来的,所以可以说高度也是按百分比计算的,这样就用约束者布局实现了屏幕适配了。
下面还有一些适配的问题,但是和约束者布局关系不大。上面案例中控件是圆了,但是效果还是不完美,主要是因为背景图被拉伸了,在某些手机上的那个圆形区域被缩放成了椭圆,背景都变形了,控件再保持原样,看上去也不算完美嵌入。如果想要背景图的那个区域也保持圆形,就需要修改背景图了,我们可以把背景做成.9图,以达到自适应缩放的目的。关于.9图的制作和用法在上面推荐的文章中也有介绍,我制作好后的.9图如下:
同时布局文件也要修改如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
android:background="@drawable/bg"
tools:context="test.constraintlayouttest.MainActivity">
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_begin="15dp"
android:id="@+id/vertical_line_1"
/>
<android.support.constraint.Guideline
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintGuide_begin="20dp"
android:id="@+id/horizontal_line_1"
/>
<ImageView
android:id="@+id/iv_top"
app:layout_constraintTop_toTopOf="@id/horizontal_line_1"
app:layout_constraintLeft_toLeftOf="@id/vertical_line_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/top_icon"
/>
</android.support.constraint.ConstraintLayout>
app:layout_constraintGuide_begin的作用是指定参考线距离父控件左边(垂直参考线)或上边(水平参考线)的距离,同样的还有app:layout_constraintGuide_end指定参考线距离父控件右边(垂直参考线)或下边(水平参考线)的距离。其实这两个属性类似于margin,所以这里如果把它们改成对应的margin也是没问题的,我们将参考线的距离写出固定的dp,然后ImageView设置为包裹内容(注意图片是720x1280的,所以要放在drawable-xhdpi文件夹下),这样由于背景是.9图,拉伸缩放时不影响背景中的圆形区域,最终效果如下:
这样应该来说是比较满意的效果了,最后感谢以上博文的作者无私的分享!