Android自定義帶popupWindow的二級(jí)菜單篩選控件

前言:近期項(xiàng)目重構(gòu),公司對(duì)項(xiàng)目結(jié)構(gòu)進(jìn)行了調(diào)整,增加了條件篩選的功能。在網(wǎng)上也看到了很多自定義的控件實(shí)現(xiàn)類似美團(tuán)的附近篩選功能,實(shí)現(xiàn)方式基本都是自定義view繼承自LinearLayout等布局控件,于是自己在Button的基礎(chǔ)上添加了popupWindow并進(jìn)行了簡單封裝,使用RecyclerView展示數(shù)據(jù),實(shí)現(xiàn)了帶二級(jí)菜單的篩選功能。

效果圖:

效果圖

實(shí)現(xiàn)方式:

(1)繼承自AppCompatButton,實(shí)現(xiàn)PopupWindow.OnDismissListener

(2)PopupWindow展示二級(jí)菜單

(3)RecyclerView展示二級(jí)菜單的列表數(shù)據(jù)

(4)監(jiān)聽接口進(jìn)行回調(diào),用于篩選后更新UI

1.定義FilterPopupButton

(1)FilterPopupButton繼承自AppCompatButton,重寫構(gòu)造方法

//三個(gè)有參構(gòu)造方法 
public FilterPopupButton(Context context) {
    super(context);
    this.context = context; 
}

public FilterPopupButton(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.context = context; 
}

public FilterPopupButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    this.context = context;
    inflater = LayoutInflater.from(context);
    initAttrs(context, attrs);
    initParams(context);   
}

(2)設(shè)置控件屬性參數(shù)

/**  
* 初始化屬性參數(shù) 
* @param context 上下文對(duì)象  
* @param attrs 屬性  
*/ 
private void initAttrs(Context context, AttributeSet attrs) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.popupbtn);
    //正常狀態(tài)下的背景色
    mBackGround = typedArray.getResourceId(R.styleable.popupbtn_normalBg, -1);
    //點(diǎn)擊時(shí)的背景色
    mClickBackGround = typedArray.getResourceId(R.styleable.popupbtn_pressBg, -1);
    //正常狀態(tài)下的圖標(biāo)
    mIcon = typedArray.getResourceId(R.styleable.popupbtn_normalIcon, -1);
    //點(diǎn)擊狀態(tài)下的圖標(biāo)
    mClickIcon = typedArray.getResourceId(R.styleable.popupbtn_pressIcon, -1);
    //回收
    typedArray.recycle(); 
}

定義attrs,方便在布局文件使用時(shí)設(shè)置相關(guān)屬性

<declare-styleable name="popupbtn">
  <!-- 正常狀態(tài)下的背景 -->
  <attr name="normalBg" format="reference" />
  <!-- 點(diǎn)擊狀態(tài)下的背景 -->
  <attr name="pressBg" format="reference" />
  <!-- 正常狀態(tài)下的圖標(biāo) -->
  <attr name="normalIcon" format="reference" />
  <!-- 點(diǎn)擊狀態(tài)下的圖標(biāo) -->
  <attr name="pressIcon" format="reference" />f

</declare-styleable>

(3)初始化其他參數(shù)

/**  
* 初始化參數(shù) 
*/ 
private void initParams(Context context) {
    //初始化圖標(biāo)的padding值
    paddingTop = this.getPaddingTop();
    paddingLeft = this.getPaddingLeft();
    paddingRight = this.getPaddingRight();
    paddingBottom = this.getPaddingBottom();
    //獲取Window管理器
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    //獲取屏幕的寬和高
    mScreenWidth = wm.getDefaultDisplay().getWidth();
    mScreenHeight = wm.getDefaultDisplay().getHeight();
    //設(shè)置正常狀態(tài)下的參數(shù)
    setNormalStatus(); 
}

(4)設(shè)置正常狀態(tài)及點(diǎn)擊狀態(tài)下的背景及圖標(biāo)

