草庐IT

Android 如何改变View的绘制层级

leilifengxingmw 2023-11-10 原文

正常来说,我们向一个ViewGroup中添加两个View,后添加的View会显示在最上层。举个例子:

首先自定义一个ViewGroup,用来添加两个View

class MyViewGroup @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {


    var mFirstView: View? = null
        set(value) {
            field = value
            addView(
                value,
                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            )
        }
    var mSecondView: View? = null
        set(value) {
            field = value
            addView(
                value,
                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            )

        }


    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        measureChild(mFirstView, widthMeasureSpec, heightMeasureSpec)
        measureChild(mSecondView, widthMeasureSpec, heightMeasureSpec)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        mFirstView?.layout(l, t, r, b)
        mSecondView?.layout(l, t, r, b)
    }

}

依次添加 mFirstView 和 mSecondView

 lateinit var fm_root_layout: FrameLayout
 override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     setContentView(R.layout.activity_my_view_group)
     fm_root_layout = findViewById(R.id.fm_root_layout)
         //先把 MyViewGroup 添加到 Activity 的根布局文件中
     val viewGroup = MyViewGroup(this)
     fm_root_layout.addView(viewGroup, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
     val firstView = TextView(this).apply {
         text = "firstView"
         textSize = 20 f
         gravity = Gravity.CENTER
         setTextColor(resources.getColor(R.color.white))
         background = ColorDrawable(resources.getColor(R.color.colorPrimary))
     }
     val secondView = TextView(this).apply {
             text = "secondView"
             textSize = 20 f
             gravity = Gravity.CENTER
             setTextColor(resources.getColor(R.color.white))
             background = ColorDrawable(resources.getColor(R.color.colorAccent))
         }
     //注释1处,依次添加 mFirstView 和 mSecondView
     viewGroup.mFirstView = firstView
     viewGroup.mSecondView = secondView
 }

注释1处,依次添加 mFirstView 和 mSecondView,我们添加View的时候,宽高都指定为 MATCH_PARENT。效果如下:

如果说,我们不改变添加View 的顺序,想让 mFirstView 显示在最上层,怎么实现呢?答案是ViewGroup 的 getChildDrawingOrder 方法。我们先来实现效果,后面再分析原理。

修改 MyViewGroup

class MyViewGroup @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {

    //注释1处,记录两个View 的下标。
    var mFirstViewIndex = -1
    var mSecondViewIndex = -1
    var mFirstView: View? = null
        set(value) {
            field = value
            addView(
                value,
                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            )

            mFirstViewIndex = indexOfChild(mFirstView)

        }
    var mSecondView: View? = null
        set(value) {
            field = value
            addView(
                value,
                LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            )

            mSecondViewIndex = indexOfChild(mSecondView)
        }

    init {

        //注释2处,这里调用的是 ViewGroup 的 setChildrenDrawingOrderEnabled 方法 并传递了 true。
        isChildrenDrawingOrderEnabled = true

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        measureChild(mFirstView, widthMeasureSpec, heightMeasureSpec)
        measureChild(mSecondView, widthMeasureSpec, heightMeasureSpec)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        mFirstView?.layout(l, t, r, b)
        mSecondView?.layout(l, t, r, b)
    }

    override fun getChildDrawingOrder(childCount: Int, i: Int): Int {
        //注释3处,修改绘制顺序,当要绘制 mFirstView的时候,绘制mSecondView。要绘制 mSecondView 的时候,绘制mFirstView。
        if (i == mFirstViewIndex) {
            return mSecondViewIndex
        } else if (i == mSecondViewIndex) {
            return mFirstViewIndex
        }
        return super.getChildDrawingOrder(childCount, i)
    }
}

注释1处,记录两个View 的下标。
注释2处,这里调用的是 ViewGroup 的 setChildrenDrawingOrderEnabled 方法并传递了 true。

注释3处,修改绘制顺序,当要绘制 mFirstView的时候,绘制mSecondView。要绘制 mSecondView 的时候,绘制mFirstView。效果如下所示:

好了,效果实现了,接下来我们来分析一下原理。

子View 的绘制是在 ViewGroup 的 dispatchDraw 方法开始的。

