Android ViewStub

1.ViewStub

ViewStub是一个可用于性能优化的控件,它是一个不可见的、零尺寸的View,可以在运行时进行延迟加载一个布局文件,从而提高显示速率。

viewstub和include比较像,都是在一个布局文件中嵌入另外一个布局文件,然而viewstub可以延迟加载,它只会在手动指定加载的时候才会加载这个布局文件,而include则会立即加载。

手动指定加载:当ViewStub的setVisibility(int)方法或inflate()方法被调用时,它才会加载被指定的布局并在父布局中将自己替换为加载的布局。替换后,控件ViewStub会从布局树中移除。控件ViewStub的布局属性会传递给被加载的布局。

因此,不是必须显示的布局就可以使用ViewStub来代替,这样可以减少界面首次加载时资源消耗,提升最初加载速度。

ViewStub实际中常用情景:比如在无数据或者网络错误的时候,需要单独显示一个布局,那么这个布局就可以用ViewStub。

注意:

①对ViewStub的inflate操作只能进行一次,因为inflate的时候是将其指向的布局文件解析inflate并替换掉当前ViewStub本身(由此体现出了ViewStub“占位符”性质),一旦替换后,此时原来的布局文件中就没有ViewStub控件了,因此,如果多次对ViewStub进行infalte,会出现错误信息:ViewStub must have a non-null ViewGroup viewParent。

②ViewStub指向的布局文件解析inflate并替换掉当前ViewStub本身,并不是完全意义上的替换(与include标签还不太一样),替换时,布局文件的layout params是以ViewStub为准,其他布局属性是以布局文件自身为准。

2.ViewStub使用

①布局文件

在主布局中使用ViewStub标签来引入目标布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <TextView

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="@string/hello_world" />

    <Button

        android:id="@+id/toggle"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:onClick="onClick"

        android:text="显示/隐藏" />

    <ViewStub

        android:id="@+id/vs"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:inflatedId="@+id/inflated_id"

        android:layout="@layout/view_stub_layout"/>

</LinearLayout>

android:layout指定被加载替换的布局。
android:inflatedId指定被加载替换的布局的id。

其中被加载替换的布局view_stub_layout.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical" >

<TextView 

    android:id="@+id/tv"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:text="vs中的tv" />    

</LinearLayout>

②java文件

布局文件写好了,接下来在程序运行时加载这个布局。

public class MainActivity extends Activity {
    private ViewStub stub;

    private boolean isShow = true;

    private TextView tv;

    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        stub = (ViewStub) findViewById(R.id.vs);

          //stub布局的加载有两种方式,一种是stub.inflate();另一种是//stub.setVisibility(View.VISIBLE);+findViewById

        View inflatedView = stub.inflate(); //inflate()只能调用一次

        //stub.setVisibility(View.VISIBLE);

        //View inflatedView = this.findViewById( R.id.inflated_id);       

        tv = (TextView) inflatedView.findViewById( R.id.tv); //注意要先实例化stub,然后才可以拿到tv

        inflatedView.setBackgroundColor( Color.BLUE);

    }

    public void onClick(View v){
        switch (v.getId()) {
            case R.id.toggle:

                if (isShow) {
                    stub.setVisibility(View.GONE);

                }else{
                    stub.setVisibility(View.VISIBLE);

                    tv.setText("---");

                }

                isShow = !isShow;

                break;

            default:

                break;

        }

    }

}

官方推荐加载首选方法是inflate()。调用inflate()方法后布局被加载替换,同时返回布局对象。避免了使用findViewById()方法。

注意:inflate()方法只能调用一次,调用被移除而没有了父布局。第二次调用会抛出异常ViewStub must have a non-null ViewGroup viewParent 。

ViewStub可以设置加载监听回调,成功加载后回调,且只会回调一次。

viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    /**

     * @param stub ViewStub 对象

     * @param inflated 被加载填充的布局

     */

    @Override

    public void onInflate(ViewStub stub, View inflated) {
      tv = (TextView) inflated.findViewById(R.id.tv);

      tv.setText("ShowTitle");

    }

}

3.ViewStub源码

①构造方法

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context);

    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes);

    saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr, defStyleRes);

    //获取在xml文件中定义的inflatedId属性

    mInflatedId = a.getResourceId( R.styleable.ViewStub_inflatedId, NO_ID);

    //获取到xml文件中定义的layout属性

    mLayoutResource = a.getResourceId( R.styleable.ViewStub_layout, 0);

    //获取xml文件中定义的id属性

    mID = a.getResourceId( R.styleable.ViewStub_id, NO_ID);

    a.recycle();

    //设置ViewStub直接不显示。所以在xml文件中如何控制它的显示属性,都是不显示的

    setVisibility(GONE);

    // 设置ViewStub不尽兴绘制

    setWillNotDraw(true);

}

