效果

分析
效果圖來看,點擊事件觸發view的展開收起,并在收起狀態下保留了第一個子view顯示,這個展開收起其實就是view的高度變化,所以只要控制好高度,就能很簡單的實作這個效果,
步驟
- 1.初始化引數 設定方向等
- 2.根據影片執行進度計算高度
初始化
class ExpandLinearLayout : LinearLayout {
//是否展開,默認展開
private var isOpen = true
//第一個子view的高度,即收起保留高度
private var firstChildHeight = 0
//所有子view高度,即總高度
private var allChildHeight = 0
/**
* 影片值改變的時候 請求重新布局
*/
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() {
//橫向的話 稍加修改計算寬度即可
orientation = VERTICAL
animPercent = 1f
isOpen = true
}
}
定義一個類ExpandLinearLayout ,繼承自LinearLayout,當然也可以是其他的view,
然后重寫構造方法,并在構造方法里面呼叫initView方法,
在initView方法中,我們對一些引數進行初始化操作,比如方向、默認展開,
計算高度
ok,這個就是重點了,
因為只是view本身高度的變化,我們只需要重寫onMeasure去計算高度即可,
來看onMeasure:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//重置高度
allChildHeight = 0
firstChildHeight = 0
if (childCount > 0) {
//遍歷計算高度
for (index in 0 until childCount) {
//這個地方實際使用中除了measuredHeight,以及margin等,也要計算在內
if (index == 0) {
firstChildHeight = getChildAt(index).measuredHeight
+getChildAt(index).marginTop + getChildAt(index).marginBottom
+this.paddingTop + this.paddingBottom
}
//實際使用時或包括padding等
allChildHeight += getChildAt(index).measuredHeight + getChildAt(index).marginTop + getChildAt(index).marginBottom
//最后一條的時候 加上當前view自身的padding
if (index == childCount - 1) {
allChildHeight += this.paddingTop + this.paddingBottom
}
}
// 根據是否展開設定高度
if (isOpen) {
setMeasuredDimension(
widthMeasureSpec,
firstChildHeight + ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
} else {
setMeasuredDimension(
widthMeasureSpec,
allChildHeight - ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
}
}
}
onMeasure里面也是分了兩個步驟的:
- 遍歷計算高度
//遍歷計算高度
for (index in 0 until childCount) {
//這個地方實際使用中除了measuredHeight,以及margin等,也要計算在內
if (index == 0) {
firstChildHeight = getChildAt(index).measuredHeight
+getChildAt(index).marginTop + getChildAt(index).marginBottom
+this.paddingTop + this.paddingBottom
}
//實際使用時或包括padding等
allChildHeight += getChildAt(index).measuredHeight + getChildAt(index).marginTop + getChildAt(index).marginBottom
//最后一條的時候 加上當前view自身的padding
if (index == childCount - 1) {
allChildHeight += this.paddingTop + this.paddingBottom
}
}
來看第一個if判斷,記錄了第一個子view的高度,這里需要注意,除了measuredHeight,margin也要算上,而且父view的內邊距padding也要加上,因為如果父view的padding很大的話,收起時view可能會顯示不出來的,
然后就是總高度的計算,道理同上,
來看最后一個if判斷,同樣總高度計算完之后也要加上父view的上下padding,才是完整的高度,
第一個判斷可以理解為收起狀態的高度,第二個判斷可以理解為展開狀態的高度,
- 展開收起邏輯
// 根據是否展開設定高度
if (isOpen) {
setMeasuredDimension(
widthMeasureSpec,
firstChildHeight + ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
} else {
setMeasuredDimension(
widthMeasureSpec,
allChildHeight - ((allChildHeight - firstChildHeight) * animPercent).toInt()
)
}
因為第一個子view是保留顯示的,所以在計算的時候都需要減去第一個子view的高度,就是剩余高度,
剩余高度可以很簡單的計算出來,但是如何在顯示的時候不突兀呢,
這里加一個影片,根據影片的執行進度來計算,
展開:第一個子view的高度 + 剩余高度 × 0到1的Float影片值
收起:總高度 - 剩余高度 × 1到0的Float影片值
author:yechaoa
影片
寫一個方法控制展開收起,并在展開收起的時候執行影片,
fun toggle(): Boolean {
isOpen = !isOpen
startAnim()
return isOpen
}
/**
* 執行影片的時候 更改 animPercent 屬性的值 即從0-1
*/
@SuppressLint("AnimatorKeep")
private fun startAnim() {
//ofFloat,of xxxX 根據引數型別來確定
//1,影片物件,即當前view,2.影片屬性名,3,起始值,4,目標值,
val animator = ObjectAnimator.ofFloat(this, "animPercent", 0f, 1f)
animator.duration = 500
animator.start()
}
并修改我們的影片引數:
/**
* 影片值改變的時候 請求重新布局
*/
private var animPercent: Float = 0f
set(value) {
field = value
requestLayout()
}
set value的時候呼叫requestLayout(),重新執行onMeasure,
呼叫
- 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>
- 代碼
ll_btn.setOnClickListener {
val toggle = ell.toggle()
tv_tip.text = if (toggle) "收起" else "展開"
}
擴展
橫向:計算高度變成計算寬度即可高度:可以根據xml自定義屬性來控制保留高度
總結
總的來說,效果還是比較實用的,難度系數也不高,可以根據擴展自己去進一步完善,
如果對你有一點點幫助,點個贊唄 ^ _ ^
Github
https://github.com/yechaoa/CustomViews
CSDN認證博客專家
Android Jetpack
Flutter
小程式
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/261081.html
標籤:其他
上一篇:IOS-SDK接入篇
