android:foreground 属性/setForeground() 方法不适用于 Button 元素
android:foreground attribute / setForeground() method does not work with Button elements
因为 Android 23, the android:foreground
XML attribute (and corresponding setForeground()
method) 应该对所有视图可用,而不是像以前那样只对 FrameLayout 实例可用。
然而,出于某种原因,每当我创建一个继承自 TextView -> View 的 Button 实例时,我似乎根本无法显示前景。
这是一个示例按钮定义,它似乎无法显示前景:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="Primary Full Width"
style="@style/Button.Primary" />
下面是 Button.Primary
样式的定义:
<style name="Button.Primary" parent="Widget.AppCompat.Button">
<item name="android:background">@drawable/button_primary_background_selector</item>
<item name="android:foreground">@drawable/button_primary_foreground_selector</item>
<item name="android:textAppearance">@style/TextAppearance.FF.Button</item>
<item name="android:minHeight">80dp</item>
<item name="android:height">80dp</item>
<item name="android:minWidth">200dp</item>
<item name="android:defaultWidth">288dp</item>
<item name="android:maxWidth">400dp</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:gravity">center_vertical|center_horizontal</item>
<item name="android:paddingStart">70dp</item>
<item name="android:paddingEnd">70dp</item>
<item name="android:stateListAnimator">@null</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
<item name="android:drawablePadding">10dp</item>
</style>
我已经确认在样式或 <Button>
定义中设置 foreground
属性不会改变任何东西,无论如何前景都无法显示。背景和文字都正确显示,只是缺少前景。
我也试过将前景设置为纯色而不是状态选择器可绘制,但没有成功。
在将样式元素一一删除并重新测试后,我将问题缩小到样式中包含 android:singleLine
属性。
删除此属性后,任何使用我的样式的 TextView 或按钮都可以根据需要正确显示前景可绘制对象。查看定义了 singleLine
的 TextView.java 的实现,我仍在努力确定设置此属性如何导致前景被忽略,但如果我发现,我会更新此答案。
我知道 singleLine
的使用已被弃用,但不幸的是,我仍然需要 singleLine
/ ellipsize
属性组合提供的功能,这些属性在替换为 [ 时不可用=19=]用法。
编辑: 在写完上面的答案后,我决定做更多的调查并发现更多的细节。
首先,我创建了一个从 AppCompatButton 扩展的自定义视图,这样我就可以尝试重新实现 singleLine
/ ellipsize
提供的功能(即,通过替换换行符在单行上显示文本带空格的字符,如果文本 运行 不在视图中,则还要添加省略号)。
通读 TextView source code,我发现了一段代码,当 singleLine
属性设置为 true 时调用:
private void applySingleLine(boolean singleLine, boolean applyTransformation,
boolean changeMaxLines) {
mSingleLine = singleLine;
if (singleLine) {
setLines(1);
setHorizontallyScrolling(true);
if (applyTransformation) {
setTransformationMethod(SingleLineTransformationMethod.getInstance());
}
}
...
}
所以我尝试提取这些行并添加到我自己的实现中:
private void updateMaxlinesLocally(int maxLines) {
if (maxLines == 1) {
Log.d("Button", "max lines was 1, setting to single line equivalent");
setLines(1);
setHorizontallyScrolling(true);
setTransformationMethod(SingleLineTransformationMethod.getInstance());
// reset any text that may have already been set
setText(getText());
} else {
Log.d("Button", "max lines was : " + maxLines);
}
}
但是当这段代码 运行 时,我看到了与之前相同的问题,前景可绘制对象将不再可见。
经过进一步测试,我能够将问题缩小到 setHorizontallyScrolling(true)
方法调用。如果我在我的自定义实现中只评论了这一行,我就能够保留我之前拥有的 singleLine
/ ellipsize
功能,并且前景可绘制对象会按预期显示!将其缩小到该方法仍然无法帮助我找出 TextView 基本实现中的根本原因,但如果有人对此有任何详细信息,请随时发表评论并提供更多信息。
这是我的最终自定义按钮 class,它只查看 maxLines
属性并在 maxLines
设置为 1 时模拟旧的 singleLine
功能,避免阻止前景显示的方法调用。万一有用...
public class Button extends AppCompatButton {
public Button(Context context) {
super(context);
init(context, null, 0);
}
public Button(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
updateMaxlinesLocally(maxLines);
}
private void init(Context context, AttributeSet attributeSet, int defStyleAttr) {
int[] set = {
android.R.attr.maxLines
};
TypedArray a = context.obtainStyledAttributes(
attributeSet, R.styleable.Button);
int maxLines = a.getInt(R.styleable.Button_android_maxLines, -1);
a.recycle();
updateMaxlinesLocally(maxLines);
}
private void updateMaxlinesLocally(int maxLines) {
if (maxLines == 1) {
Log.d("Button", "max lines was 1, setting to single line equivalent");
setLines(1);
// The act of setting horizontally scrolling to true is what disables foregrounds from
// showing...
// setHorizontallyScrolling(true);
setTransformationMethod(SingleLineTransformationMethod.getInstance());
// reset any text that may have already been set
setText(getText());
} else {
Log.d("Button", "max lines was : " + maxLines);
}
}
}
来自attrs.xml:
<declare-styleable name="Button">
<attr name="android:maxLines" format="integer" />
</declare-styleable>
正如 Keith setHorizontallyScrolling(true)
所述,滚动会使前景消失。很容易重现问题。
但是为什么横向滚动会影响前景呢?
设置水平(或垂直滚动)告诉 TextView
它包含比布局边界更大的区域。
如果您打印 scrollX
/ scrollY
值,通常它不会是零,而是一个非常大的数字,即使用户无法滚动内容也是如此。
另一方面,前景设计为随内容滚动,所以它基本上是从 canvas 的 (0, 0)
开始绘制的,而不是可见内容的左上角。所以它绘制得离实际视图边界很远。你可以打开View.java
获取onDrawForeground
的源代码。
我能做什么
- 在设置
singleline
属性或任何暗示滚动的内容后调用 setHorizontallyScrolling(false)
来禁用滚动。当心不要在尝试初始化/更新视图时启用滚动。
- 翻译前景可绘制对象,例如通过添加
scrollX
& scrollY
从 setBounds
更新边界
因为 Android 23, the android:foreground
XML attribute (and corresponding setForeground()
method) 应该对所有视图可用,而不是像以前那样只对 FrameLayout 实例可用。
然而,出于某种原因,每当我创建一个继承自 TextView -> View 的 Button 实例时,我似乎根本无法显示前景。
这是一个示例按钮定义,它似乎无法显示前景:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="Primary Full Width"
style="@style/Button.Primary" />
下面是 Button.Primary
样式的定义:
<style name="Button.Primary" parent="Widget.AppCompat.Button">
<item name="android:background">@drawable/button_primary_background_selector</item>
<item name="android:foreground">@drawable/button_primary_foreground_selector</item>
<item name="android:textAppearance">@style/TextAppearance.FF.Button</item>
<item name="android:minHeight">80dp</item>
<item name="android:height">80dp</item>
<item name="android:minWidth">200dp</item>
<item name="android:defaultWidth">288dp</item>
<item name="android:maxWidth">400dp</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:gravity">center_vertical|center_horizontal</item>
<item name="android:paddingStart">70dp</item>
<item name="android:paddingEnd">70dp</item>
<item name="android:stateListAnimator">@null</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
<item name="android:drawablePadding">10dp</item>
</style>
我已经确认在样式或 <Button>
定义中设置 foreground
属性不会改变任何东西,无论如何前景都无法显示。背景和文字都正确显示,只是缺少前景。
我也试过将前景设置为纯色而不是状态选择器可绘制,但没有成功。
在将样式元素一一删除并重新测试后,我将问题缩小到样式中包含 android:singleLine
属性。
删除此属性后,任何使用我的样式的 TextView 或按钮都可以根据需要正确显示前景可绘制对象。查看定义了 singleLine
的 TextView.java 的实现,我仍在努力确定设置此属性如何导致前景被忽略,但如果我发现,我会更新此答案。
我知道 singleLine
的使用已被弃用,但不幸的是,我仍然需要 singleLine
/ ellipsize
属性组合提供的功能,这些属性在替换为 [ 时不可用=19=]用法。
编辑: 在写完上面的答案后,我决定做更多的调查并发现更多的细节。
首先,我创建了一个从 AppCompatButton 扩展的自定义视图,这样我就可以尝试重新实现 singleLine
/ ellipsize
提供的功能(即,通过替换换行符在单行上显示文本带空格的字符,如果文本 运行 不在视图中,则还要添加省略号)。
通读 TextView source code,我发现了一段代码,当 singleLine
属性设置为 true 时调用:
private void applySingleLine(boolean singleLine, boolean applyTransformation,
boolean changeMaxLines) {
mSingleLine = singleLine;
if (singleLine) {
setLines(1);
setHorizontallyScrolling(true);
if (applyTransformation) {
setTransformationMethod(SingleLineTransformationMethod.getInstance());
}
}
...
}
所以我尝试提取这些行并添加到我自己的实现中:
private void updateMaxlinesLocally(int maxLines) {
if (maxLines == 1) {
Log.d("Button", "max lines was 1, setting to single line equivalent");
setLines(1);
setHorizontallyScrolling(true);
setTransformationMethod(SingleLineTransformationMethod.getInstance());
// reset any text that may have already been set
setText(getText());
} else {
Log.d("Button", "max lines was : " + maxLines);
}
}
但是当这段代码 运行 时,我看到了与之前相同的问题,前景可绘制对象将不再可见。
经过进一步测试,我能够将问题缩小到 setHorizontallyScrolling(true)
方法调用。如果我在我的自定义实现中只评论了这一行,我就能够保留我之前拥有的 singleLine
/ ellipsize
功能,并且前景可绘制对象会按预期显示!将其缩小到该方法仍然无法帮助我找出 TextView 基本实现中的根本原因,但如果有人对此有任何详细信息,请随时发表评论并提供更多信息。
这是我的最终自定义按钮 class,它只查看 maxLines
属性并在 maxLines
设置为 1 时模拟旧的 singleLine
功能,避免阻止前景显示的方法调用。万一有用...
public class Button extends AppCompatButton {
public Button(Context context) {
super(context);
init(context, null, 0);
}
public Button(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public Button(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
updateMaxlinesLocally(maxLines);
}
private void init(Context context, AttributeSet attributeSet, int defStyleAttr) {
int[] set = {
android.R.attr.maxLines
};
TypedArray a = context.obtainStyledAttributes(
attributeSet, R.styleable.Button);
int maxLines = a.getInt(R.styleable.Button_android_maxLines, -1);
a.recycle();
updateMaxlinesLocally(maxLines);
}
private void updateMaxlinesLocally(int maxLines) {
if (maxLines == 1) {
Log.d("Button", "max lines was 1, setting to single line equivalent");
setLines(1);
// The act of setting horizontally scrolling to true is what disables foregrounds from
// showing...
// setHorizontallyScrolling(true);
setTransformationMethod(SingleLineTransformationMethod.getInstance());
// reset any text that may have already been set
setText(getText());
} else {
Log.d("Button", "max lines was : " + maxLines);
}
}
}
来自attrs.xml:
<declare-styleable name="Button">
<attr name="android:maxLines" format="integer" />
</declare-styleable>
正如 Keith setHorizontallyScrolling(true)
所述,滚动会使前景消失。很容易重现问题。
但是为什么横向滚动会影响前景呢?
设置水平(或垂直滚动)告诉 TextView
它包含比布局边界更大的区域。
如果您打印 scrollX
/ scrollY
值,通常它不会是零,而是一个非常大的数字,即使用户无法滚动内容也是如此。
另一方面,前景设计为随内容滚动,所以它基本上是从 canvas 的 (0, 0)
开始绘制的,而不是可见内容的左上角。所以它绘制得离实际视图边界很远。你可以打开View.java
获取onDrawForeground
的源代码。
我能做什么
- 在设置
singleline
属性或任何暗示滚动的内容后调用setHorizontallyScrolling(false)
来禁用滚动。当心不要在尝试初始化/更新视图时启用滚动。 - 翻译前景可绘制对象,例如通过添加
scrollX
&scrollY
从
setBounds
更新边界