②ViewStub的onMeasure和onDraw方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //设置宽和高都为0,也就是控件的大小为0

    setMeasuredDimension(0, 0);

@Override

public void draw(Canvas canvas) {
    //不进行任何绘制

}

@Override

protected void dispatchDraw(Canvas canvas) {
}

③inflate()方法

public View inflate() {
    final ViewParent viewParent = getParent(); //获取ViewStub在布局文件中的父布局

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) { //mLayoutResource就是属性layout指定的真正要加载的布局

            final ViewGroup parent = (ViewGroup) viewParent;

            final View view = inflateViewNoAdd( parent); //把真正要显示的View布局文件渲染成View对象并且给返回

            replaceSelfWithView(view, parent); //将ViewStub从布局文件结构中移除,并且把渲染好的View添加到ViewStub所处的位置

            mInflatedViewRef = new WeakReference<>(view);

            if (mInflateListener != null) {
                mInflateListener.onInflate(this, view); //保存当前View对象的弱引用,方便其他地方使用

            }

            return view; //返回创建的View对象

        } else { //当没有为ViewStub指定layut属性时会走这个case,抛出异常

            throw new IllegalArgumentException( "ViewStub must have a valid layoutResource");

        }

    } else { //第一次调用ViewStub的inflate方法后会把ViewStub从布局文件结构中移除,也就没有了ViewGroup。当第二次调用ViewStub的inflate方法后会走这个case,抛出异常

        throw new IllegalStateException( "ViewStub must have a non-null ViewGroup viewParent");

    }

}

可以看到当viewParent为空或viewParent不是ViewGroup时就会报ViewStub must have a non-null ViewGroup viewParent错误。第一次调用inflate方法的时候不会报错,肯定是进了if,if里面有一个方法replaceSelfWithView(view,parent),其中参数view就是在布局文件中给viewstub指定的layout所引用的那个布局;参数parent就是getParent方法得到的,也就是activity的填充布局LinearLayout。

其实inflate方法主要的就是inflateViewNoAdd和replaceSelfWithView这两个方法。其中,inflateViewNoAdd方法负责获取到布局渲染器将真正需要展示的布局文件渲染成View并且给返回。replaceSelfWithView方法负责将ViewStub从布局文件结构中移除,同时把渲染好的View添加到ViewStub之前所处的位置。之后把渲染好的View的弱引用给存储起来,方便在setVisibility()方法中使用。

首先看inflateViewNoAdd方法:

private View inflateViewNoAdd(ViewGroup parent) {      

    final LayoutInflater factory; //布局填充器

    if (mInflater != null) {
        factory = mInflater;

    } else {
        factory = LayoutInflater.from(mContext);

    }

    // 把真正要显示的布局文件渲染成View对象

    final View view = factory.inflate( mLayoutResource, parent, false);

    // mInflatedId对应android:inflatedId,如果指定了就为渲染好的View给设置进去

    if (mInflatedId != NO_ID) {
        view.setId(mInflatedId);

    }

    return view;

}

然后进去replaceSelfWithView方法看一下:

private void replaceSelfWithView(View view, ViewGroup parent) {
    final int index = parent.indexOfChild(this); //获取ViewStub在父布局中所处在的位置

    parent.removeViewInLayout(this); //将ViewStub从父布局中移除

    final ViewGroup.LayoutParams layoutParams = getLayoutParams(); //获取ViewStub的布局参数

    if (layoutParams != null) { //当设置了布局参数(例如 android:width="50dp",height="50dp"),就将渲染好的View连同ViewStub的布局参数添加到ViewStub所处的位置

        parent.addView(view, index, layoutParams);

    } else { //否则,将渲染好的View添加到ViewStub所处的位置

        parent.addView(view, index);

    }

}

首先通过parent.removeViewInLayout(this);把this也就是viewstub从父布局linearlayout中移除了。然后通过parent.addView()把view(也就是layout引用的布局)添加到了父布局LinearLayout中。

用layout inspector来查看一下:

inflate前:可以看到viewstub是灰色的

 inflate后:可以看到viewstub直接被移除了,把引用布局直接放到view树里了。

所以第二次调用inflate方法时,viewstub的parent已经为空了,就会抛出此异常。

当调用textView = viewStub.findViewById( R.id.tv);获取到的textview是空的;而使用textView = findViewById(R.id.tv);就可以直接拿到控件对象。

④setVisibility()方法

