【问题标题】:Spinner dropdown items: how is width determined?微调器下拉项目:宽度如何确定?
【发布时间】:2019-06-12 12:10:13
【问题描述】:

我的应用中有一个Spinner,带有自定义的下拉视图。这是下拉项的布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:focusable="false"
    android:orientation="horizontal">

    <androidx.appcompat.widget.AppCompatImageButton
        android:id="@+id/leadingButton"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_weight="0" />

    <FrameLayout
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_gravity="center_vertical"
        android:layout_weight="1">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/dropdown_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/dropdown_text_subtitle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/dropdown_text" />
        </RelativeLayout>
    </FrameLayout>

    <androidx.appcompat.widget.AppCompatImageButton
        android:id="@+id/trailingButton"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:layout_weight="0" />
</LinearLayout>

Android Studio 警告我我的FrameLayout 没用。但是当我取出FrameLayout 时,下拉视图会变窄,并且不再与微调器本身对齐。当我尝试用ConstraintLayout 重写下拉项目时遇到了同样的问题:下拉列表变窄,大约是 Spinner 大小的一半,并且无法显示所有文本,即使 ConstraintLayout 有 @ 987654329@.

一个草图来说明我的意思:

为什么会这样?如何根据布局预测下拉菜单的宽度?

我发现这个下拉视图的大小非常神奇

【问题讨论】:

  • 微调器在哪里?
  • 无论如何我都会使用 ConstraintLayout,由于嵌套,您的布局很难在视觉上导航;您还使用带有权重的 LinearLayout;这对性能非常不利,并且不会增加太多价值。您的布局中没有什么是 ConstraintLayout 不可能实现的。
  • @MartinMarconcini 我知道为什么 ConstraintLayout 很好。这个问题与此无关。本题是关于Android布局系统如何确定spinner下拉项的宽度的。

标签: android android-layout android-view android-spinner android-measure


【解决方案1】:

你看过 Spinner 类的源代码吗?我已经做了。这是我发现的(API 27 Sources):

微调器在内部使用ListView(第一个LOL),由DropdownPopup(私有类)支持:

