完全可以创建自定义容器视图,并且这是可行的和被鼓励的。在Android中,这被称为复合控件。
public class MyCustomView extends RelativeLayout {
private LinearLayout mContentView;
public MyCustomView(Context context) {
this(context, null);
}
public MyCustomView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context).inflate(R.layout.custom_layout, this);
mContentView = (LinearLayout) findViewById(R.id.content);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if(mContentView == null){
super.addView(child, index, params);
} else {
mContentView.addView(child, index, params);
}
}
}
您可以覆盖尽可能多的
addView()
版本,但最终它们都会回调我放置在示例中的版本。仅覆盖此方法将使框架将其XML标记内找到的所有子项传递给特定的子容器。
然后按如下修改XML:
res/layout/custom_layout.xml<merge>
<SomeView />
<SomeOtherView />
<!-- maybe more layout stuff here later -->
<LinearLayout
android:id="@+id/content" />
</merge>
使用 <merge>
的原因是为了简化布局层次结构。所有子视图都将附加到您的自定义类,即 RelativeLayout
。如果不使用 <merge>
,则最终会得到一个与所有子项关联的 RelativeLayout
附加到另一个 RelativeLayout
的情况,这可能会导致问题。
Kotlin 版本:
private fun expand(view: View) {
val parentWidth = (view.parent as View).width
val matchParentMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY)
val wrapContentMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
view.measure(matchParentMeasureSpec, wrapContentMeasureSpec)
val targetHeight = view.measuredHeight
view.isVisible = true
val animation: Animation = getExpandAnimation(view, targetHeight)
view.startAnimation(animation)
}
private fun getExpandAnimation(
view: View,
targetHeight: Int
): Animation = object : Animation() {
override fun applyTransformation(
interpolatedTime: Float,
transformation: Transformation
) {
view.layoutParams.height =
if (interpolatedTime == 1f) {
LayoutParams.WRAP_CONTENT
} else {
(targetHeight * interpolatedTime).toInt()
}
view.requestLayout()
}
override fun willChangeBounds(): Boolean {
return true
}
}.apply {
duration = getDuration(targetHeight, view)
}
private fun collapse(view: View) {
val initialHeight = view.measuredHeight
val animation: Animation = getCollapseAnimation(view, initialHeight)
view.startAnimation(animation)
}
private fun getCollapseAnimation(
view: View,
initialHeight: Int
): Animation = object : Animation() {
override fun applyTransformation(
interpolatedTime: Float,
transformation: Transformation
) {
if (interpolatedTime == 1f) {
view.isVisible = false
} else {
view.layoutParams.height =
initialHeight - (initialHeight * interpolatedTime).toInt()
view.requestLayout()
}
}
override fun willChangeBounds(): Boolean = true
}.apply {
duration = getDuration(initialHeight, view)
}
private fun getDuration(initialHeight: Int, view: View) =
(initialHeight / view.context.resources.displayMetrics.density).toLong()
Card
,扩展LinearLayout
,其中包含一个标题TextView
和另一个包含卡片子项的LinearLayout
)。然而,在构造函数中填充card.xml时,调用了Card.addView()
,它试图将卡片本身添加到其子项之一,导致NullPointerException
... - SanderMyCustomView
扩展RelativeLayout
并且子项添加到的容器是LinearLayout
时,为这些子项设置的XML边距不被尊重。然而,将MyCustomView
更改为扩展LinearLayout
可以按预期尊重子项边距。是否需要在这两个ViewGroup的LayoutParams之间进行任何转换以支持此功能? - Matthew