在 Android UI开发过程中,我们会觉得非常繁琐的事情是什么呢?分隔线、圆角、边框、阴影、点击态等,在以往的实践中,我们都是通过 drawable 去实现的,非常麻烦。以分隔线为例,如果左右都打通,那还能复用,但现在的设计师都喜欢左边或右边有一定的 inset,而 inset 的值并非一成不变的,这不经意间就使得项目添加了数不清的 drawable,因此目前大多数人都开始尝试不使用 drawble 去解决这些问题了,QMUI 也不例外。
QMUI 1.1.0 版本带来的新的组件套装 QMUILayout (QMUILinearLayout、QMUIRelativeLayout、QMUIFramelayout),这篇文章将介绍 QMUI 提出的解决方法。
点击态
点击态最为简单,QMUI 早已经提出用 alpha 来表示点击态,因此提供了了 QMUIAlpha 系列,QMUILayout 继承自 QMUIAlpha 系列,但默认禁止了 alpha 行为, 你可以通过以下两个方法开启:
// change alpha when pressed setChangeAlphaWhenPress(boolean changeAlphaWhenPress); // change alpha when disabled setChangeAlphaWhenDisable(boolean changeAlphaWhenDisable)
当然,点击时背景变灰也是非常常见的。qmui 最开始提供了一系列的用于 list item 的 drawable, 现在我们基本只需要 qmui_s_list_item_bg_with_border_none
, 因为现在分割线不再受背景控制了。
分隔线
分隔线是平常使用得非常多的,上下分隔下用的较多,左右分隔线用得比较少,QMUILayout 都提供了 xml 和 java 代码的支持:
<declare-styleable name="QMUILayout"> ... <attr name="qmui_bottomDividerHeight" format="dimension"/> <attr name="qmui_bottomDividerColor" format="color|reference"/> <attr name="qmui_bottomDividerInsetLeft" format="dimension"/> <attr name="qmui_bottomDividerInsetRight" format="dimension"/> <attr name="qmui_topDividerHeight" format="dimension"/> <attr name="qmui_topDividerColor" format="color|reference"/> <attr name="qmui_topDividerInsetLeft" format="dimension"/> <attr name="qmui_topDividerInsetRight" format="dimension"/> <attr name="qmui_leftDividerWidth" format="dimension"/> <attr name="qmui_leftDividerColor" format="color|reference"/> <attr name="qmui_leftDividerInsetTop" format="dimension"/> <attr name="qmui_leftDividerInsetBottom" format="dimension"/> <attr name="qmui_rightDividerWidth" format="dimension"/> <attr name="qmui_rightDividerColor" format="color|reference"/> <attr name="qmui_rightDividerInsetTop" format="dimension"/> <attr name="qmui_rightDividerInsetBottom" format="dimension"/> ... </declare-styleable>
四个维度的支持,使用非常方便, java 接口:
/** * config the top divider */ void updateTopDivider(int topInsetLeft, int topInsetRight, int topDividerHeight, int topDividerColor); /** * show top divider, and hide others */ void onlyShowTopDivider(int topInsetLeft, int topInsetRight, int topDividerHeight, int topDividerColor); // same for others
其实现原理也很简单,就是在 dispatchDraw 里主动 draw 上去的,那我们为什么是在 dispatchDraw 而不是在 onDraw 里操作呢? 因为我们不想分隔线被子元素、背景等因素遮挡。
阴影、圆角、边框
这是 QMUILayout 最令人兴奋的地方,大家可以在 QMUIDemo 上体验下效果:
其实原理很简单,利用 android 5.0 后提供的 elevation 来实现的, 大多数人通过阅读官方文档或者别人的教材,了解了 elevation 可以控制 shadow 的大小,但是很少人知道 shadow 还会被另外一个因素所控制:outline 的 alpha。 有了这两个因素,可以更精确的控制 shadow 的深浅,能无限接近设计师的阴影要求,当然,除了阴影颜色,那是 QMUILayout 所不能控制的。
利用 outline 还可以用来实现圆角,以前我们用背景去实现圆角效果,但你需要非常小心的保护圆角背景不被子元素所覆盖,但 outline 不会有这个问题。outline 如此强大,如果还不知道它,那么赶紧去通过View.setOutlineProvider()
了解一下它吧。
但 outline 也有缺陷的,它没办法做到四个圆角分别控制,现实使用过程中,有一个圆角或三个圆角的情况比较少,所以我在 QMUILayout 中支持了 显示两个圆角的方法,但可惜的是会丢失阴影,现实总是不尽人意,尽自己最大努力就好。
另外一个比较关心的话题是, 4.x的手机怎么办?网页重构早就讲究降级兼容,所以我也认为没有必要让 4.x 和 5.x+ 保持同样的用户体验。因此在 4.x 下,阴影肯定是没办法使用的,只能使用圆角和边框,要使用它们,我们需要提供一个额外的参数: qmui_outerNormalColor
, 这个参数是什么意思呢?这首先要理解圆角在 4.x 上的实现: 它的实现是在 dispatchDraw 后,draw 上一层 mask, 然后镂空中间部分,因此 qmui_outerNormalColor
就是 mask 的颜色,一般要与 QMUILayout 外部环境相同, 这样在用户眼中,看到的就是圆角了。如果你没有提供这个属性,那么圆角将不会生效。此外,它也无法解决 dialog 这种外部环境透明的场景。
接下来看看提供的非常有用的 API:
/** * use the shadow elevation from the theme */ void setUseThemeGeneralShadowElevation(); /** * See {@link android.view.View#setElevation(float)} * * @param elevation */ void setShadowElevation(int elevation); /** * See {@link View#getElevation()} * * @return */ int getShadowElevation(); /** * set the outline alpha, which will change the shadow * * @param shadowAlpha */ void setShadowAlpha(float shadowAlpha); /** * get the outline alpha we set * * @return */ float getShadowAlpha(); /** * set the layout radius * @param radius */ void setRadius(int radius); /** * set the layout radius with one or none side been hidden * @param radius * @param hideRadiusSide */ void setRadius(int radius, @QMUILayoutHelper.HideRadiusSide int hideRadiusSide); /** * get the layout r * @return */ int getRadius(); /** * the shadow elevation only work after L, so we provide a downgrading compatible solutions for android 4.x * usually we use border, but the border may be redundant for android L+. so will not show border default, * if your designer like the border exists with shadow, you can call setShowBorderOnlyBeforeL(false) * * @param showBorderOnlyBeforeL */ void setShowBorderOnlyBeforeL(boolean showBorderOnlyBeforeL); /** * in some case, we maybe hope the layout only have radius in one side. * but there is no convenient way to write the code like canvas.drawPath, * so we take another way that hide one radius side * * @param hideRadiusSide */ void setHideRadiusSide(@HideRadiusSide int hideRadiusSide); /** * get the side that we have hidden the radius * * @return */ int getHideRadiusSide(); /** * this method will determine the radius and shadow. * * @param radius * @param shadowElevation * @param shadowAlpha */ void setRadiusAndShadow(int radius, int shadowElevation, float shadowAlpha); /** * this method will determine the radius and shadow with one or none side be hidden * * @param radius * @param hideRadiusSide * @param shadowElevation * @param shadowAlpha */ void setRadiusAndShadow(int radius, @HideRadiusSide int hideRadiusSide, int shadowElevation, float shadowAlpha); /** * border color, if you don not set it, the layout will not draw the border * * @param borderColor */ void setBorderColor(@ColorInt int borderColor); /** * border width, default is 1px, usually no need to set * * @param borderWidth */ void setBorderWidth(int borderWidth);
QMUILayout 所有的 API 都由 IQMUILayout
所规范, 由 QMUILayoutHelper
所实现,如果你需要为自己的自定义 view 实现这些方法, 那么你可以在你的类中引入 QMUILayoutHelper
,具体可以参考 QMUILinearLayout
/QMUIFrameLayout
/QMUIRelativeLayout
。
最后一点,关于性能,View.setOutlineProvider()
会存在性能消耗,不过就我们的测试数据统计,这点消耗是可以容忍的。目前 QMUILayout 已经为读书项目服务了几个版本了,稳定性应该是蛮好的,希望大家喜欢。