private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {

在查看它之前,请查看ListPopupWindow,因为它有很多关于它必须处理的问题的信息。这是一门大课,但在这些事情中,你可以看到:

    private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
    private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
    private int mDropDownHorizontalOffset;
    private int mDropDownVerticalOffset;

DropDown 似乎 - 默认情况下 - 基于基类包装内容,但是,驱动的 DropDownPopup(并包含带有微调器中所有项目的适配器)也有一个 void computeContentWidth() { 方法。

这个方法是从show() 方法调用的,所以在显示弹出窗口之前,这个计算每次都会发生。

我认为这是您正在寻找的部分答案:

        void computeContentWidth() {
            final Drawable background = getBackground();
            int hOffset = 0;
            if (background != null) {
                background.getPadding(mTempRect);
                hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
            } else {
                mTempRect.left = mTempRect.right = 0;
            }

            final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
            final int spinnerPaddingRight = Spinner.this.getPaddingRight();
            final int spinnerWidth = Spinner.this.getWidth();

            if (mDropDownWidth == WRAP_CONTENT) {
                int contentWidth =  measureContentWidth(
                        (SpinnerAdapter) mAdapter, getBackground());
                final int contentWidthLimit = mContext.getResources()
                        .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
                if (contentWidth > contentWidthLimit) {
                    contentWidth = contentWidthLimit;
                }
                setContentWidth(Math.max(
                       contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
            } else if (mDropDownWidth == MATCH_PARENT) {
                setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
            } else {
                setContentWidth(mDropDownWidth);
            }

            if (isLayoutRtl()) {
                hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
            } else {
                hOffset += spinnerPaddingLeft;
            }
            setHorizontalOffset(hOffset);
        }

您可能想在此处调试并设置断点以观察这些值是什么以及它们的含义。

另一部分是setContentWidth() 方法。这个方法来自ListPopupWindow,看起来像:

/**
     * Sets the width of the popup window by the size of its content. The final width may be
     * larger to accommodate styled window dressing.
     *
     * @param width Desired width of content in pixels.
     */
    public void setContentWidth(int width) {
        Drawable popupBackground = mPopup.getBackground();
        if (popupBackground != null) {
            popupBackground.getPadding(mTempRect);
            mDropDownWidth = mTempRect.left + mTempRect.right + width;
        } else {
            setWidth(width);
        }
    }

setWidth(也在那个班级)它所做的只是:

    /**
     * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
     * or {@link #WRAP_CONTENT}.
     *
     * @param width Width of the popup window.
     */
    public void setWidth(int width) {
        mDropDownWidth = width;
    }

这个mDropDownWidth似乎到处都在使用,但也让我在ListPopupWindow中找到了这个其他方法......

    /**
     * Sets the width of the popup window by the size of its content. The final width may be
     * larger to accommodate styled window dressing.
     *
     * @param width Desired width of content in pixels.
     */
    public void setContentWidth(int width) {
        Drawable popupBackground = mPopup.getBackground();
        if (popupBackground != null) {
            popupBackground.getPadding(mTempRect);
            mDropDownWidth = mTempRect.left + mTempRect.right + width;
        } else {
            setWidth(width);
        }
    }

所以你有它,需要更多的逻辑,包括“门面”(?)

我同意 Spinner 是一个设计糟糕的类(或者更确切地说,设计过时),而且名称更是如此(在 2019 年的 Google I/O 上,他们实际上在其中一个会议中解释了为什么名称为“Spinner “提示:它来自第一个 android 原型)。通过查看所有这些代码,需要几个小时才能弄清楚微调器正在尝试做什么以及它是如何工作的,但这次旅行并不愉快。

祝你好运。

我将重申我的建议,即使用您所说的您熟悉的 ConstraintLayout;至少,丢弃权重。 通过查看其工作原理(ListView !!!),权重计算保证了第二次测量/布局传递,这不仅效率极低且不需要,而且可能会导致此 DropDown 事物管理的内部数据适配器出现问题,因此显示“列表”。

最终,还涉及到另一个类,这都在PopupView 中呈现。例如,当您打开菜单项时,您会看到 PopupViews,并且有时很难自定义,具体取决于您想要做什么。

我不知道为什么 Google 当时选择了这种方法,但它肯定值得更新,而且 Material Design 在这方面还没有带来太多好处,因为它总是不完整或处于 alpha 状态比其他任何东西都落后一年。

【讨论】:

  • 关于setContentWidth 的一个好点:我可能只是将它设置为微调器的测量宽度,我猜这是“自然”宽度。
  • 老实说,我很想知道这是否有效(例如强制微调器的宽度) 该方法是公共的,但 这不会是第一次 Android 公共方法什么都不做或不是你所期望的 :)
【解决方案2】:

它告诉你 FrameLayout 是无用的,因为它有一个子视图(相对布局)。

您的 Framelayout 的宽度定义如下:

<FrameLayout
    android:layout_width="0dp"
    android:layout_weight="1"

您的相对布局的宽度定义为:

<RelativeLayout
    android:layout_width="match_parent"

因此,仅删除 FrameLayout 意味着为宽度设置了不同的“规则”。

要真正用 RelativeLayout 替换 FrameLayout,它应该如下所示:

<RelativeLayout
    android:layout_width="0dp"
    android:layout_weight="1"

【讨论】:

  • 我的问题并不清楚,但这正是导致我的问题的原因,即onMeasure 中的宽度协商对于FrameLayout 而言似乎与RelativeLayout 不同,在这种微调器下降的情况下-down 项目,对我来说似乎不太一致。
  • 它可能不喜欢重量tbh,你为什么不把它作为匹配父母?然后控制父布局中微调器的宽度而不是每个单独的项目
猜你喜欢
  • 2013-08-16
  • 1970-01-01
  • 2016-08-22
  • 1970-01-01
  • 2011-12-03
  • 1970-01-01
  • 2017-02-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多