我的应用程序中有一个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大小的一半,即使{{1 }}有ConstraintLayout
。
一个草图来说明我的意思:
为什么会这样?如何根据布局预测下拉菜单的宽度?
我发现此下拉视图的大小非常神奇
答案 0 :(得分:1)
您看过Spinner类的源代码吗?我已经做了。这是我发现的(API 27来源):
微调器在内部(第一个LOL)使用ListView
,由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 ”提示:它来自1st android原型)。通过查看所有这些代码,可能需要花费几个小时才能确定微调器正在尝试执行的操作以及其工作方式,但是旅途并不愉快。
祝你好运。
我会再次建议您使用ConstraintLayout(您说过很熟悉);至少要丢弃砝码。 通过查看其工作方式(ListView !!!),权重计算可以确保第二次测量/布局遍历,这不仅效率极低而且不需要,而且还可能导致内部数据适配器出现问题,而这是DropDown所管理的,因此显示“列表”。
最终,还涉及另一个类,所有这些都以PopupView
的形式出现。例如,当您打开菜单项时,您会看到PopupViews,有时很难自定义,这取决于您要执行的操作。
我不知道为什么Google当时选择了这种方法,但是它肯定值得进行更新,并且Material Design在这方面还没有带来太多进展,因为它总是不完整或处于alpha状态比其他任何产品都要晚一年。
答案 1 :(得分:0)
它告诉您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"