【问题标题】:How to avoid total view-hierarchy re-layout when using EditText?使用 EditText 时如何避免总视图层次结构重新布局?
【发布时间】:2016-02-10 17:22:07
【问题描述】:

背景

我在一个活动中向用户显示了一个相当复杂的布局。

其中一个视图是 EditText。

因为我必须让其中一个视图留在软键盘后面,而其余的都在它上面,所以我不得不听取视图布局的变化(写了关于它here)。

问题

我注意到,只要 EditText 具有焦点并显示其插入符号,整个视图层次结构就会重新布局。

您可以通过查看我创建的侦听器的日志来查看它,或者通过开发人员设置启用“显示表​​面更新”。

这会在某些设备上导致性能不佳,尤其是在 Activity 的布局很复杂或具有复杂布局的片段时。

代码

我不打算展示原始代码,但有一个简单的方法可以重现该问题:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.user.myapplication.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="just some text"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="phone"
            android:text="write here"
            android:textSize="18dp"/>


        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="just some text 2"/>
    </LinearLayout>
</FrameLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(android.R.id.content).getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {

            @Override
            public boolean onPreDraw() {
                Log.d("AppLog", "onPreDraw");
                return true;
            }
        });
    }
}

我尝试过的

当禁用插入符号时(使用“cursorVisible”,由于某种原因被称为“光标”),我可以看到问题不存在。

我试图找到内置插入符号行为的替代方法,但找不到。我发现的唯一东西是this post,但它似乎使它成为静态的,我不确定它的性能如何(性能和兼容性与普通插入符号相比)。

我尝试强制设置 EditText 的大小,这样它就不会导致包含它的布局失效。没用。

我还注意到,在原始应用上,即使应用进入后台,日志也可以(出于某种原因)继续写入。

我已经报告了这个问题(包括示例和视频)here,希望 Google 能指出问题所在或解决此问题。

问题

有没有办法避免整个视图层次结构的重新布局?一种仍然可以让 EditText 具有与普通 EditText 相同的外观和感觉的方法?

也许是一种自定义 EditText 如何使用插入符号的方式?

【问题讨论】:

  • 前几天我在 android 开发者网站上查看了硬件加速主题,我认为它与使用复杂布局时的性能密切相关,并且其中的某些区域应该重绘。
  • @SanjeetAjnabee 我不明白你的意思。
  • 当 EditText 获得“焦点”或插入符号一直闪烁时会发生这种情况吗?是否在编辑文本上应用了 Span/CharacterStyle 来改变文本边界?

标签: android android-layout android-edittext


【解决方案1】:

我注意到,每当 EditText 具有焦点并显示其插入符号时, 整个视图层次结构得到重新布局。

这不是真的。 EditText 的大小和位置是恒定的 - 没有重新布局。您可以使用下面的代码进行检查。

findViewById(android.R.id.content).getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    Log.d("AppLog", "Layout phase");
  }
});

由于插入符号闪烁 - EditText 不断调用invalidate()。这会强制 GPU 再次绘制 EditText。

在我的 nexus 5(棉花糖)上,我看到只有 EditText 被重绘(显示 GPU 视图更新 - 已启用)。

【讨论】:

  • 不应该使用“addOnPreDrawListener”和“显示表面更新”来显示整个树被重新绘制吗?正如我发现的那样,“显示 GPU 视图更新”非常错误,因为有时它会卡住并显示整个屏幕为红色(即使在 Android 的设置应用程序中也是如此)。 “显示 GPU 视图更新”是不是更多地是关于 GPU,而不是布局阶段?
【解决方案2】:

如何覆盖dispatchOnPreDraw() 您在活动中使用的所有视图并使用标志来检查该特定视图是否需要重绘?

因为只有当文本视图处于焦点时,您才需要禁用所有其他视图的重绘。因此,当文本视图处于焦点时,有一个标志来禁用其他视图的重绘。

如果 dispatchOnPreDraw() 方法返回 false,则刷新视图将继续,否则不会。我不知道您的布局有多复杂以及使用了多少视图,但这里一个单独的类应该扩展使用的视图并覆盖该方法,并且还需要一个机制/变量来区分当前焦点中的对象。

希望这个方法有帮助!

【讨论】:

  • 这里没有放任何源代码。但只是一个小想法:当键盘打开时,您应该获得所有其他视图的 x 和 y 坐标,而不是 EditText。当 EditText 获得焦点时,只需强制在计算的 x,y 坐标处绘制其他视图。
  • “Dominik Suszczewicz”的回答让我相信它可能不是重新布局,而是重新绘制。我不确定它对性能的影响有多大。我想知道如何更好地检查它,为什么我如此确定我已经检查了所有内容并看到它确实重新布局。现在我也不确定应该如何使用“显示表​​面更新”以及它的作用(我记得它是用于检查布局阶段引起的性能)。
  • 是的,当布局特定更改发生时,Android 会重新布局,例如视图的高度或宽度。显示表面更新可能是正确的选项,但“显示 GPU 视图更新”可能不正确,因为它显示了由 GPU 支持的视图。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-24
  • 2023-04-03
  • 2015-12-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多