Android 自定义下拉菜单的实现(基于PopupWindow+RecyclerView)
文章目录
- 一、引言
- 二、效果
- 三、代码实现
- 四、结语
一、引言
安卓自带的Spinner局限性较大,基本不能满足开发样式要求,当前又没有成熟的相关框架,所以决定自己使用PopupWindow实现一个下拉菜单
二、效果
三、代码实现
布局:
新建xml文件:layout_dropdown_menu
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android=""android:layout_width="match_parent"android:layout_height="48dp"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"android:layout_marginLeft="12dp"android:id="@+id/ly_dropdown_tab"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/black"android:textSize="16sp"android:layout_gravity="center_vertical"android:id="@+id/tv_dropdown_title"/><ImageViewandroid:layout_width="12dp"android:layout_height="12dp"android:layout_gravity="center_vertical"android:layout_marginLeft="4dp"android:src="@drawable/ic_up"android:layout_marginStart="4dp"android:id="@+id/iv_dropdown_icon"/></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"android:layout_alignParentRight="true"android:layout_marginRight="12dp"android:layout_alignParentEnd="true"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/black"android:textSize="16sp"android:layout_gravity="center_vertical"android:text="@string/choose"/><ImageViewandroid:layout_width="16dp"android:layout_height="16dp"android:src="@drawable/ic_choose"android:layout_gravity="center_vertical"android:layout_marginLeft="4dp"android:layout_marginStart="4dp" /></LinearLayout></RelativeLayout>
在需要使用的地方引用
<include layout="@layout/layout_dropdown_menu"/>
为下面布局添加一个遮罩的View:
ps:比如我下拉框下面是一个rv,使用FrameLayout布局,为rv添加一个相同大小的View,来实现遮罩效果(当然有更好的实现方式欢迎私信我)
<include layout="@layout/layout_dropdown_menu"/><com.scwang.smart.refresh.layout.SmartRefreshLayoutandroid:id="@+id/refreshLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="8dp"><com.scwang.smart.refresh.header.ClassicsHeaderandroid:layout_width="match_parent"android:layout_height="wrap_content" /><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><androidx.recyclerview.widget.RecyclerViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/rv"/><Viewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/mask_view"android:visibility="gone"android:background="@color/shadow_bg"/></FrameLayout></com.scwang.smart.refresh.layout.SmartRefreshLayout >
最后添加动画文件:
style.xm
<?xml version="1.0" encoding="utf-8"?>
<resources><style name="popwin_anim" parent="android:Animation"><item name="android:windowEnterAnimation">@anim/pop_enter_anim</item><item name="android:windowExitAnimation">@anim/pop_exit_anim</item></style>
</resources>
pop_enter_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android=""><scaleandroid:duration="200"android:fromXScale="1.0"android:fromYScale="0.0"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:pivotX="50%"android:pivotY="0%"android:toXScale="1.0"android:toYScale="1.0" ></scale><alphaandroid:duration="180"android:fromAlpha="0.0"android:toAlpha="1.0" />
</set>
pop_exit_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android=""><scaleandroid:duration="200"android:fromXScale="1.0"android:fromYScale="1.0"android:interpolator="@android:anim/accelerate_interpolator"android:pivotX="50%"android:pivotY="0%"android:toXScale="1.0"android:toYScale="0.0" ></scale><alphaandroid:duration="180"android:fromAlpha="1.0"android:toAlpha="0.0" />
</set>
代码:
新建一个DropdownMenu类,进行基本封装
package cn.edu.swu.reptile_android.ui.baseimport android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import cn.edu.swu.reptile_android.Rclass DropdownMenu (val context: Context?
) {lateinit var maskView: Viewlateinit var tabView: LinearLayoutlateinit var tabTitle: TextViewlateinit var tabIcon: ImageViewlateinit var data: List<String>private var onItemSelectListener: OnItemSelectListener? = nullpublic fun init(view: View,data: List<String>) {this.data = datatabView = view.findViewById(R.id.ly_dropdown_tab)tabTitle = view.findViewById(R.id.tv_dropdown_title)tabIcon = view.findViewById(R.id.iv_dropdown_icon)maskView = view.findViewById(R.id.mask_view)//默认显示itemtabTitle.text = data[0]tabView.setOnClickListener {//角标变化tabIcon.setImageResource(R.drawable.ic_down)//遮罩层动画maskView.startAnimation(AnimationUtils.loadAnimation(context,R.anim.view_mask_enter_anim))//弹出popWinshowPopupWindow(tabView)}}public fun setOnItemSelectListener(onItemSelectListener: OnItemSelectListener){this.onItemSelectListener = onItemSelectListener}interface OnItemSelectListener{fun onItemSelect(position: Int)fun onDismiss()}private fun showPopupWindow(tabView: View) {val contentView: View =LayoutInflater.from(context).inflate(R.layout.popup_dropdown_menu, null)val popWindow = PopupWindow(contentView, ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT, true)popWindow.contentView = contentView//RVval dropdownRv: RecyclerView = contentView.findViewById(R.id.rv)dropdownRv.layoutManager = LinearLayoutManager(contentView.context)val adapter = BaseAdapter(R.layout.item_rv_dropdown, data) { view, s ->view.findViewById<TextView>(R.id.tv_item_title).text = sif (s == tabTitle.text) { //当前选中的itemview.findViewById<TextView>(R.id.tv_item_title).setTextColor(Color.BLACK)view.findViewById<ImageView>(R.id.iv_item_icon).visibility = View.VISIBLE}}//select itemadapter.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {override fun onItemClick(position: Int) {popWindow.dismiss()tabTitle.text = data[position]//加载数据//暴露给调用者自定义选项逻辑onItemSelectListener?.onItemSelect(position)}})dropdownRv.adapter = adapter//弹出动画popWindow.animationStyle = R.style.popwin_anim//遮罩效果maskView.visibility = View.VISIBLEpopWindow.setOnDismissListener {maskView.visibility = View.GONEtabIcon.setImageResource(R.drawable.ic_up)onItemSelectListener?.onDismiss()}//弹出窗口popWindow.showAsDropDown(tabView)}}
最后,在Activity或者Fragment中使用:
private fun initDropdownMenu(view: View) {val dropdownMenu = DropdownMenu(context)dropdownMenu.init(view, vm.dropdownData)dropdownMenu.setOnItemSelectListener(object : DropdownMenu.OnItemSelectListener {override fun onItemSelect(position: Int) {//点击item后逻辑(加载数据?)}override fun onDismiss() {}})}
四、结语
自己实现的一个基于PopupWindow和RecyclerView实现的下拉菜单,几乎自己实现,可能想法不太成熟,比如遮罩的实现和对于控件的一些封装还存在一些问题,这里仅提供一些思路,有更好的想法欢迎私信讨论。
Android 自定义下拉菜单的实现(基于PopupWindow+RecyclerView)
文章目录
- 一、引言
- 二、效果
- 三、代码实现
- 四、结语
一、引言
安卓自带的Spinner局限性较大,基本不能满足开发样式要求,当前又没有成熟的相关框架,所以决定自己使用PopupWindow实现一个下拉菜单
二、效果
三、代码实现
布局:
新建xml文件:layout_dropdown_menu
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android=""android:layout_width="match_parent"android:layout_height="48dp"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"android:layout_marginLeft="12dp"android:id="@+id/ly_dropdown_tab"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/black"android:textSize="16sp"android:layout_gravity="center_vertical"android:id="@+id/tv_dropdown_title"/><ImageViewandroid:layout_width="12dp"android:layout_height="12dp"android:layout_gravity="center_vertical"android:layout_marginLeft="4dp"android:src="@drawable/ic_up"android:layout_marginStart="4dp"android:id="@+id/iv_dropdown_icon"/></LinearLayout><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"android:layout_alignParentRight="true"android:layout_marginRight="12dp"android:layout_alignParentEnd="true"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/black"android:textSize="16sp"android:layout_gravity="center_vertical"android:text="@string/choose"/><ImageViewandroid:layout_width="16dp"android:layout_height="16dp"android:src="@drawable/ic_choose"android:layout_gravity="center_vertical"android:layout_marginLeft="4dp"android:layout_marginStart="4dp" /></LinearLayout></RelativeLayout>
在需要使用的地方引用
<include layout="@layout/layout_dropdown_menu"/>
为下面布局添加一个遮罩的View:
ps:比如我下拉框下面是一个rv,使用FrameLayout布局,为rv添加一个相同大小的View,来实现遮罩效果(当然有更好的实现方式欢迎私信我)
<include layout="@layout/layout_dropdown_menu"/><com.scwang.smart.refresh.layout.SmartRefreshLayoutandroid:id="@+id/refreshLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="8dp"><com.scwang.smart.refresh.header.ClassicsHeaderandroid:layout_width="match_parent"android:layout_height="wrap_content" /><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><androidx.recyclerview.widget.RecyclerViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/rv"/><Viewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/mask_view"android:visibility="gone"android:background="@color/shadow_bg"/></FrameLayout></com.scwang.smart.refresh.layout.SmartRefreshLayout >
最后添加动画文件:
style.xm
<?xml version="1.0" encoding="utf-8"?>
<resources><style name="popwin_anim" parent="android:Animation"><item name="android:windowEnterAnimation">@anim/pop_enter_anim</item><item name="android:windowExitAnimation">@anim/pop_exit_anim</item></style>
</resources>
pop_enter_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android=""><scaleandroid:duration="200"android:fromXScale="1.0"android:fromYScale="0.0"android:interpolator="@android:anim/accelerate_decelerate_interpolator"android:pivotX="50%"android:pivotY="0%"android:toXScale="1.0"android:toYScale="1.0" ></scale><alphaandroid:duration="180"android:fromAlpha="0.0"android:toAlpha="1.0" />
</set>
pop_exit_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android=""><scaleandroid:duration="200"android:fromXScale="1.0"android:fromYScale="1.0"android:interpolator="@android:anim/accelerate_interpolator"android:pivotX="50%"android:pivotY="0%"android:toXScale="1.0"android:toYScale="0.0" ></scale><alphaandroid:duration="180"android:fromAlpha="1.0"android:toAlpha="0.0" />
</set>
代码:
新建一个DropdownMenu类,进行基本封装
package cn.edu.swu.reptile_android.ui.baseimport android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import cn.edu.swu.reptile_android.Rclass DropdownMenu (val context: Context?
) {lateinit var maskView: Viewlateinit var tabView: LinearLayoutlateinit var tabTitle: TextViewlateinit var tabIcon: ImageViewlateinit var data: List<String>private var onItemSelectListener: OnItemSelectListener? = nullpublic fun init(view: View,data: List<String>) {this.data = datatabView = view.findViewById(R.id.ly_dropdown_tab)tabTitle = view.findViewById(R.id.tv_dropdown_title)tabIcon = view.findViewById(R.id.iv_dropdown_icon)maskView = view.findViewById(R.id.mask_view)//默认显示itemtabTitle.text = data[0]tabView.setOnClickListener {//角标变化tabIcon.setImageResource(R.drawable.ic_down)//遮罩层动画maskView.startAnimation(AnimationUtils.loadAnimation(context,R.anim.view_mask_enter_anim))//弹出popWinshowPopupWindow(tabView)}}public fun setOnItemSelectListener(onItemSelectListener: OnItemSelectListener){this.onItemSelectListener = onItemSelectListener}interface OnItemSelectListener{fun onItemSelect(position: Int)fun onDismiss()}private fun showPopupWindow(tabView: View) {val contentView: View =LayoutInflater.from(context).inflate(R.layout.popup_dropdown_menu, null)val popWindow = PopupWindow(contentView, ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT, true)popWindow.contentView = contentView//RVval dropdownRv: RecyclerView = contentView.findViewById(R.id.rv)dropdownRv.layoutManager = LinearLayoutManager(contentView.context)val adapter = BaseAdapter(R.layout.item_rv_dropdown, data) { view, s ->view.findViewById<TextView>(R.id.tv_item_title).text = sif (s == tabTitle.text) { //当前选中的itemview.findViewById<TextView>(R.id.tv_item_title).setTextColor(Color.BLACK)view.findViewById<ImageView>(R.id.iv_item_icon).visibility = View.VISIBLE}}//select itemadapter.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {override fun onItemClick(position: Int) {popWindow.dismiss()tabTitle.text = data[position]//加载数据//暴露给调用者自定义选项逻辑onItemSelectListener?.onItemSelect(position)}})dropdownRv.adapter = adapter//弹出动画popWindow.animationStyle = R.style.popwin_anim//遮罩效果maskView.visibility = View.VISIBLEpopWindow.setOnDismissListener {maskView.visibility = View.GONEtabIcon.setImageResource(R.drawable.ic_up)onItemSelectListener?.onDismiss()}//弹出窗口popWindow.showAsDropDown(tabView)}}
最后,在Activity或者Fragment中使用:
private fun initDropdownMenu(view: View) {val dropdownMenu = DropdownMenu(context)dropdownMenu.init(view, vm.dropdownData)dropdownMenu.setOnItemSelectListener(object : DropdownMenu.OnItemSelectListener {override fun onItemSelect(position: Int) {//点击item后逻辑(加载数据?)}override fun onDismiss() {}})}
四、结语
自己实现的一个基于PopupWindow和RecyclerView实现的下拉菜单,几乎自己实现,可能想法不太成熟,比如遮罩的实现和对于控件的一些封装还存在一些问题,这里仅提供一些思路,有更好的想法欢迎私信讨论。