Android最牛逼的多条件筛选菜单

说明


本来很懒,但是还是会忍不住的写下这有用既没有用的所谓技术博客,希望会给你带来有所启发,因为这样的功能,写的人很多,也是为了自己能够理解的够透彻,也是为了大家也能更好的理解吧,其实自定义控件,也就是那么回事,没有什么可难的,好吧,可能是这个太简单了。
这是58,赶集都用到的控件,这样的控件,自定义起来其实也并不难,自己项目当中用到了这个,于是就研究了一番,可能在你的项目当中也有可能会遇到这样的需求,正好,也可以来研究下,说真的,今天你见到的这个效果,比58,和赶集做的效果 要好多了,他们做的真的很low。


效果展示


先来看下他长什么样子吧

效果

对,就是这个样子,今天我们就来看看到底是怎么做出来的。

思路


整体的所有布局都是用代码创建出来的,不是写死在布局文件里的,这样会更灵活

  • 创建菜单栏,就是地址呀,年龄,性别的布局,水平的LinearLayout
  • 向菜单栏里添加布局,就是一个简单的TextView,和分割线,然后将该布局添加至父控件中,就是最外层布局
  • 创建下划线,再添加至父控件中
  • 创建内容显示区,注意,内容显示区包括上图中的显示的内容(这里是内容区域),还有点击下拉菜单出来的布局,和半透明的背景,都是在内容区的,他的布局是FrameLayout,注意FrameLayout的特点,有不熟悉他的特点的可以先查一下他的特点,这样就会更好的理解这个控件的绘制了。
  • 内容显示区为FrameLayout,他里面包括3部分,最里层是真正的的内容显示区,就是上图中看到的文字(这里是内容区域),中间层是半透明区域,就是大家看到的变暗的背景,最外层是点击下拉菜单之后弹出来的内容

代码实现


下面将会带你用代码我做这个控件的思路

  public class DropDownMenu extends LinearLayout {

    //顶部菜单布局
    private LinearLayout tabMenuView;
    //底部容器,包含popupMenuViews,maskView
    private FrameLayout containerView;
    //弹出菜单父布局
    private FrameLayout popupMenuViews;
    //遮罩半透明View,点击可关闭DropDownMenu
    private View maskView;
    //tabMenuView里面选中的tab位置,-1表示未选中
    private int current_tab_position = -1;

    //分割线颜色
    private int dividerColor = 0xffcccccc;
    //tab选中颜色
    private int textSelectedColor = 0xff890c85;
    //tab未选中颜色
    private int textUnselectedColor = 0xff111111;
    //遮罩颜色
    private int maskColor = 0x88888888;
    //tab字体大小
    private int menuTextSize = 14;

    //tab选中图标
    private int menuSelectedIcon;
    //tab未选中图标
    private int menuUnselectedIcon;


    public DropDownMenu(Context context) {
        super(context, null);
    }

    public DropDownMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DropDownMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        setOrientation(VERTICAL);

        //为DropDownMenu添加自定义属性
        int menuBackgroundColor = 0xffffffff;
        int underlineColor = 0xffcccccc;

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DropDownMenu);
        underlineColor = a.getColor(R.styleable.DropDownMenu_underlineColor, underlineColor);
        dividerColor = a.getColor(R.styleable.DropDownMenu_dividerColor, dividerColor);
        textSelectedColor = a.getColor(R.styleable.DropDownMenu_textSelectedColor, textSelectedColor);
        textUnselectedColor = a.getColor(R.styleable.DropDownMenu_textUnselectedColor, textUnselectedColor);
        menuBackgroundColor = a.getColor(R.styleable.DropDownMenu_menuBackgroundColor, menuBackgroundColor);
        maskColor = a.getColor(R.styleable.DropDownMenu_maskColor, maskColor);
        menuTextSize = a.getDimensionPixelSize(R.styleable.DropDownMenu_menuTextSize, menuTextSize);
        menuSelectedIcon = a.getResourceId(R.styleable.DropDownMenu_menuSelectedIcon, menuSelectedIcon);
        menuUnselectedIcon = a.getResourceId(R.styleable.DropDownMenu_menuUnselectedIcon, menuUnselectedIcon);
        a.recycle();

        //初始化tabMenuView并添加到tabMenuView
        tabMenuView = new LinearLayout(context);
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        tabMenuView.setOrientation(HORIZONTAL);
        tabMenuView.setBackgroundColor(menuBackgroundColor);
        tabMenuView.setLayoutParams(params);
        addView(tabMenuView, 0);

        //为tabMenuView添加下划线
        View underLine = new View(getContext());
        underLine.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dpTpPx(1.0f)));
        underLine.setBackgroundColor(underlineColor);
        addView(underLine, 1);

        //初始化containerView并将其添加到DropDownMenu
        containerView = new FrameLayout(context);
        containerView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
        addView(containerView, 2);

    }

  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

注意下面的成员变量

private LinearLayout tabMenuView;顶部菜单布局

