*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
好久没有写过文章了,借着前几天 UI 提出的一个“简单”的动画需求,写写自己实现的
原本想着改改动画就算了,结果写完之后发现把自定义 ViewGroup 该用的知识差不多都涉及到了,那么就写篇文章来一起复习一下自定义一个 ViewGroup 的流程。接下来看看我们要实现的效果,咱们就开始进入正题了。
MenuDisplayView.gif
简单的分析
需求里的 menu item 其实没有像 demo 上显示的那样——都是同样类型的,事实上有直接添加在 xml 布局里面的,也有从接口请求下来之后再添加进去的。所以需要定义一个 ViewGroup,不管子 View 是啥样子的往里面添加就是。
ViewGroup 需要两种不同的布局方式:一种是展开时候的,看上去和 LinearLayout 一致;另一种是收起时候的,看上去像我们常用的 ViewPager 轮播一样。根据两种不同的模式,需要实现两种不同的测量和布局方式。
收起和展开按钮需要在 EggacheDisplayView 构造的时候动态添加进去。
简单的准备
在讲具体的 onMeasure 和 onLayout 方法之前,需要简单的看下构造方法。
public EggacheDisplayView(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(
attrs, R.styleable.EggacheDisplayView, 0, 0);
mBtnSpacing = typedArray.getDimensionPixelSize(
R.styleable.EggacheDisplayView_btn_spacing, dpToPx(context, 10));
int layoutCollapse = typedArray.getResourceId(
R.styleable.EggacheDisplayView_collapse_layout,
R.layout.layout_collapse_button);
int layoutExpand = typedArray.getResourceId(
R.styleable.EggacheDisplayView_expand_layout, R.layout.layout_expand_button);
mClickLoopToExpand = typedArray.getBoolean(
R.styleable.EggacheDisplayView_click_loop_to_expand, false);
typedArray.recycle();
createCollapseAndExpandButton(context, layoutCollapse, layoutExpand);
}
这里只有简单的几个自定义属性,mBtnSpacing 是每个子 view 之间的间隔,后面测量和布局的时候会用到,至于两个 layout 则分别是我们 createCollapseAndExpandButton 方法要添加进去的两个按钮。
另外定义了一个枚举类,表示是在展开状态还是在轮播状态。
public enum DisplayMode {
LIST,
LOOP
}
onMeasure 方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
mMaxButtonWidth = 0;
mMaxButtonHeight = 0;
// 1.
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) continue;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
}
// 2.
if (mDisplayMode == DisplayMode.LIST) {
// DisplayMode.LIST 相当于 LinearLayout
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) continue;
height += child.getMeasuredHeight();
// 最后一个
if (i != getChildCount() - 1) {
height += mBtnSpacing;
}
}
} else if (mDisplayMode == DisplayMode.LOOP) {
// DisplayMode.LOOP 高度只包括 最高的子 view 的高度 + mBtnExpand 的高度
height += mMaxButtonHeight + mBtnSpacing + mBtnExpand.getMeasuredHeight();
}
width = mMaxButtonWidth + getPaddingLeft() + getPaddingRight();
height += getPaddingTop() + getPaddingBottom();
// 3.
if (getLayoutParams().width == LayoutParams.MATCH_PARENT) {
width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
}
if (getLayoutParams().height == LayoutParams.MATCH_PARENT) {
height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
}
setMeasuredDimension(width, height);
}
从之前的分析来看,其实不适合继承 Lin