The effect
Analysis of the
Click on the event to trigger the view to expand and collapse, and the first child view remains in the collapse state. This expansion and collapse is actually the height change of the view, so as long as the height is controlled, it can be very simple to achieve this effect.
steps
- 1. Initialize the direction of parameters
- 2. Calculate the height according to the animation execution progress
Initialize the
class ExpandLinearLayout : LinearLayout {
// Whether to expand, the default expansion
private var isOpen = true
// The height of the first child view
private var firstChildHeight = 0
// The total height of all subviews
private var allChildHeight = 0
/** * Request relayout when animation values change */
private var animPercent: Float = 0f
constructor(context: Context) : super(context) {
initView()
}
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
initView()
}
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(
context,
attributeSet,
defStyleAttr
) {
initView()
}
private fun initView(a) {
// Change the width of the field
orientation = VERTICAL
animPercent = 1f
isOpen = true}}Copy the code
Define a class, ExpandLinearLayout, that inherits from LinearLayout, or any other view.
Then override the constructor and call the initView method inside the constructor.
In the initView method, we initialize parameters such as orientation and default expansion.
Calculate height
Ok, that’s the point.
Since only the height of the view itself changes, we just need to rewrite onMeasure to calculate the height.
Look at onMeasure:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// Reset the height
allChildHeight = 0
firstChildHeight = 0
if (childCount > 0) {
// Traverse to calculate the height
for (index in 0 until childCount) {
// The measuredHeight, margin, and so on should also be counted
if (index == 0) {
firstChildHeight = getChildAt(index).measuredHeight
+getChildAt(index).marginTop + getChildAt(index).marginBottom
+this.paddingTop + this.paddingBottom
}
// The actual use may include padding, etc
allChildHeight += getChildAt(index).measuredHeight + getChildAt(index).marginTop + getChildAt(index).marginBottom
// The last one adds the padding of the current view itself
if (index == childCount - 1) {
allChildHeight += this.paddingTop + this.paddingBottom
}
}
// Set height based on expansion
if (isOpen) {
setMeasuredDimension(
widthMeasureSpec,
firstChildHeight + ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
} else {
setMeasuredDimension(
widthMeasureSpec,
allChildHeight - ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
}
}
}
Copy the code
There are two steps in onMeasure:
- Traverse to calculate the height
// Traverse to calculate the height
for (index in 0 until childCount) {
// The measuredHeight, margin, and so on should also be counted
if (index == 0) {
firstChildHeight = getChildAt(index).measuredHeight
+getChildAt(index).marginTop + getChildAt(index).marginBottom
+this.paddingTop + this.paddingBottom
}
// The actual use may include padding, etc
allChildHeight += getChildAt(index).measuredHeight + getChildAt(index).marginTop + getChildAt(index).marginBottom
// The last one adds the padding of the current view itself
if (index == childCount - 1) {
allChildHeight += this.paddingTop + this.paddingBottom
}
}
Copy the code
Let’s look at the first if judgment, which is measuring the height of the first child view, and notice that in addition to measuredHeight, margin has to be added, and the padding of the parent view has to be added, because if the parent view has a larger padding, The view might not show up when you fold it up.
And then the total height, same thing.
Let’s look at the last if judgment. Again, after calculating the total height, we need to add the upper and lower padding of the parent view to make the full height.
The first judgment can be understood as the height of the folded state, and the second judgment as the height of the expanded state.
- Unfurl logic
// Set height based on expansion
if (isOpen) {
setMeasuredDimension(
widthMeasureSpec,
firstChildHeight + ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
} else {
setMeasuredDimension(
widthMeasureSpec,
allChildHeight - ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
}
Copy the code
Since the first child view is reserved for display, the height of the first child view is subtracted from the remaining height.
The remaining height can be easily calculated, but how to display it without obfuscating.
Add an animation here, and calculate according to the progress of the animation.
Expand: Height of first child view + remaining height × 0 to 1 Float animation value
Pack up: Total height – remaining height × 0 to 1 Float animation value
Author: yechaoa
animation
Write a method to control unfurl and animate it when it is unfurl.
fun toggle(a): Boolean{ isOpen = ! isOpen startAnim()return isOpen
}
/** * Change the value of the animPercent property from 0 to 1 */
@SuppressLint("AnimatorKeep")
private fun startAnim(a) {
//ofFloat, of xxxX is determined by parameter type
//1, animation object, that is, the current view. 2. Name of the animation property. 3, the starting value. 4, target value.
val animator = ObjectAnimator.ofFloat(this."animPercent".0f.1f)
animator.duration = 500
animator.start()
}
Copy the code
And modify our animation parameters:
/** * Request relayout when animation values change */
private var animPercent: Float = 0f
set(value) {
field = value
requestLayout()
}
Copy the code
When set value, call requestLayout() and re-execute onMeasure.
call
- xml
<com.yechaoa.customviews.expand.ExpandLinearLayout
android:id="@+id/ell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#f5f5f5"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/app_name"
android:textColor="@android:color/holo_red_dark"
android:textSize="20sp" />
...
</com.yechaoa.customviews.expand.ExpandLinearLayout>
Copy the code
- code
ll_btn.setOnClickListener {
val toggle = ell.toggle()
tv_tip.text = if (toggle) "Fold" else "A"
}
Copy the code
extension
The transverse
: Change the calculated height to the calculated widthhighly
: You can control the retention height based on XML custom attributes
conclusion
In general, the effect is more practical, the difficulty coefficient is not high, can expand their own to further improve.
If it helps you a little bit, give it a like ^ _ ^
Github
Github.com/yechaoa/Cus…