@Override
protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
    //...
    int clipSaveCount = 0;
    
    // We will draw our child's animation, let's reset the flag
    mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
    mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
    boolean more = false;
    final long drawingTime = getDrawingTime();
    if(usingRenderNodeProperties) canvas.insertReorderBarrier();
    final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    int transientIndex = transientCount != 0 ? 0 : -1;
    // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
    // draw reordering internally
    final ArrayList < View > preorderedList = usingRenderNodeProperties ? null : buildOrderedChildList();
    //注释1处,preorderedList == null 并且 isChildrenDrawingOrderEnabled() 为 true  那么使用自定义的绘制顺序。
    final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
    for(int i = 0; i < childrenCount; i++) {
        //...
        //注释2处,在循环绘制的过程中,获取要绘制的child的下标。
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        //注释3处,根须下标获取要绘制的View。
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            //注释4处,绘制View。
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    //...
}

注释1处,preorderedList == null 并且 isChildrenDrawingOrderEnabled() 为 true 那么使用自定义的绘制顺序。 MyViewGroup 调用了 ViewGroup 的 setChildrenDrawingOrderEnabled 方法并传递了 true。

/**
 * 告诉 ViewGroup 是否 使用 {@link #getChildDrawingOrder(int, int)} 方法中定义子View的绘制顺序。
 * <p>
 * Note that {@link View#getZ() Z} reordering, done by {@link #dispatchDraw(Canvas)},
 * will override custom child ordering done via this method.
 *
 * @param enabled true if the order of the children when drawing is determined by
 *        {@link #getChildDrawingOrder(int, int)}, false otherwise
 *
 * @see #isChildrenDrawingOrderEnabled()
 * @see #getChildDrawingOrder(int, int)
 */
protected void setChildrenDrawingOrderEnabled(boolean enabled) {
    setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled);
}

注释2处,在循环绘制的过程中,获取要绘制的child的下标。

private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
    final int childIndex;
    if(customOrder) {
        //注释1处,这里返回了真正要绘制的View 的下标。
        final int childIndex1 = getChildDrawingOrder(childrenCount, i);
        if(childIndex1 >= childrenCount) {
            throw new IndexOutOfBoundsException("getChildDrawingOrder() " + "returned invalid index " + childIndex1 + " (child count is " + childrenCount + ")");
        }
        childIndex = childIndex1;
    } else {
        childIndex = i;
    }
    return childIndex;
}

getAndVerifyPreorderedIndex方法注释1处,通过调用 getChildDrawingOrder 获取真正要绘制的View 的下标。我们正是通过重写了这方法实现了交换 mFirstView 和 mSecondView 的 绘制顺序。

/**
 * 返回要绘制的 View 的下标。如果想改变自 View 的绘制顺序,可以重写这个方法。 
 * <p>
 * 注意:这个方法必须配合 {@link #setChildrenDrawingOrderEnabled(boolean)} 方法使用才有效果。 需要调用{@link #setChildrenDrawingOrderEnabled(boolean)} 方法并传递 true。
 *
 * @param i The current iteration. 注意我感觉这里叫 iteration 不合适,应该叫 index 。是当前要绘制的View 的下标。
 * @return The index of the child to draw 返回真正要绘制的 View 的 下标。
 *
 * @see #setChildrenDrawingOrderEnabled(boolean)
 * @see #isChildrenDrawingOrderEnabled()
 */
override fun getChildDrawingOrder(childCount: Int, i: Int): Int {
    //注释3处,修改绘制顺序,当要绘制 mFirstView的时候,绘制mSecondView。要绘制 mSecondView 的时候,绘制mFirstView。
    if (i == mFirstViewIndex) {
        return mSecondViewIndex
    } else if (i == mSecondViewIndex) {
        return mFirstViewIndex
    }
    return super.getChildDrawingOrder(childCount, i)
}

dispatchDraw 方法注释3处,根须下标获取要绘制的View。
注释4处,绘制View。

总结一下,要改变View的绘制顺序步骤如下:

  1. 自定义 ViewGroup 调用 setChildrenDrawingOrderEnabled(boolean) 方法并传递 true。
  2. 重写 getChildDrawingOrder(int childCount, int i) 方法,根据自己的逻辑返回真正要绘制的View 的下标。

有关Android 如何改变View的绘制层级的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  8. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  9. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  10. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

随机推荐