private FrameLayout containerView;//底部容器,包含popupMenuViews,maskView(暗色背景)

private FrameLayout popupMenuViews;//弹出菜单父布局

private View maskView;//遮罩半透明View,点击可关闭DropDownMenu

public class DropDownMenu extends LinearLayout {}

也就说整体的布局是继承自LinearLayout的

setOrientation(VERTICAL);//在第三个构造方法里指定了整体布局的方向

完成了以上步骤,其实大体上的布局差不多都搭好了,下面就是填充子布局和数据的问题了

    /**
     * 初始化DropDownMenu
     *
     * @param tabTexts
     * @param popupViews
     * @param contentView
     */
    public void setDropDownMenu(@NonNull List<String> tabTexts, @NonNull List<View> popupViews, @NonNull View contentView) {
        if (tabTexts.size() != popupViews.size()) {
            throw new IllegalArgumentException("params not match, tabTexts.size() should be equal popupViews.size()");
        }

        for (int i = 0; i < tabTexts.size(); i++) {
            addTab(tabTexts, i);
        }
        containerView.addView(contentView, 0);

        maskView = new View(getContext());
        maskView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
        maskView.setBackgroundColor(maskColor);
        maskView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                closeMenu();
            }
        });
        containerView.addView(maskView, 1);
        maskView.setVisibility(GONE);

        popupMenuViews = new FrameLayout(getContext());
        popupMenuViews.setVisibility(GONE);
        containerView.addView(popupMenuViews, 2);

        for (int i = 0; i < popupViews.size(); i++) {
            popupViews.get(i).setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            popupMenuViews.addView(popupViews.get(i), i);
        }

    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

这一步是对外暴露的方法,专门给菜单栏,填充数据,还有为内容区填充布局的,布局可以填充任何布局,很灵活

addTab(tabTexts, i);这个方法,下面会给出代码,用来给菜单栏添加布局和菜单栏的分割线的

containerView.addView(contentView, 0);//将传入的内容布局添加到内容区(默认是显示的)

containerView.addView(maskView, 1);//添加暗色背景(默认是GONG的)

containerView.addView(popupMenuViews, 2);//将点击菜单栏后显示的布局,注意,这个布局里也有子布局,那就是listview,所以现在是讲父布局添加到内容区的(默认是GONG的)

popupMenuViews.addView(popupViews.get(i), i);//这一步才是为点击菜单栏后显示的布局填充子布局的,也就是listview

  private void addTab(@NonNull List<String> tabTexts, int i) {
        final TextView tab = new TextView(getContext());
        tab.setSingleLine();
        tab.setEllipsize(TextUtils.TruncateAt.END);
        tab.setGravity(Gravity.CENTER);
        tab.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize);
        tab.setLayoutParams(new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f));
        tab.setTextColor(textUnselectedColor);
        tab.setCompoundDrawablesWithIntrinsicBounds(null, null, null, getResources().getDrawable(menuUnselectedIcon));
        tab.setText(tabTexts.get(i));
        tab.setPadding(dpTpPx(5), dpTpPx(12), dpTpPx(5), dpTpPx(12));
        //添加点击事件
        tab.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                switchMenu(tab);
            }
        });
        tabMenuView.addView(tab);
        //添加分割线
        if (i < tabTexts.size() - 1) {
            View view = new View(getContext());
            LayoutParams layoutParams = new LayoutParams(dpTpPx(0.5f), dpTpPx(25));
            layoutParams.gravity = Gravity.CENTER_VERTICAL;
            view.setLayoutParams(layoutParams);
            view.setBackgroundColor(dividerColor);
            tabMenuView.addView(view);
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

这一步是为菜单栏添加布局,和分割线的,还有为菜单栏添加点击事件,没有什么可说的

     /**
     * 切换菜单
     *
     * @param view
     */
    private void switchMenu(View view) {

        for (int i = 0; i < tabMenuView.getChildCount(); i = i + 2) {

            if (view == tabMenuView.getChildAt(i)) {
                if (current_tab_position == i) {
                    closeMenu();
                } else {
                    if (current_tab_position == -1) {
                        popupMenuViews.setVisibility(View.VISIBLE);
                        popupMenuViews.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.dd_menu_in));
                        maskView.setVisibility(VISIBLE);
                        maskView.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.dd_mask_in));
                        popupMenuViews.getChildAt(i / 2).setVisibility(View.VISIBLE);
                    } else {
                        popupMenuViews.getChildAt(i / 2).setVisibility(View.VISIBLE);
                    }
                    current_tab_position = i;
                    ((TextView) tabMenuView.getChildAt(i)).setTextColor(textSelectedColor);
                    ((TextView) tabMenuView.getChildAt(i)).setCompoundDrawablesWithIntrinsicBounds(null, null,
                            null, getResources().getDrawable(menuSelectedIcon));
                }
            } else {
                //设置其他未点击tab的状态
                ((TextView) tabMenuView.getChildAt(i)).setTextColor(textUnselectedColor);
                ((TextView) tabMenuView.getChildAt(i)).setCompoundDrawablesWithIntrinsicBounds(null, null,
                        null, getResources().getDrawable(menuUnselectedIcon));
                popupMenuViews.getChildAt(i / 2).setVisibility(View.GONE);
            }
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

