Android自定义View XML设置foreground后getForeground返回null及点击效果失效问题
问题原因
- 最核心的原因是自定义View与加载的布局结构不匹配:你把
foreground属性写在了填充布局的根<FrameLayout>节点上,而你重写setClickable、调用getForeground()的对象是自定义View实例本身。如果你的自定义View是继承FrameLayout,且加载布局时没有用<merge>作为根节点,那么你写的这个根FrameLayout会作为子View被加入到自定义View中,你调用自定义View的getForeground()自然拿不到子View配置的前景,返回null。 - 如果你在自定义View构造函数中就提前调用了
setClickable(),此时布局还未完成inflate,前景Drawable还未被解析加载,也会返回null。 - 若你的自定义View不是继承FrameLayout、LinearLayout等原生支持foreground属性的容器类,且minSdk<23,View类本身没有foreground的原生实现,调用
getForeground()也会始终返回null。
解决方案
你可以根据业务场景选择以下任意一种方案解决:
- 方案1:调整布局结构,合并属性到自定义View
把你当前填充布局的根节点从<FrameLayout>改成<merge>,将原来写在根FrameLayout上的foreground、clickable、focusable等属性,移动到你布局文件中引用自定义View的标签上,或者在自定义View的构造函数中主动调用setForeground(ContextCompat.getDrawable(context, R.drawable.ripple_colors))设置前景,之后你调用自定义View的getForeground()就能拿到正确的实例,你重写的setClickable逻辑就可以正常运行。 - 方案2:直接操作子FrameLayout的前景
如果你不想修改现有布局结构,在自定义View加载完布局后,先拿到你inflate出来的根FrameLayout实例,后续setClickable逻辑都作用在这个子FrameLayout上即可:// 自定义View构造函数中inflate后拿到子FrameLayout实例 private FrameLayout mRootContainer; // inflate代码示例 mRootContainer = (FrameLayout) LayoutInflater.from(context).inflate(R.layout.your_layout, this, false); addView(mRootContainer); @Override public void setClickable(boolean clickable) { super.setClickable(clickable); mRootContainer.setClickable(clickable); Drawable foreground = mRootContainer.getForeground(); if (foreground != null) { foreground.mutate().setVisible(clickable, false); } } - 方案3:替代实现点击效果控制
如果你只是要控制ripple的显隐,不需要修改前景可见性,也可以通过自定义状态实现:- 在attrs.xml中定义自定义状态
<attr name="state_ripple_enabled" format="boolean" /> - 重写自定义View的
onCreateDrawableState方法,把自定义状态加入到状态集:
private static final int[] STATE_RIPPLE_ENABLED = {R.attr.state_ripple_enabled}; private boolean mRippleEnabled = true; @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (mRippleEnabled) { mergeDrawableStates(drawableState, STATE_RIPPLE_ENABLED); } return drawableState; } public void setRippleEnabled(boolean enabled) { mRippleEnabled = enabled; refreshDrawableState(); setClickable(enabled); }- 把你的ripple选择器根节点加上自定义状态判断,就可以通过
setRippleEnabled方法直接控制ripple是否生效,不需要操作前景Drawable。
- 在attrs.xml中定义自定义状态
内容的提问来源于stack exchange,提问作者JustAnEuropean
相关产品推荐
相关产品推荐

