由于产品是个果婊,有时候为了统一app风格,需要被迫使用ios风格的系统控件,比较常见的就是ios 的底部弹出菜单,在网上找了很久都没有找到还原度很高的,就自己动手写了,为了以防万一以后再有这类需求,这里把大致实现思路和代码献上,有需要的自己copy就行了。
成品图:
UI层级
具体都写在图上了,考虑到性能因素,viewGroup继承自FrameLayout,contentGroup、menuList继承自LinearLayout、CancelButton继承自TextView。
核心思想
- 采用伪Build模式编写,之所以说伪Build是因为没有严格按照模式框架来编写,但是对外调用和Build相似。
- 运用了Toast模式,直接将布局添加到Window下DecorView中,可以在任意Activity中调用,不需要改动原本View或者进行View绑定。
源代码
class IosBottomListWindow(private val activity: Activity) {
private val shadowMax = 0xa0
private var majorTitle: TextView? = null
private var title: TextView? = null
private val viewGroup: FrameLayout
private val contentGroup: LinearLayout
private val menuList: LinearLayout
private val commonMargin = dpToPx(10f, activity).toInt()
private val itemMargin = dpToPx(16f, activity).toInt()
private var contentLayoutHeight = 0
var isShow = false
init {
viewGroup = initViewGroup()
contentGroup = initContentGroup()
menuList = initMenuList()
viewGroup.addView(contentGroup)
contentGroup.addView(menuList)
}
fun show() {
if (!isShow) {
(activity.window.decorView as ViewGroup).addView(viewGroup)
contentGroup.apply {
post {
contentLayoutHeight = measuredHeight
translationY = contentLayoutHeight.toFloat()
visibility = View.VISIBLE
startAnimator(true)
}
}
isShow = true
}
}
fun dismiss() {
if (isShow) {
startAnimator(false, object : SimpleAnimListener() {
override fun onAnimationEnd(animation: Animator?) {
(activity.window.decorView as ViewGroup).removeView(viewGroup)
}
})
isShow = false
}
}
/**
* 设置主标题
*/
fun setMajorTitle(text: String): IosBottomListWindow {
majorTitle = TextView(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT).apply {
val margin = dpToPx(20f, activity).toInt()
if (title == null) {
setMargins(0, margin, 0, margin)
} else {
setMargins(0, margin, 0, 0)
}
}
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
setTextColor(Color.parseColor("#8f8f8f"))
typeface = Typeface.DEFAULT_BOLD
setText(text)
}
menuList.addView(majorTitle, 0)
return this
}
/**
* 设置副标题
*/
fun setTitle(text: String): IosBottomListWindow {
title = TextView(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT).apply {
val margin = dpToPx(20f, activity).toInt()
if (majorTitle == null) {
setMargins(0, margin, 0, margin)
} else {
setMargins(0, 0, 0, margin)
}
}
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
setTextColor(Color.parseColor("#8f8f8f"))
setText(text)
}
if (majorTitle == null) {
menuList.addView(title, 0)
} else {
menuList.addView(title, 1)
}
return this
}
/**
* 设置子项
*/
fun setItem(text: String, textColor: Int = 0, itemClickListener: () -> Unit): IosBottomListWindow {
val textView = TextView(activity)
textView.apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setPadding(0, itemMargin, 0, itemMargin)
}
gravity = Gravity.CENTER
setBackgroundResource(R.drawable.command_bg_click_xml)
setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
if (textColor == 0) {
setTextColor(Color.parseColor("#FF3B30"))
} else {
try {
setTextColor(textColor)
} catch (e: Throwable) {
e.printStackTrace()
}
}
setText(text)
setOnClickListener {
itemClickListener.invoke()
dismiss()
}
}
val lineView = View(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 1)
setBackgroundColor(Color.parseColor("#dcdbdf"))
}
menuList.addView(lineView)
menuList.addView(textView)
return this
}
/**
* 设置按钮
*/
fun setCancelButton(text: String, textColor: Int = 0): IosBottomListWindow {
val cancelView = TextView(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(commonMargin, 0, commonMargin, commonMargin)
}
setPadding(0, itemMargin, 0, itemMargin)
background = resources.getDrawable(R.drawable.bottom_btn_click_xml, null)
if (textColor == 0) {
setTextColor(Color.parseColor("#FF3B30"))
} else {
try {
setTextColor(textColor)
} catch (e: Throwable) {
e.printStackTrace()
}
}
gravity = Gravity.CENTER
setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
setText(text)
typeface = Typeface.DEFAULT_BOLD
setOnClickListener {
dismiss()
}
}
contentGroup.addView(cancelView)
return this
}
/**
* 初始化容器
* 背景阴影容器
*/
private fun initViewGroup() = FrameLayout(activity).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)
setOnClickListener {
dismiss()
}
}
/**
* 初始化内容容器
* 滑动动画载体
*/
private fun initContentGroup() = LinearLayout(activity).apply {
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.BOTTOM
bottomMargin = getBottomStatusHeight(activity)
visibility = View.INVISIBLE
}
orientation = LinearLayout.VERTICAL
}
/**
* 初始化菜单列表
*/
private fun initMenuList() = LinearLayout(activity).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(commonMargin, 0, commonMargin, commonMargin)
}
gravity = Gravity.CENTER_HORIZONTAL
orientation = LinearLayout.VERTICAL
background = activity.resources.getDrawable(R.drawable.shape_bottom_list_menu, null)
}
private fun startAnimator(enterType: Boolean, listener: Animator.AnimatorListener? = null) {
viewGroup.post {
ValueAnimator().apply {
if (enterType) {
setFloatValues(contentLayoutHeight.toFloat(), 0f)
} else {
setFloatValues(0f, contentLayoutHeight.toFloat())
}
duration = 300
interpolator = DecelerateInterpolator()
addUpdateListener {
val value = it.animatedValue as Float
contentGroup.translationY = value
setShadow()
}
listener?.let {
addListener(it)
}
start()
}
}
}
private fun setShadow() {
val ratio = contentGroup.translationY / contentLayoutHeight
val shadow = (shadowMax * (1 - ratio)).toInt()
if (shadow >= 16) {
viewGroup.setBackgroundColor(Color.parseColor("#${shadow.toString(16)}000000"))
}
}
}
用到资源文件
shape_bottom_list_menu.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="15dp"/>
<solid android:color="#f1f1f1"/>
</shape>
bottom_btn_click.xml
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/ColorRipe">
<item android:drawable="@drawable/shape_bottom_list_button"/>
</ripple>
shape_bottom_list_button.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="15dp"/>
<solid android:color="#ffffff"/>
</shape>
调用
IosBottomListWindow(this)
.setTitle("上传头像")
.setMajorTitle("上传头像")
.setItem("从相册", resources.getColor(R.color.color_c10)) {
getPhotoFromAlbum()
}
.setItem("从相机") {
dispatchTakePictureIntent()
}
.setCancelButton("取消")
.show()
最后
如果该文章对你有帮助,希望能顺手点个赞或者收藏。Σ(*゚д゚ノ)ノ