IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 动态添加View为什么margin会失效 -> 正文阅读

[移动开发]动态添加View为什么margin会失效

目录

一.场景

二.如何动态插入View (源码:SDK30)

三.代码实现

第一种实现方法

第一种,设置root==mainRoot,attchToRoot==false

第二种,设置root==null,attchToRoot==false

源码分析(root与attachToRoot区别)

四.总结


一.场景

例如现在我们需要在布局中添加一个如下面的一个跑步提示,我们有多少种方法实现?

  • 1.直接在想要插入的xml布局中写

  • 2.直接用代码生成布局,在代码中动态的插入(addView)

  • 3.单独写一个xml布局,在代码中动态的插入(addView)

    最终,选择了第三种办法,

    • 一是避免页面的复杂度,

    • 二是降低xml的加载显示速度。

    • 三是相比与第二种,第二种的效率明显会比第三种高(因为xml转换成View需要解析),但是代码生成的布局较难维护和可观性差,还是选择第三种方式。

二.如何动态插入View (源码:SDK30)

在讲实现前,我们先看下如何动态插入View,因为这里是通过xml文件来写的布局,所以就涉及到一个xml转换成View的问题,可以通过LayoutInflater类来转换
主要看inflate方法,他有三个参数:
第一个(resource):布局代码,例如R.layout.xx
第二个(root):需要插入的布局的父布局
第三个(attachToRoot):是否需要附加到root上

`public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)`

关于参数的具体逻辑后面再做分析,这里先看下如何使用

三.代码实现

第一种实现方法

1.需要插入的根布局(注意是ConstraintLayout根布局)

//R.layout.activity_main
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_root"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="#ff0000"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

布局代码和UI效果图:
注意margin,上下左右各留出10dp的距离

核心插入代码:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    //mainRoot就是activity_main的根布局
    val mainRoot = findViewById<ConstraintLayout>(R.id.main_root)
    //把R.layout.layout_run_view布局插入到mainRoot中
    LayoutInflater.from(this).inflate(R.layout.layout_run_view, mainRoot,true)
}

看下运行的效果:
可以很明显的看到,左右间隔是有了,但上间隔却没有(下间隔不好表示就不说了,不过原理跟上间隔一样)

到这里,我们再把根布局换成FrameLayout试试(注意代码记得修改),再看下效果图

很明显,换成FrameLayout上间隔就出来,那这到底是什么导致的呢?
原因很简单,这是由于根布局自身导致的。因为ConstraintLayout与其他布局间隔设置(FrameLayoutRelativeLayoutLinearLayout)的区别就是,ConstraintLayout需要与向上留出间隔,那么你就需要指定一个他处在的位置,例如` app:layout_constraintTop_toTopOf="parent"`
解决方法也有,如下:

第一种,设置root==mainRoot,attchToRoot==false

//解析R.layout.layout_run_view,返回View对象,
//attachToRoot == true,返回的就是root
//attachToRoot == false,返回的就是设置LayoutParams的View
val mRunTipView = LayoutInflater.from(this).inflate(R.layout.layout_run_view, mainRoot, false)
val layoutParams = mRunTipView.layoutParams as ConstraintLayout.LayoutParams
layoutParams.apply {
    topToTop = ConstraintLayout.LayoutParams.PARENT_ID
}
//把View添加到布局中
mainRoot?.addView(mRunTipView, layoutParams)

第二种,设置root==null,attchToRoot==false

与第一种的区别是,此时inflate返回的是没有设置LayoutParams的View,就需要我们自己生成一个ConstraintLayout.LayoutParams传进去,如下代码:

val mRunTipView =
        LayoutInflater.from(this).inflate(R.layout.layout_run_view1, null,false)
//创建LayoutParams
val layoutParams = ConstraintLayout.LayoutParams(
    ConstraintLayout.LayoutParams.MATCH_PARENT,
    ConstraintLayout.LayoutParams.WRAP_CONTENT
).apply {
    topToTop = ConstraintLayout.LayoutParams.PARENT_ID
}

//把View添加到布局中
mainRoot?.addView(mRunTipView, layoutParams)

运行代码,看下效果。


是的,没有看错,什么都没有。关于原因是因为我们设置的LayoutParams的高是WRAP_CONTENT,而他布局里又没有子控件自然就没有高度。

解决方法有两个:
1.修改LayoutParams,固定高度如下:

val layoutParams = ConstraintLayout.LayoutParams(
    ConstraintLayout.LayoutParams.MATCH_PARENT,
    100
)