public void setVisibility(int visibility) {
    if (mInflatedViewRef != null) { //mInflatedViewRef只有在inflate方法中被初始化了,即当真正的布局文件被加载之后才不为空(第二次就不为空)

        View view = mInflatedViewRef.get(); //获取到当前的View

        if (view != null) {
            view.setVisibility(visibility); //当前view不空则直接显示

        } else {
            throw new IllegalStateException( "setVisibility called on un-referenced view");

        }

    } else { //没有调用inflate的话,会设置可见性(第一次会为空)

        super.setVisibility(visibility);

        if (visibility == VISIBLE || visibility == INVISIBLE) { //如果当前设置可见性为 VISIBLE或者INVISIBLE的时候,会调用inflate方法

            inflate();

        }

    }

}

首先使用mInflatedViewRef获取到view,然后设置隐藏与显示;mInflatedViewRef是一个view的弱引用WeakReference<View>。其实在上面的inflate方法中已经为其添加了mInflatedViewRef = new WeakReference<>(view);这个view就是viewstub中的引用布局。

所以,使用viewstub可以实现相同的显示或隐藏效果。

可以发现,setVisibility方法中也调用了inflate方法,因此即使没有调用inflate方法,而是直接点击show按钮,引用布局也可以绘制出来。

注意:ViewStub本身是不可见的,对ViewStub的setVisibility(..)与其他控件不一样,ViewStub的setVisibility成View.VISIBLE或INVISIBLE时,如果是首次使用,都会自动inflate其指向的布局文件,并替换ViewStub本身,再次使用则是相当于对其指向的布局文件设置可见性。

注意:

ViewStub支持使用 <include> 标签的布局。

ViewStub不支持使用 <merge> 标签的布局。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/803886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在前后端一体的项目中引入element-ui,即引入index.js、index.css等文件。

24年接手了一个18年的项目&#xff0c;想使用el-ui的组件库&#xff0c;得自己手动引入。 通过官网可以知道&#xff0c;首先得准备以下文件 <!-- 引入样式 --> <link rel"stylesheet" href"https://unpkg.com/element-ui/lib/theme-chalk/index.css…

计算机视觉——Python OpenCV BGR转HSV

这里将介绍如何使用 OpenCV 与 Python 来作彩色影像转HSV(RGB to HSV 或 BGR to HSV)&#xff0c;在写 Python 影像处理程序时常会用到 OpenCV cvtColor 作颜色空间转换的功能&#xff0c;接下来介绍怎么使用 Python 搭配 OpenCV 模块来进行 RGB/BGR 转 HSV 彩色转HSV空间。 H…

第6章 6.1.1 文本格式化 sprintf函数(MATLAB入门课程)

sprintf函数源自 C 语言标准库中的同名函数&#xff0c;这个函数在 C 语言中用于创建格式化的字符串&#xff0c;且使用频率非常高。作为一门高级编程语言&#xff0c;MATLAB借鉴了 C 语言和其他编程语言中的许多特性和命名惯例。在MATLAB中&#xff0c;sprintf函数主要有两种用…

Redis系列之主从复制集群搭建

在上一篇博客&#xff0c;我们已经知道怎么搭建一个redis单机版&#xff0c;这篇博客基于之前的基础&#xff0c;来搭建一个redis主从同步&#xff0c;本博客框架是一主二从&#xff0c;一个主节点&#xff0c;其它两个从节点 实验环境 CentOS7Xshell6XFtp6Redis6.2.2 主从关…

Redis中的集群(二)

节点 集群数据结构 redisClient结构和clusterLink结构的相同和不同之处 redisClient结构和clusterLink结构都有自己的套接字描述符和输入、输出缓冲区&#xff0c;这两个结构的区别在于&#xff0c;redisClient结构中的套接字和缓冲区是用于连接客户端的&#xff0c;而clust…

手写一个民用Tomcat (03)

我们完成了这个 小型Tomcat 基本功能&#xff0c;但是他处理请求还是 一个一个的执行&#xff0c;并不能做到并行处理。 我们仿照Tomcat的思路来进行 一比一精准优化。 首先看一下我们的JxdHttpConnector 有什么改进&#xff0c;他可以理解成一个快递站领导&#xff0c;统一指挥…

Django实现的登录注册功能

1 前言 在Web开发中&#xff0c;用户登录和注册是最基本且必不可少的功能。Django&#xff0c;作为一个高级的Python Web框架&#xff0c;为我们提供了强大的工具和库来快速实现这些功能。下面&#xff0c;我将详细介绍如何使用Django来实现用户登录和注册功能。 2 功能介绍 …

MATLAB计算投资组合的cVaR和VaR