/**  
* 設(shè)置點(diǎn)擊狀態(tài)下的背景及圖標(biāo) 
*/ 
private void setClickStatus() {
    //設(shè)置點(diǎn)擊時(shí)的背景色
    if (mClickBackGround != -1) {
        this.setBackgroundResource(mClickBackGround);
        this.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    }
    //設(shè)置點(diǎn)擊時(shí)的圖標(biāo)
    if (mClickIcon != -1) {
        Drawable drawable = getResources().getDrawable(mClickIcon);
        // 設(shè)置drawable的bounds以便展示圖標(biāo)
        drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
        this.setCompoundDrawables(null, null, drawable, null);
    }
}

/**  
* 設(shè)置正常狀態(tài)下的背景及圖標(biāo) 
*/ 
private void setNormalStatus() {
    //未點(diǎn)擊狀態(tài)背景
    if (mBackGround != -1) {
        this.setBackgroundResource(mBackGround);
        this.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    }
    //未點(diǎn)擊狀態(tài)下的圖標(biāo)
    if (mIcon != -1) {
        Drawable drawable = getResources().getDrawable(mIcon);
        // 設(shè)置drawable的bounds以便展示圖標(biāo)
        drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
        this.setCompoundDrawables(null, null, drawable, null);
  }
}

2.PopupWindow

(1)初始化popupWindow布局文件

/**  
* 初始化popupWindow的布局文件 
*/ 
private void init() {
    //初始化popupWindow的布局文件
    View view = inflater.inflate(R.layout.popup_layout, null);
    //展示一級(jí)菜單的view
    mRvParent = (RecyclerView) view.findViewById(R.id.rv_parent);
    //展示二級(jí)菜單的view
    mRvChild = (RecyclerView) view.findViewById(R.id.rv_child);
    //存儲(chǔ)取值列表的集合
    mValuesList = new ArrayList<>();
    //默認(rèn)添加一級(jí)菜單對(duì)應(yīng)的二級(jí)菜單的第一個(gè)值列表
     //mValuesList.addAll(mChildList.get(0));    
     mRvParent.setLayoutManager(new   LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
     mRvParent.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
     mRvChild.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
    mRvChild.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));
    //創(chuàng)建一級(jí)和二級(jí)菜單的adapter
    mParentAdapter = new FilterAdapter(context, R.layout.popup_item, mParentList);
    mChildAdapter = new FilterAdapter(context, R.layout.popup_item, mValuesList);
    //設(shè)置一級(jí)菜單的第一個(gè)默認(rèn)選中狀態(tài)
    mParentAdapter.recordSelectPosition(0);
    mRvParent.setAdapter(mParentAdapter);
    mRvChild.setAdapter(mChildAdapter);
    //初始化popupWindow
    initPopupView(view);
    //父條目點(diǎn)擊事件
    mParentAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
            if (position == 0) {
                //position=0為篩選全部,點(diǎn)擊popupWindow消失并設(shè)置取值為父條目的第一個(gè)值
                setText(mParentList.get(position));
                hidePopupWindow(mParentList.get(position));
            }
            //記錄父條目點(diǎn)擊的position
            mParentAdapter.recordSelectPosition(position);
            mParentAdapter.notifyDataSetChanged();
            mValuesList.clear();
            //將子條目添加到取值列表
            mValuesList.addAll(mChildList.get(position));
            mChildAdapter.notifyDataSetChanged();
            mChildAdapter.recordSelectPosition(-1);
            //設(shè)置子條目默認(rèn)選中第一個(gè)
            //mRvChild.scrollToPosition(0);  
        }

        @Override
        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
            return false;
        }

    });
  //子條目點(diǎn)擊事件
  mChildAdapter.setOnItemClickListener(new MultiItemTypeAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
            //記錄子條目點(diǎn)擊的position
            mChildAdapter.recordSelectPosition(position);
            mChildAdapter.notifyDataSetChanged();
            //設(shè)置選中的字符
            setText(mValuesList.get(position));
            //隱藏popupWindow
            hidePopupWindow(mValuesList.get(position));
        }

        @Override
        public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
            return false;
        }

    }); 
}

注:本文使用RecyclerView用于二級(jí)菜單數(shù)據(jù)列表的展示,并使用鴻洋大神封裝的adapter,文末貼出依賴。