2.在插入布局中增加子控件或者padding
修改下R.layout.layout_run_view的布局,代码如下:
仅仅是把layout_height变成wrap_content,增加paddingBottom="50dp"(你也可以在布局里放个50dp的View)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="50dp"
    android:layout_margin="50dp"
    android:background="#EE000000">
<!--中间代码省略-->
</RelativeLayout>

再看下效果。可以看到黑色块出来了,但是左右上下间距呢?你都没有设置当然就没有了



注意:
1.因为attachToRoot == false,所以inflate返回的是传入布局(R.layout.layout_run_view)的View,而不是mainRoot
2.如果attachToRoot == true,返回的就是mainRoot,也就是我们一开始的插入代码

源码分析(root与attachToRoot区别)

关于inflate方法的参数传值导致不同的结果,我们可以深入源码看一下(代码经过精简,主要梳理重要逻辑):

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    //1.尝试使用预编译生成View,但是这个版本不支持预编译布局。
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    //通过xml解析resource布局
    XmlResourceParser parser = res.getLayout(resource);
    try {
        //这里才是真正的创建View
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

可以看下tryInflatePrecompiled的方法,很明显,因为mUseCompiledView的值一直都是false,所以这个方法暂时没用到的。可以看一下mUseCompiledView的赋值

private @Nullable View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
    boolean attachToRoot) {
    if (!mUseCompiledView) {
        return null;
    }
}

跟踪一下mUseCompiledView的设置路径,可以看到源码的提示
“Precompiled layouts are not supported in this release.”此版本不支持预编译布局

private void initPrecompiledViews() {
    // Precompiled layouts are not supported in this release.
    boolean enabled = false;
    initPrecompiledViews(enabled);
}

回到主流程继续查看inflate方法

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        View result = root;

        //判断布局是不是 merge
        if (TAG_MERGE.equals(name)) {
            if (root == null || !attachToRoot) {
                throw new InflateException("<merge /> can be used only with a valid "
                        + "ViewGroup root and attachToRoot=true");
            }
        } else {
            // 创建布局(R.layout.layout_run_view)
            final View temp = createViewFromTag(root, name, inflaterContext, attrs);

            ViewGroup.LayoutParams params = null;
            //1.root != null 并且 attachToRoot==false,返回的就只是设置了params的View
            if (root != null) {
                params = root.generateLayoutParams(attrs);
                if (!attachToRoot) {
                    temp.setLayoutParams(params);
                }
            }
            //2.root != null 并且 attachToRoot==true,返回的是root,也就是根布局
            if (root != null && attachToRoot) {
                root.addView(temp, params);
            }
            //3.root == null 并且 attachToRoot==false,返回的就只是设置了params的View
            if (root == null || !attachToRoot) {
                result = temp;
            }
        }
        return result;
}

看到这我们就知道的inflate方法中ViewGroup rootboolean attachToRoot两个参数的区别。
总结一下:

  1. root == null,返回的是一个没有设置LayoutParams的View

    if (root == null || !attachToRoot) {
                    result = temp;
                }
  2. root != null,attachToRoot == true,返回的就是已经添加完布局的root

    if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
  3. root != null,attachToRoot == false,返回的就是设置LayoutParams的View

    if (root != null) {
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        temp.setLayoutParams(params);
                    }
                }

四.总结

  1. 在代码中,ConstraintLayout与其他布局的区别。在xml中很常见,但是在代码中却容易疏漏。

  2. 在需要在代码中插入布局时,可分为两种
    2.1. 不需要返回生成的View

    LayoutInflater.from(this).inflate(R.layout.layout_run_view, mainRoot,true)
    //或者
    LayoutInflater.from(this).inflate(R.layout.layout_run_view, mainRoot)


    2.2. 需要返回生成的View,可能需要对View进行一些动画操作,比较方便
    ?
    val mRunTipView =
            LayoutInflater.from(this).inflate(R.layout.layout_run_view, mainRoot,false)
    mainRoot?.addView(mRunTipView)
    //mRunTipView 旋转,平移。。。

    2.3. 需要返回生成的View,自己对View的位置大小进行修改
    val mRunTipView =
          LayoutInflater.from(this).inflate(R.layout.layout_run_view, null,false)
    
    //创建LayoutParams
    val layoutParams = ConstraintLayout.LayoutParams(
      100,
      100
    ).apply {
      topMargin = 100
    }
    
    //把View添加到布局中
    mainRoot?.addView(mRunTipView,layoutParams)

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2022-03-04 15:44:08  更:2022-03-04 15:46:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 17:42:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码