Android ViewStub

这篇具有很好参考价值的文章主要介绍了Android ViewStub。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.ViewStub

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

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

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

也就是在app启动绘制页面的时候,ViewStub不会绘制到view树中,当在代码中执行inflate或setVisibility操作时,ViewStub才会被添加到视图中。其实ViewStub就是一个宽高都为0的View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。最终目的是把app加载页面的速度提高了,使用户体验更好。

因此,不是必须显示的布局就可以使用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是灰色的

Android ViewStub

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

Android ViewStub

所以第二次调用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> 标签的布局。文章来源地址https://www.toymoban.com/news/detail-406133.html

到了这里,关于Android ViewStub的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 新的CoolSiC™槽沟MOSFET技术,用于低栅氧化物应力和高性能

    UPS(Uninterruptible Power Supply)系统也称不间断电源系统,是一种能够提供电力备用的设备,当主电源出现故障或停电时,UPS系统可以立即启动,维持电力供应,保证设备的正常运行。 该论文描述了一种新型的SiC槽沟MOSFET概念,旨在平衡低导通损耗和类似Si-IGBT的可靠性。介绍了

    2024年02月15日
    浏览(41)
  • Jenkins 创建一个 job , 用于单独执行脚本

    目录 1.首先,在Jenkins中创建一个新的job 2.之后,会进入配置页面,在配置页面进行配置。  2.1.找到【Build Steps】,下拉菜单中选择「シェルの実行」 (执行Shell) 2.2.之后,会出现シェルスクリプト (Shell Script) 的 Area,在这里录入你想执行的 脚本 3.运行,并查看Log ・设置脚

    2024年02月12日
    浏览(39)
  • 用java语言写一个网页爬虫 用于获取图片

    以下是一个简单的Java程序,用于爬取网站上的图片并下载到本地文件夹: 这个程序首先读取指定网址的HTML源码,然后从中提取出所有的图片URL。最后,程序利用 Java 的 IO 功能下载这些图片并保存到指定的本地文件夹中。 需要注意的是,该程序只是一个简单的演示,实际使

    2024年02月11日
    浏览(49)
  • Linuxc centos下的网络性能测试命令iperf、iperf2、iperf3(常用于网络测速)

    目                录 一、网络性能测试命令介绍 (一)Iperf (二)iperf2 (三)iperf3 (四)几个命令的比较 二、使用场景 三、iperf命令详解 (一)安装 (二)命令 (三)启动和停止 1、启动服务端 2、启动客户端 3、停止命令 (四)应用 1、测试网络连接的带宽和吞吐量

    2024年01月21日
    浏览(46)
  • Android 实现MQTT客户端,用于门禁消息推送

    添加MQTT依赖 implementation ‘org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.2’ implementation ‘org.eclipse.paho:org.eclipse.paho.android.service:1.1.1’ 在Manifest清单文件中添加服务 MqttClient的实现方式 MQTT初始化连接线程,实现与服务器的连接、订阅、发布消息 MQTT重连 MQTT断开 发送消息 MqttAndroid

    2024年02月14日
    浏览(52)
  • 【Github】一个用于Active Directory的自助密码更改工具

    在众多企业的日常运营中,Active Directory(AD)扮演着核心角色,负责管理和维护员工账户。然而,密码重置作为IT支持团队的常规工作之一,往往既耗时又繁琐。虽然一些商业解决方案和通过Windows服务器上RDS服务可以应对密码修改的需求,但这些方法可能带来额外的成本和局

    2024年04月16日
    浏览(41)
  • Webmin--一个用于Linux基于Web的系统管理工具

    Webmin是一个用于Linux系统管理的开源的基于web的系统管理配置工具。有了这个工具的帮助,我们可以管理内部的系统配置,诸如设置用户账户,磁盘配额,像Apache, DNS, PHP, MySQL,文件共享的服务等。 Webmin应用程序是基于Perl模块并且为了通过浏览器通信它使用了TCP端口10000和O

    2024年02月04日
    浏览(45)
  • Matplotlib 是一个广泛用于 Python 数据可视化的库

    Matplotlib 是一个广泛用于 Python 数据可视化的库,它提供了丰富的绘图功能,允许用户创建各种类型的图表,从简单的折线图到复杂的三维图表,以及定制图形的各个方面。以下是Matplotlib的一些重要特点和常见用法: Matplotlib 的特点: 灵活性 :Matplotlib允许用户高度定制图形

    2024年02月07日
    浏览(49)
  • ImageCombiner是一个专门用于Java服务端图片合成的工具

    最近公司上了不少传播方面的需求,免不了合成各种营销图片,图片合成本身并不是什么高深的技术,但用底层api去搞确实繁琐,于是抽时间封装了一个小工具,初衷是解放生产力,后来发现挺好使,那就开源吧,花了一个整天重新整理了一下代码,作为自己从业十年第一个

    2024年02月06日
    浏览(45)
  • Android下载apk并安装apk(用于软件版本升级用途)

    软件版本更新是每个应用必不可少的功能,基本实现方案是请求服务器最新的版本号与本地的版本号对比,有新版本则下载apk并执行安装。请求服务器版本号与本地对比很容易,本文就不过多讲解,主要讲解下载apk到安装apk的内容。 (1)读写外部存储的权限需要动态申请,

    2024年02月01日
    浏览(88)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包