计算条件风险价值 (Conditional Value-at-Risk, cVaR) 是一种衡量投资组合风险的方法&#xff0c;它关注的是损失分布的尾部风险。 MATLAB代码如下: clc;close all;clear all;warning off;%清除变量 rand(seed, 100); randn(seed, 100); format long g;% 随机产生数据&#x…

Mac 安装 brew brew cask 遇到的问题以及解决办法

安装Homebrew和Homebrew Cask是在Mac上管理软件包的常用方法。虽然大多数情况下安装这两个工具是比较简单的&#xff0c;但有时候也可能遇到一些问题。下面是一些常见的问题以及解决办法&#xff1a; 问题1&#xff1a;无法安装Homebrew 解决办法&#xff1a; 1.确保你的Mac已连…

低代码ARM计算机在IIoT中的采集控制生产面板

工业4.0的大潮下工业物联网&#xff08;IIoT&#xff09;已成为推动制造业转型升级的重要动力。其中&#xff0c;低代码ARM嵌入式计算机凭借其出色的性能、灵活的配置以及高度集成化的特点&#xff0c;在工业设备远程监控、维护与诊断方面发挥着关键作用。 一、远程监控与维护 …

华为 2024 届校园招聘-硬件通⽤/单板开发——第一套(部分题目分享,完整版带答案,共十套)

华为 2024 届校园招聘-硬件通⽤/单板开发——第一套 部分题目分享&#xff0c;完整版带答案(有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09;&#xff08;共十套&#xff09;获取&#xff08;WX:didadidadidida313&#xff0c;加我…

【双指针】删除有序数组中的重复项

给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k &#xff0c;你…

【黑马头条】-day07APP端文章搜索-ES-mongoDB

文章目录 今日内容1 搭建es环境1.1 拉取es镜像1.2 创建容器1.3 配置中文分词器ik1.4 测试 2 app文章搜索2.1 需求说明2.2 思路分析2.3 创建索引和映射2.3.1 PUT请求添加映射2.3.2 其他操作 2.4 初始化索引库数据2.4.1 导入es-init2.4.2 es-init配置2.4.3 导入数据2.4.4 查询已导…

IT行业网络安全守护者-行云管家云堡垒机

IT行业即信息技术行业&#xff0c;是一个涵盖广泛的行业领域&#xff0c;主要涉及与信息的处理、存储、传输和应用相关的技术。对于IT行业而言&#xff0c;保障数据安全以及网络安全至关重要&#xff0c;一不小心就容易造成数据泄露事件。今天我们小编就给大家介绍一下IT行业网…

《模版模式(极简c++)》

本文章属于专栏- 概述 - 《设计模式&#xff08;极简c版&#xff09;》-CSDN博客 本章简要说明适配器模式。本文分为模式说明、本质思想、实践建议、代码示例四个部分。 模式说明 方案&#xff1a; 模版模式定义了一个逻辑的骨架&#xff0c;将某些步骤推迟到子类中实现。父类…

设计模式系列:单例模式

作者持续关注WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;WPS二次开发QQ群:250325397&#xff09;&#xff0c;摸鱼吹牛嗨起来&#xff01…

技术即服务:产品预告-1

为了让大家以最小的代价入门、学到、升华一些领域的技术&#xff0c; 博主准备给大家做一个代理&#xff1a;我来深入研究各项技术点 和 应用点&#xff0c;你只需要从我这里轻松学习就行了。 目标&#xff1a;从我这里更加容易地理解技术并学到想要的知识&#xff0c;并且在短…

scaling laws for neural language models

关于scaling law 的正确认识 - 知乎最近scaling law 成了最大的热词。一般的理解就是&#xff0c;想干大模型&#xff0c;清洗干净数据&#xff0c;然后把数据tokens量堆上来&#xff0c;然后搭建一个海量H100的集群&#xff0c;干就完了。训练模型不需要啥技巧&#xff0c;模型…

通过本机调试远端路由器非直连路由

实验目的&#xff1a;如图拓扑&#xff0c;通过本机电脑发&#xff0c;telnet调试远程AR4设备。 重点1&#xff1a;通过ospf路由协议配置拓扑网络&#xff0c;知识点&#xff1a;ospf配置路由器协议语法格式&#xff0c;area区域的定义&#xff0c;区域内网络的配置&#xff0…

基于单片机的奶瓶温控系统设计

摘要:本设计使用STC89C51单片机为核心芯片,DS18B20 作为温度检测模块,LCD1602 显示温度值,当温度低于设定的温度时,启动加热功能;当温度高于设定的温度时,该系统中断加热,实现自动报警功能。设计简单、成本低、实用性强。 关键词:单片机;温度传感器;设计 1 概述 随…