这一步是切换菜单时要做的,只是改变其他状态而已

用法


做到这,也就意味着,自定义控件写完了,下面就是如何用他了

布局

 <example.fussen.dropdownmenu.DropDownMenu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/dropDownMenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:dividerColor="@color/blue_app"
    app:maskColor="@color/mask_color"
    app:menuBackgroundColor="@color/base_title_color"
    app:menuSelectedIcon="@drawable/ic_img_up"
    app:menuTextSize="16sp"
    app:menuUnselectedIcon="@drawable/ic_img_down"
    app:textSelectedColor="@color/blue_app"
    app:textUnselectedColor="@color/drop_down_unselected"
    app:underlineColor="@color/blue_app" />

将上面的布局copy到你的布局里就ok啦

自定义的属性

 <declare-styleable name="DropDownMenu">
    <!--下划线颜色-->
    <attr name="underlineColor" format="color" />
    <!--分割线颜色(菜单栏)-->
    <attr name="dividerColor" format="color" />
    <!--tab选中颜色-->
    <attr name="textSelectedColor" format="color" />
    <!--tab未选中颜色-->
    <attr name="textUnselectedColor" format="color" />
    <!--tab 背景颜色-->
    <attr name="menuBackgroundColor" format="color" />
    <!--遮罩颜色,一般是半透明-->
    <attr name="maskColor" format="color" />
    <!--字体大小-->
    <attr name="menuTextSize" format="dimension" />
    <!--tab选中状态图标-->
    <attr name="menuSelectedIcon" format="reference" />
    <!--tab未选中状态图标-->
    <attr name="menuUnselectedIcon" format="reference" />
</declare-styleable>

结束


  1. ok,做到这,就大功告成了,具体的代码还是以我的Demo为主
  2. 如果需要源码,关注微信公共号:AppCode,回复关键字“筛选菜单”,即可拿到源码下载的链接

apk下载

点击下载demo的apk

如果这篇文章对你有用,欢迎关注我们的微信公共号:AppCode

扫描下面二维码即可关注

### 回答1: 在计算机编程领域,牛的代码有很多种,不同的人对于“牛”的定义也有所不同。以下是一个被认为相当牛的代码示例: ```python def fibonacci(n): if n <= 0: return [] elif n == 1: return [0] elif n == 2: return [0, 1] else: fib_list = [0, 1] while len(fib_list) < n: fib_list.append(fib_list[-1] + fib_list[-2]) return fib_list ``` 这段代码是一个计算斐波那契数列的函数,其思路简洁高效。通过传入一个整数n,该函数可以返回一个长度为n的斐波那契数列列表。 该代码的逻辑如下: - 若n小于等于0,则返回空列表; - 若n等于1,则返回只包含0的列表; - 若n等于2,则返回包含0和1的列表; - 否则,创建一个初始列表fib_list,包含0和1两个元素; - 当fib_list列表长度不到n时,通过迭代将新的斐波那契数添加到列表中; - 返回最终得到的斐波那契数列列表。 这段代码的优点是: - 考虑了输入为非正整数的情况,用简洁的逻辑进行处理; - 通过只计算必要的斐波那契数,避免了不必要的计算,提高了性能; - 使用循环(while)避免了递归过深导致的栈溢出问题; - 返回的斐波那契数列是完整的(长度为n),确保了代码的正确性。 总之,这段代码在实现功能的同时,兼顾了代码的简洁性、性能和健壮性,可以说是一段相当牛的代码。 ### 回答2: 最牛的代码完整是指在编程方面达到了极致的代码,具备以下几个特点: 首先,最牛的代码完整一般都非常高效。它利用了各种算法和数据结构的优势,使得代码的执行速度尽可能快。这样可以在短时间内完成大量计算任务,提高程序的性能。 其次,最牛的代码完整往往具有良好的可维护性。它采用了一系列规范的编程风格和设计模式,使得代码的结构清晰,逻辑明确。这样可以方便其他开发人员理解和修改代码,提高项目的可维护性。 此外,最牛的代码完整通常都具备高度的健壮性和适应性。它能够应对各种异常情况,预防和处理错误。同时,最牛的代码完整通常具有一定的扩展性和灵活性,能够很容易地进行功能的扩展和改进。 最后,最牛的代码完整在实现功能的同时,还注重代码的美观和可读性。它采用了适当的注释和命名规范,使得代码易读易懂。同时,最牛的代码完整还遵循了一定的代码规范和约定,使得团队协作更加方便和高效。 因此,最牛的代码完整是综合考虑了代码的效率、可维护性、健壮性和美观性等方面,在完成功能的同时,具备了优秀的编码品质和开发效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值