天天看点

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

一,前期基础知识储备

大屏,全面屏时代到来,屏幕适配从未显得如此重要。

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

如图,一个简单的付费界面,如果完全按照上图的标注来写,那么在传统16:9的手机上表现良好,但在大屏手机上表现就差强人意,无论使用相对布局还是线性布局,都无法做到完整的适配。在正式项目中,就采用了一个笨办法:根据分辨率比例,使用不同的布局:

View view;
        if (mScreenRatio > 1.8) {
            view = View.inflate(this, R.layout.dialog_prime_permission_max, null);
        } else {
            view = View.inflate(this, R.layout.dialog_prime_permission, null);
        }
           

UI做两套图,不同分辨率,做到适配。但是不香,而且不灵活,甚至会有其他问题。所以找寻最优解。

1)support包下百分比布局PercentRelativeLayout + PercentFrameLayout;

implementation 'com.android.support:percent:28.0.0'
           

用宽高百分比确认View的大小和边距Margin值。但是已被谷歌弃用,所以不建议使用。

<ImageView
        android:id="@+id/img"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_heightPercent="57.19%"
        app:layout_marginTopPercent="3.13%"
        app:layout_widthPercent="57.22%" />
           

2)support包下约束布局ConstraintLayout,真香;

ConstraintLayout, 即约束布局, 在2016年由Google I/O推出. 从支持力度而言, 将成为主流布局样式, 完全代替其他布局, 减少布局的层级, 优化渲染性能.。在新版Android Studio中, ConstraintLayout已替代RelativeLayout, 成为HelloWorld项目的默认布局。ConstraintLayout作为非绑定(Unbundled)的支持库, 命名空间是app

:

, 即来源于本地的包命名空间. 最新版本是1.1.3(2019.10.16)。

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

“嘴上说不学,身体却很诚实”

ConstraintLayout具有以下优势:

  1. 较高的性能优势。

    布局嵌套层次越高,性能开销越大。而使用ConstraintLayout,经常就一层嵌套就搞定了,所以其性能要好很多。

    详细的性能分析可参见:解析ConstraintLayout的性能优势

  2. 完美的屏幕适配

    ConstraintLayout的大小、距离都可以使用比例来设置,所以其适配性更好。

  3. 书写简单
  4. 可视化编辑。

    ConstraintLayout也有十分方便完善的可视化编辑器,不用写xml也基本上能实现大部分功能。但个人还是比较喜欢写xml,所以本篇文章主要介绍如何使用代码控制。如果想看如何使用可视化编辑器,可以参考郭霖大神的这篇文章

二,上代码,具体实现

添加依赖:

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

定位一个View就是三大元素:位子,Margin,大小;下面就来使用清楚这三个方向。

1)掌握基础用法;

① 属性定位View位置

  • 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
  • layout_constraintStart_toEndOf
  • layout_constraintStart_toStartOf
  • layout_constraintEnd_toStartOf
  • layout_constraintEnd_toEndOf

ConstraintLayout约束布局的含义: 根据布局中的其他元素或视图, 确定View在屏幕中的位置, 受到三类约束, 即其他视图, 父容器(parent), 基准线(Guideline)。

layout_constraint[本源位置]_[目标位置]="[目标ID]"
           

例如:

app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
           

约束当前View的底部至目标View的底部, 目标View是constraintLayout,即,把当前View的底部对齐到constraintLayout的底部。

② 可视化编辑器定位

推荐大腿郭神文章《Android新特性介绍,ConstraintLayout完全解析》

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

大神出品,必属精品,足够清晰,包看包会。

2)掌握比例(偏移量 / 边距);

ConstraintLayout除了指定约束, 还支持设置比例. Center按钮的全部边界与ConstraintLayout

(父容器)

边界对齐, 则为居中. 同时还可以设置水平与竖直的比例, 如Bias按钮, 在对齐父容器后, 设置水平与竖直的比例均为0.25, 表示左侧与右侧比例是1:4, 上部与下部的比例是1:4。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Center"
        app:layout_constraintEnd_toEndOf="@id/constraintLayout"
        app:layout_constraintStart_toStartOf="@id/constraintLayout"
        app:layout_constraintTop_toTopOf="@+id/constraintLayout"
        app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bias"
        app:layout_constraintEnd_toEndOf="@id/constraintLayout"
        app:layout_constraintStart_toStartOf="@id/constraintLayout"
        app:layout_constraintTop_toTopOf="@+id/constraintLayout"
        app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
        app:layout_constraintHorizontal_bias="0.25"
        app:layout_constraintVertical_bias="0.25"/>