(2)popup_layout布局文件

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/white"
  android:orientation="horizontal">   
  <android.support.v7.widget.RecyclerView  
    android:id="@+id/rv_parent"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:scrollbars="none">   
  </android.support.v7.widget.RecyclerView>   
  <View  
    android:layout_width="1dp"
    android:layout_height="match_parent"
    android:background="@android:color/darker_gray" />   
  <android.support.v7.widget.RecyclerView  
    android:id="@+id/rv_child"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1">   
  </android.support.v7.widget.RecyclerView>

(3)initPopupWindow及hidePoupWindow

/**  
* 初始化popupWindow 
* @param view popupWindow的布局view  
*/ 
public void initPopupView(final View view) {
    this.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mFixPopupWindow == null) {
                LinearLayout layout = new LinearLayout(context);
                LinearLayout.LayoutParams params = new  LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) (mScreenHeight * 0.6));
                view.setLayoutParams(params);
                layout.addView(view);
                //設(shè)置背景色,不設(shè)置的話在有些機(jī)型會(huì)不顯示popupWindow
                layout.setBackgroundColor(Color.argb(60, 0, 0, 0));
                //自定義的FixPopupWindow,解決在Build.VERSION.SDK_INT >= 24時(shí),popupWindow顯示位置在屏幕頂部問題
                mFixPopupWindow = new FixPopupWindow(layout, mScreenWidth, mScreenHeight);
                mFixPopupWindow.setFocusable(true);
                mFixPopupWindow.setBackgroundDrawable(new BitmapDrawable());
                //設(shè)置點(diǎn)擊popupWindow外部可消失
                mFixPopupWindow.setOutsideTouchable(true);
                mFixPopupWindow.setOnDismissListener(FilterPopupButton.this);
                layout.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mFixPopupWindow.dismiss();
                    }
                });
        }
            //設(shè)置點(diǎn)擊popupButton時(shí)的狀態(tài)
            setClickStatus();
            mFixPopupWindow.showAsDropDown(FilterPopupButton.this);
        }
    }); 
}
/**  
* 隱藏popupWindow 
* @param value 獲取到的值  
*/ 
public void hidePopupWindow(String value) {
    if (mFixPopupWindow != null && mFixPopupWindow.isShowing()) {
        popupButtonMonitor.setFilterResult(value);
        mFixPopupWindow.dismiss();
    }
}

(4)定義recyclerView的adapter(FilterAdapter)

/**  
* Created by ruancw on 2018/5/31. 
* 
*/   
public class FilterAdapter extends CommonAdapter<String> {
    private int selection;   
    public FilterAdapter(Context context, int layoutId, List<String> dataList) {
        super(context,layoutId,dataList);
        this.selection = -1;
    }

     @Override
     protected void convert(ViewHolder holder, String value, int position) {
          holder.setText(R.id.tv_des,value);
         if(position == selection) {
            holder.setBackgroundRes(R.id.tv_des,R.color.press);
        }else {
            holder.setBackgroundRes(R.id.tv_des,R.color.normal);
        }
    }

    /**  
    * 記錄選中的position位置 
    * @param position 上一次點(diǎn)擊的位置  
    */  
    public void recordSelectPosition(int position) {
        this.selection = position;
    }

}

注意:在adapter中有if必須要有else與之對(duì)應(yīng),不然會(huì)出現(xiàn)記錄條目的背景色錯(cuò)亂問題。

3.Button+PopupWindow封裝

(1)在FilterPopupWindow中定義設(shè)置數(shù)據(jù)的方法

/**  
* 設(shè)置數(shù)據(jù) 
* @param mParentList 一級(jí)菜單數(shù)據(jù)集合  
* @param mChildList 二級(jí)菜單數(shù)據(jù)集合  
*/ 
public void setValue(List<String> mParentList, List<List<String>> mChildList) {
    this.mParentList = mParentList;
    this.mChildList = mChildList;
    //初始化popupWindow的布局文件
    init(); 
}

init方法即是開始初始化popupWindow。

