Раскрывающиеся элементы Spinner: как определяется ширина? - PullRequest
0 голосов
/ 12 июня 2019

У меня есть 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 имел android:layout_width="match_parent".

Эскиз, иллюстрирующий, что я имею в виду:

Expected and actual dropdown width

Почему это происходит? Как я могу предсказать, какая ширина выпадающего меню будет основана на макете?

Я считаю этот выпадающий вид довольно волшебным

Ответы [ 2 ]

1 голос
/ 12 июня 2019

Вы смотрели на исходный код класса 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 - это плохо спроектированный класс (точнее, с устаревшим дизайном) и, тем более, с именем (на этом вводе-выводе Google в 2019 году они фактически объяснили в одной из сессий, почему название «Spinner» подсказка: это из 1-го андроидного прототипа). Изучив весь этот код, потребуется несколько часов, чтобы выяснить, что вертушка пытается сделать и как она работает, но путешествие не будет приятным.

Удачи.

Я повторю свой совет использовать ConstraintLayout, с которым, как вы сказали, вы знакомы; по крайней мере, сбросить вес. Рассматривая, как это работает (ListView !!!), вычисление веса гарантирует 2-й этап измерения / разметки, который не только чрезвычайно неэффективен и не нужен, но также может вызывать проблемы с внутренним адаптером данных, которым управляет эта DropDown, поэтому "список" отображается.

В конечном счете, другой класс также вовлечен, все это представлено в PopupView. PopupViews - это то, что вы видите, например, когда открываете элемент меню, и их иногда очень сложно настроить, в зависимости от того, что вы хотите сделать.

Почему Google выбрал этот подход в то время, я не знаю, но это, безусловно, требует обновления, и Material Design пока не принес много пользы в этом отношении, так как он всегда будет неполным или в альфа-состоянии. год позади всего остального.

0 голосов
/ 12 июня 2019

Это говорит вам, что FrameLayout бесполезен, потому что у него есть одно дочернее представление (Relative Layout).

Ширина вашего Framelayout определена следующим образом:

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

Ширина вашего относительного макета определена как:

<RelativeLayout
    android:layout_width="match_parent"

Так что просто удаление FrameLayout означает, что другой«Правило» на месте для ширины.

Чтобы действительно заменить FrameLayout на RelativeLayout, он должен выглядеть следующим образом:

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