</android.support.constraint.ConstraintLayout>
           
多分辨率适配终结者之约束布局ConstraintLayout,我佛了

注意,要同时为View加上同一方向上的两个约束,此时使用偏移才会生效。利用此特性,在做适配的时候可以将UI标注好的上下边距或者左右边距转换为百分比例的形式,然后设置为偏移量,实测非常好用。

3)掌握引导线(Guidline);

ConstraintLayout除了与视图约束以外, 还支持与

引导线(

Guidline

)

约束. 如, 设置

竖直引导线(

Guidline

)

距离左侧

72dp

. 两个按钮的左侧都与引导线约束, 上下使用比例方式排列, 一个是0.25, 一个是0.75。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.constraint.Guideline
        android:id="@+id/guideLine"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="72dp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Guide Up"
        app:layout_constraintStart_toStartOf="@id/guideLine"
        app:layout_constraintTop_toTopOf="@+id/constraintLayout"
        app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
        app:layout_constraintVertical_bias="0.25"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Guide Down"
        app:layout_constraintStart_toStartOf="@id/guideLine"
        app:layout_constraintTop_toTopOf="@+id/constraintLayout"
        app:layout_constraintBottom_toBottomOf="@+id/constraintLayout"
        app:layout_constraintVertical_bias="0.75"/>

</android.support.constraint.ConstraintLayout>
           
多分辨率适配终结者之约束布局ConstraintLayout,我佛了

引导线非常重要,它可以帮助确定View的位置,还可以据此帮助确定View的大小,用四根线划分View区域,View在此填充,用在布局中的关键元素,十分好用。

4)大小(视图尺寸);

ConstraintLayout也支持自动填充宽高, 把宽高设置为

0dp

会根据位置自动填充. 如, Large按钮, 左侧与Small按钮的左侧对齐, 右侧与ConstraintLayout

(父控件)

的右侧对齐, 宽度设置为

0dp

, 则会填充全部空位。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    android:id="@+id/constraintLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/small"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Small"
        app:layout_constraintStart_toStartOf="@id/constraintLayout"
        app:layout_constraintTop_toTopOf="@id/constraintLayout"/>

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Large"
        app:layout_constraintBottom_toBottomOf="@id/constraintLayout"
        app:layout_constraintEnd_toEndOf="@id/constraintLayout"
        app:layout_constraintStart_toEndOf="@id/small"
        app:layout_constraintTop_toTopOf="@id/constraintLayout"/>

</android.support.constraint.ConstraintLayout>
           
多分辨率适配终结者之约束布局ConstraintLayout,我佛了

View的大小是重中之重,注意约束布局不鼓励使用match_parent,相反使用wrap_content和0dp非常多,使用0dp和约束规则,很容易实现填充(match_parent)的效果。

三,上代码,具体实现付费界面

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

这是使用约束布局得到的结果,用不同分辨率的手机实测表现良好。

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

使用7根引导线,去布置View的位置和控制View的大小,然后根据上下Margin值,去计算竖直方向的偏移量,设置好比例,就可以定下竖直方向的显示问题。具体分析如下:

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