(2)定義數(shù)據(jù)監(jiān)聽接口,用于篩選完成重新更新UI

/**  
* 監(jiān)聽數(shù)據(jù)的接口 
*/ 
public interface PopupButtonMonitor {
    //設(shè)置回調(diào)的方法
    void setFilterResult(String filterResult); 
}

/**  
* 接口綁定 
* @param popupButtonMonitor 接口  
*/ 
public void setPopupButtonMonitor(PopupButtonMonitor popupButtonMonitor) {
    this.popupButtonMonitor = popupButtonMonitor; 
}

(3)實(shí)現(xiàn)PopupWindow.OnDismissListener的方法

@Override 
public void onDismiss() {
    //在popupWindow消失時(shí),將狀態(tài)設(shè)置為正常狀態(tài)
    setNormalStatus(); 
}

4.使用

(1)布局中

<com.rcw.filterpopup.FilterPopupButton
  android:id="@+id/filter_popup_button"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="測試1"
  android:textSize="20dp"
  popupbtn:normalBg="@drawable/tab_bkg_line"
  popupbtn:normalIcon="@drawable/arrow_down"
  popupbtn:pressBg="@drawable/tab_bkg_selected"
  popupbtn:pressIcon="@drawable/arrow_up_blue"
/>

使用popupbtn:xx屬性需在根布局添加自定義屬性聲明:

xmlns:popupbtn="http://schemas.android.com/apk/res-auto"</pre>

(2)代碼中

filterPopup=findViewById(R.id.filter_popup_button); 
filterPopup.setValue(pList,cList); 
filterPopup.setPopupButtonMonitor(this);

5.問題

細(xì)心的你可能發(fā)現(xiàn),在文中我使用的是FixPopupWindow,沒錯(cuò),這是自定義的popupWindow。為什么要重新定義poupWindow而不使用系統(tǒng)的PopupWindow呢???

寫完代碼,迫不及待的進(jìn)行了一番測試,一頓操作猛如虎,結(jié)果發(fā)現(xiàn)popupWindow的位置是在系統(tǒng)頂部而不是控件的下方,趕緊檢查了下代碼,發(fā)現(xiàn)使用的是showAsDropDown(View anchor)方法,經(jīng)過一番努力,在stackoverflow中找到原因,原來是一系統(tǒng)級(jí)bug,在版本>=24時(shí)要在showASDropDown方法重新測量。

/**  
* Created by ruancw on 2018/5/30. 
* 重新定義popupWindow,解決版本過高,popupWindow的位置不在控件下方的問題 
*/   
public class FixPopupWindow extends PopupWindow {
    public FixPopupWindow(View contentView, int width, int height) {
        super(contentView, width, height);
    }

    @Override
    public void showAsDropDown(View anchor, int xoff, int yoff) {
        if(Build.VERSION.SDK_INT >= 24) {
            Rect rect = new Rect();
            anchor.getGlobalVisibleRect(rect);
            int h = anchor.getResources().getDisplayMetrics().heightPixels - rect.bottom;
            setHeight(h);
        }
        super.showAsDropDown(anchor, xoff, yoff);
    }

}

鴻洋大神recyclerView的adapter依賴:

implementation 'com.zhy:base-rvadapter:3.0.3'

至此,自定義帶popupWindow的二級(jí)菜單篩選控件簡單的封裝就實(shí)現(xiàn)了,不足之處,歡迎評(píng)論與留言?。?!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,765評(píng)論 25 709
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,133評(píng)論 22 665
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 33,143評(píng)論 6 472
  • 自責(zé),我生氣我自己為什么只會(huì)替別人考慮,不能考慮我自己呢,我只心疼他、他的父母沒有錢,想跟他一起抗,可是卻把我自己...
    66f7eee7a750閱讀 160評(píng)論 0 0
  • 好像生活沒有給你太多壓力 但是你莫名其妙就多了很多壓力,為什么,別人不信任你,別人欺負(fù)你,不是的 其實(shí)最大的困難在...
    Yolo十二閱讀 865評(píng)論 0 1

友情鏈接更多精彩內(nèi)容