布局如下:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#175700">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:text="Join Premium"
        android:textSize="24sp"
        app:layout_constraintBottom_toTopOf="@+id/viewPager"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.56" />

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#A25151"
        app:layout_constraintBottom_toTopOf="@+id/guideline9"
        app:layout_constraintEnd_toStartOf="@+id/guideline7"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="@+id/guideline8"
        app:layout_constraintVertical_bias="0.0" />

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_marginEnd="2dp"
        android:src="@drawable/ic_prime_point_select"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline9" />

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_marginEnd="4dp"
        android:src="@drawable/ic_prime_point_unselect"
        app:layout_constraintEnd_toStartOf="@+id/imageView2"
        app:layout_constraintTop_toTopOf="@+id/imageView2" />

    <ImageView
        android:id="@+id/imageView4"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_marginStart="2dp"
        android:src="@drawable/ic_prime_point_unselect"
        app:layout_constraintBottom_toTopOf="@+id/guideline3"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline9" />

    <ImageView
        android:id="@+id/imageView5"
        android:layout_width="10dp"
        android:layout_height="10dp"
        android:layout_marginStart="4dp"
        android:src="@drawable/ic_prime_point_unselect"
        app:layout_constraintStart_toEndOf="@+id/imageView4"
        app:layout_constraintTop_toTopOf="@+id/imageView4" />

    <RelativeLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="@+id/guideline4"
        app:layout_constraintEnd_toEndOf="@+id/guideline7"
        app:layout_constraintStart_toStartOf="@+id/guideline6"
        app:layout_constraintTop_toTopOf="@+id/guideline3">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true">

            <ImageView
                android:id="@+id/imageView6"
                android:layout_width="4dp"
                android:layout_height="4dp"
                android:layout_alignParentStart="true"
                android:layout_centerVertical="true"
                android:src="@drawable/ic_dot" />

            <TextView
                android:id="@+id/textView3"
                android:layout_width="wrap_content"
                android:layout_height="23dp"
                android:layout_alignParentTop="true"
                android:layout_centerVertical="true"
                android:layout_marginStart="14dp"
                android:layout_toRightOf="@id/imageView6"
                android:text="60+ film filters"
                android:textSize="15sp" />
        </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true">

            <ImageView
                android:id="@+id/imageView8"
                android:layout_width="4dp"
                android:layout_height="4dp"
                android:layout_alignParentStart="true"
                android:layout_centerVertical="true"
                android:src="@drawable/ic_dot" />

            <TextView
                android:id="@+id/textView4"
                android:layout_width="wrap_content"
                android:layout_height="23dp"
                android:layout_centerVertical="true"
                android:layout_marginStart="14dp"
                android:layout_toRightOf="@id/imageView8"
                android:text="No Ads"
                android:textSize="15sp" />
        </RelativeLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="23dp"
            android:layout_alignParentBottom="true">

            <ImageView
                android:id="@+id/imageView9"
                android:layout_width="4dp"
                android:layout_height="4dp"
                android:layout_alignParentStart="true"
                android:layout_centerVertical="true"
                android:src="@drawable/ic_dot" />

            <TextView
                android:id="@+id/textView5"
                android:layout_width="wrap_content"
                android:layout_height="23dp"
                android:layout_alignParentBottom="true"
                android:layout_marginStart="14dp"
                android:layout_toEndOf="@id/imageView9"
                android:text="Import Photos"
                android:textSize="15sp" />
        </RelativeLayout>

    </RelativeLayout>

    <TextView
        android:id="@+id/button"
        android:layout_width="140dp"
        android:layout_height="44dp"
        android:layout_marginEnd="5dp"
        android:background="@drawable/dialog_buy"
        android:gravity="center"
        android:text="$2.9/ Year"
        android:textSize="15sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline4"
        app:layout_constraintVertical_bias="0.562" />

    <TextView
        android:id="@+id/button2"
        android:layout_width="140dp"
        android:layout_height="44dp"
        android:layout_marginStart="5dp"
        android:background="@drawable/dialog_buy"
        android:gravity="center"
        android:text="$6.9 One time pay"
        android:textSize="15sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="@+id/guideline4"
        app:layout_constraintVertical_bias="0.562" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.213" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.787" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.68105066" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.82216144" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.111" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.62106705" />
</android.support.constraint.ConstraintLayout>
           

总结一下:

① 使用约束布局去做适配最关键的就是,对于边距值Margin的处理,Ui标注的都是dp,使用时,注意计算为比例,然后作为比例偏移量添加到约束布局中;

② 使用约束布局尽量将布局写活,不使用固定大小或者不鼓励使用match_parent属性,多使用0dp+约束规则实现;

③ 可在约束布局中插入其他ViewGroup,灵活为上。

多分辨率适配终结者之约束布局ConstraintLayout,我佛了

参考文章

《Android ConstraintLayout百分比布局-适配终结者(基本适配所有机型)》

《约束布局ConstraintLayout看这一篇就够了》

继续阅读