Filter DropDownMenu---带过滤功能的下拉菜单

转自:chenfuduo.me 效果还是很不错的,要是封装一下就更好了。 

带过滤功能的下拉菜单在美团网移动端,腾讯课堂移动端都可以看到,本篇就是实现这个功能。

效果图

11

分析

从上面的效果图中可以看出,我们可以这样做:

  • 自定义顶部的每一个按钮(DropDownButton)

  • 自定义ListView的每一个Item(包含一个箭头和文字)

  • 自定义ListView,将Item绑定到ListView上。

  • 具体怎么实现过滤,由自己的需求而定,这里没有做处理。

    DropDownButton

    布局

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:drawablePadding="2dp"
        android:drawableRight="@drawable/ic_dropdown_normal"
        android:ellipsize="end"
        android:maxLines="1"
        android:singleLine="true"
        android:textColor="#222222" />
    <View
        android:id="@+id/bottomLine"
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:background="#FF3BBD79"
        android:layout_alignParentBottom="true"
        android:visibility="visible"
        />
</merge>

这里唯一需要提到的是这里使用了merge标签(后来因为这个掉进坑里去了,下表,这里留意一下即可,关于merge标签,参考android-developers.blog.com在09年三月份的一篇文章,当时看了国内的一篇文章,MD误导我了)

实现

package me.chenfuduo.dropdownmenu;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
 * Created by Administrator on 2015/5/28.
 */
public class DropdownButton extends RelativeLayout {
    TextView textView;
    View bottomLine;
    public DropdownButton(Context context) {
        this(context, null);
    }
    public DropdownButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public DropdownButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        View view =  LayoutInflater.from(getContext()).inflate(R.layout.dropdown_tab_button,this, true);
        textView = (TextView) view.findViewById(R.id.textView);
        bottomLine = view.findViewById(R.id.bottomLine);
    }
    public void setText(CharSequence text) {
        textView.setText(text);
    }
    public void setChecked(boolean checked) {
        Drawable icon;
        if (checked) {
            icon = getResources().getDrawable(R.drawable.ic_dropdown_actived);
            textView.setTextColor(getResources().getColor(R.color.green));
            bottomLine.setVisibility(VISIBLE);
        } else {
            icon = getResources().getDrawable(R.drawable.ic_dropdown_normal);
            textView.setTextColor(getResources().getColor(R.color.font_black_content));
            bottomLine.setVisibility(GONE);
        }
        textView.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
    }
}

首先在构造器中通过LayoutInflater获取布局,然后设置DropDownButton选中和没有选中对应的状态。
唯一需要留意的是LayoutInflater的用法。

我遇到的问题

当时我的LayoutInflater不是这样写的:

View view =  LayoutInflater.from(getContext()).inflate(R.layout.dropdown_tab_button,this, true);

而是这样写的:

View view =  LayoutInflater.from(getContext()).inflate(R.layout.dropdown_tab_button,null);

这样写会报错误:

<merge/> can be only with a valid ViewGroup root and attachToRoot=true

巨坑啊,当我在查找这个问题的答案的时候,很多人说merge不能使用LayoutInflater,而且下面还有人评论说:学习了,MD,劳资是在很无语。
关于LayoutInflater,可以看下这个哥们的博客,很不错。

ListItemView

package me.chenfuduo.dropdownmenu;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;
/**
 * Created by Administrator on 2015/5/28.
 */
public class DropdownListItemView extends TextView {
    public DropdownListItemView(Context context) {
        this(context,null);
    }
    public DropdownListItemView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public DropdownListItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public void bind(CharSequence text,boolean checked){
        setText(text);
        if (checked){
            Drawable icon = getResources().getDrawable(R.drawable.ic_task_status_list_check);
            setCompoundDrawablesWithIntrinsicBounds(null,null,icon,null);
        }else{
            setCompoundDrawablesWithIntrinsicBounds(null,null,null,null);
        }
    }
}

实现“对号”图标和选中的条目的绑定。setCompoundDrawablesWithIntrinsicBounds(...)方法对应的还有一个方法:setCompoundDrawables(),但是调用这方法必须调用Drawable.setBounds()方法。

自定义ListView

布局

<?xml version="1.0" encoding="utf-8"?>
<merge  xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <LinearLayout
        android:id="@+id/linearLayout"
        android:background="@color/stand_bg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
    </LinearLayout>
</merge >

实现

package me.chenfuduo.dropdownmenu;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import java.util.LinkedList;
import java.util.List;
/**
 * Created by Administrator on 2015/5/28.
 */
public class DropdownListView extends ScrollView {
    public LinearLayout linearLayout;
    DropdownItemObject current;
    List<? extends DropdownItemObject> list;
    DropdownButton button;
    public DropdownListView(Context context) {
        this(context, null);
    }
    public DropdownListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public DropdownListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
    private void init() {
        View view =  LayoutInflater.from(getContext()).inflate(R.layout.dropdown_tab_list, this,true);
        linearLayout = (LinearLayout) view.findViewById(R.id.linearLayout);
    }
    public void flush() {
        for (int i = 0, n = linearLayout.getChildCount(); i < n; i++) {
            View view = linearLayout.getChildAt(i);
            if (view instanceof DropdownListItemView) {
                DropdownListItemView itemView = (DropdownListItemView) view;
                DropdownItemObject data = (DropdownItemObject) itemView.getTag();
                if (data == null) {
                    return;
                }
                boolean checked = data == current;
                String suffix = data.getSuffix();
                itemView.bind(TextUtils.isEmpty(suffix) ? data.text : data.text + suffix, checked);
                if (checked) button.setText(data.text);
            }
        }
    }
    public void bind(List<? extends DropdownItemObject> list,
                     DropdownButton button,
                     final Container container,
                     int selectedId
    ) {
        current = null;
        this.list = list;
        this.button = button;
        LinkedList<View> cachedDividers = new LinkedList<>();
        LinkedList<DropdownListItemView> cachedViews = new LinkedList<>();
        for (int i = 0, n = linearLayout.getChildCount(); i < n; i++) {
            View view = linearLayout.getChildAt(i);
            if (view instanceof DropdownListItemView) {
                cachedViews.add((DropdownListItemView) view);
            } else {
                cachedDividers.add(view);
            }
        }
        linearLayout.removeAllViews();
        LayoutInflater inflater = LayoutInflater.from(getContext());
        boolean isFirst = true;
        for (DropdownItemObject item : list) {
            if (isFirst) {
                isFirst = false;
            } else {
                View divider = cachedDividers.poll();
                if (divider == null) {
                    divider = inflater.inflate(R.layout.dropdown_tab_list_divider, linearLayout, false);
                }
                linearLayout.addView(divider);
            }
            DropdownListItemView view = cachedViews.poll();
            if (view == null) {
                view = (DropdownListItemView) inflater.inflate(R.layout.dropdown_tab_list_item, linearLayout, false);
            }
            view.setTag(item);
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    DropdownItemObject data = (DropdownItemObject) v.getTag();
                    if (data == null) return;
                    DropdownItemObject oldOne = current;
                    current = data;
                    flush();
                    container.hide();
                    if (oldOne != current) {
                        container.onSelectionChanged(DropdownListView.this);
                    }
                }
            });
            linearLayout.addView(view);
            if (item.id == selectedId && current == null) {
                current = item;
            }
        }
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (getVisibility() == VISIBLE) {
                    container.hide();
                } else {
                    container.show(DropdownListView.this);
                }
            }
        });
        if (current == null && list.size() > 0) {
            current = list.get(0);
        }
        flush();
    }
    public static interface Container {
        void show(DropdownListView listView);
        void hide();
        void onSelectionChanged(DropdownListView view);
    }
}

其中bind(...)方法实现了每个ListView和上面DropDownButton的绑定。调用flush()方法去刷新每个Item。

使用

布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <LinearLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@color/white"
        android:gravity="center">
        <me.chenfuduo.dropdownmenu.DropdownButton
            android:id="@+id/chooseType"
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <View
            android:layout_width="0.5dp"
            android:layout_height="18dp"
            android:background="#dddddd" />
        <me.chenfuduo.dropdownmenu.DropdownButton
            android:id="@+id/chooseLabel"
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1" />
        <View
            android:layout_width="0.5dp"
            android:layout_height="18dp"
            android:background="#dddddd" />
        <me.chenfuduo.dropdownmenu.DropdownButton
            android:id="@+id/chooseOrder"
            android:layout_width="0px"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    </LinearLayout>
    <View
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/divide" />
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:footerDividersEnabled="false" />
        <View
            android:id="@+id/mask"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#80000000" />
        <me.chenfuduo.dropdownmenu.DropdownListView
            android:id="@+id/dropdownType"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" />
        <me.chenfuduo.dropdownmenu.DropdownListView
            android:id="@+id/dropdownLabel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" />
        <me.chenfuduo.dropdownmenu.DropdownListView
            android:id="@+id/dropdownOrder"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" />
    </FrameLayout>
</LinearLayout>

实现

package me.chenfuduo.dropdownmenu;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
    private static final int ID_TYPE_ALL = 0;
    private static final int ID_TYPE_MY = 1;
    private static final String TYPE_ALL = "全部讨论";
    private static final String TYPE_MY = "我的讨论";
    private static final int ID_LABEL_ALL = -1;
    private static final String LABEL_ALL = "全部标签";
    private static final String ORDER_REPLY_TIME = "最后评论排序";
    private static final String ORDER_PUBLISH_TIME = "发布时间排序";
    private static final String ORDER_HOT = "热门排序";
    private static final int ID_ORDER_REPLY_TIME = 51;
    private static final int ID_ORDER_PUBLISH_TIME = 49;
    private static final int ID_ORDER_HOT = 53;
    ListView listView;
    View mask;
    DropdownButton chooseType, chooseLabel, chooseOrder;
    DropdownListView dropdownType, dropdownLabel, dropdownOrder;
    Animation dropdown_in, dropdown_out, dropdown_mask_out;
    private List<TopicLabelObject> labels = new ArrayList<>();
    private DropdownButtonsController dropdownButtonsController = new DropdownButtonsController();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView) findViewById(R.id.listView);
        mask = findViewById(R.id.mask);
        chooseType = (DropdownButton) findViewById(R.id.chooseType);
        chooseLabel = (DropdownButton) findViewById(R.id.chooseLabel);
        chooseOrder = (DropdownButton) findViewById(R.id.chooseOrder);
        dropdownType = (DropdownListView) findViewById(R.id.dropdownType);
        dropdownLabel = (DropdownListView) findViewById(R.id.dropdownLabel);
        dropdownOrder = (DropdownListView) findViewById(R.id.dropdownOrder);
        dropdown_in = AnimationUtils.loadAnimation(this,R.anim.dropdown_in);
        dropdown_out = AnimationUtils.loadAnimation(this,R.anim.dropdown_out);
        dropdown_mask_out = AnimationUtils.loadAnimation(this,R.anim.dropdown_mask_out);
        dropdownButtonsController.init();
        //id count name
        TopicLabelObject topicLabelObject1 =  new TopicLabelObject(1,1,"Fragment");
        labels.add(topicLabelObject1);
        TopicLabelObject topicLabelObject2 =new TopicLabelObject(2,1,"CustomView");
        labels.add(topicLabelObject2);
        TopicLabelObject topicLabelObject3 =new TopicLabelObject(2,1,"Service");
        labels.add(topicLabelObject3);
        TopicLabelObject topicLabelObject4 =new TopicLabelObject(2,1,"BroadcastReceiver");
        labels.add(topicLabelObject4);
        TopicLabelObject topicLabelObject5 =new TopicLabelObject(2,1,"Activity");
        labels.add(topicLabelObject5);
        mask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dropdownButtonsController.hide();
            }
        });
        dropdownButtonsController.flushCounts();
        dropdownButtonsController.flushAllLabels();
        dropdownButtonsController.flushMyLabels();
    }
    private class DropdownButtonsController implements DropdownListView.Container {
        private DropdownListView currentDropdownList;
        private List<DropdownItemObject> datasetType = new ArrayList<>(2);//全部讨论
        private List<DropdownItemObject> datasetAllLabel = new ArrayList<>();//全部标签
        private List<DropdownItemObject> datasetMyLabel = new ArrayList<>();//我的标签
        private List<DropdownItemObject> datasetLabel = datasetAllLabel;//标签集合   默认是全部标签
        private List<DropdownItemObject> datasetOrder = new ArrayList<>(3);//评论排序
        @Override
        public void show(DropdownListView view) {
            if (currentDropdownList != null) {
                currentDropdownList.clearAnimation();
                currentDropdownList.startAnimation(dropdown_out);
                currentDropdownList.setVisibility(View.GONE);
                currentDropdownList.button.setChecked(false);
            }
            currentDropdownList = view;
            mask.clearAnimation();
            mask.setVisibility(View.VISIBLE);
            currentDropdownList.clearAnimation();
            currentDropdownList.startAnimation(dropdown_in);
            currentDropdownList.setVisibility(View.VISIBLE);
            currentDropdownList.button.setChecked(true);
        }
        @Override
        public void hide() {
            if (currentDropdownList != null) {
                currentDropdownList.clearAnimation();
                currentDropdownList.startAnimation(dropdown_out);
                currentDropdownList.button.setChecked(false);
                mask.clearAnimation();
                mask.startAnimation(dropdown_mask_out);
            }
            currentDropdownList = null;
        }
        @Override
        public void onSelectionChanged(DropdownListView view) {
            if (view == dropdownType) {
                updateLabels(getCurrentLabels());
            }
        }
        void reset() {
            chooseType.setChecked(false);
            chooseLabel.setChecked(false);
            chooseOrder.setChecked(false);
            dropdownType.setVisibility(View.GONE);
            dropdownLabel.setVisibility(View.GONE);
            dropdownOrder.setVisibility(View.GONE);
            mask.setVisibility(View.GONE);
            dropdownType.clearAnimation();
            dropdownLabel.clearAnimation();
            dropdownOrder.clearAnimation();
            mask.clearAnimation();
        }
        void init() {
            reset();
            datasetType.add(new DropdownItemObject(TYPE_ALL, ID_TYPE_ALL, "all"));
            datasetType.add(new DropdownItemObject(TYPE_MY, ID_TYPE_MY, "my"));
            dropdownType.bind(datasetType, chooseType, this, ID_TYPE_ALL);
            datasetAllLabel.add(new DropdownItemObject(LABEL_ALL, ID_LABEL_ALL, null) {
                @Override
                public String getSuffix() {
                    return dropdownType.current == null ? "" : dropdownType.current.getSuffix();
                }
            });
            datasetMyLabel.add(new DropdownItemObject(LABEL_ALL, ID_LABEL_ALL, null));
            datasetLabel = datasetAllLabel;
            dropdownLabel.bind(datasetLabel, chooseLabel, this, ID_LABEL_ALL);
            datasetOrder.add(new DropdownItemObject(ORDER_REPLY_TIME, ID_ORDER_REPLY_TIME, "51"));
            datasetOrder.add(new DropdownItemObject(ORDER_PUBLISH_TIME, ID_ORDER_PUBLISH_TIME, "49"));
            datasetOrder.add(new DropdownItemObject(ORDER_HOT, ID_ORDER_HOT, "53"));
            dropdownOrder.bind(datasetOrder, chooseOrder, this, ID_ORDER_REPLY_TIME);
            dropdown_mask_out.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }
                @Override
                public void onAnimationEnd(Animation animation) {
                    if (currentDropdownList == null) {
                        reset();
                    }
                }
                @Override
                public void onAnimationRepeat(Animation animation) {
                }
            });
        }
        private List<DropdownItemObject> getCurrentLabels() {
            return dropdownType.current != null && dropdownType.current.id == ID_TYPE_MY ? datasetMyLabel : datasetAllLabel;
        }
        void updateLabels(List<DropdownItemObject> targetList) {
            if (targetList == getCurrentLabels()) {
                datasetLabel = targetList;
                dropdownLabel.bind(datasetLabel, chooseLabel, this, dropdownLabel.current.id);
            }
        }
        public void flushCounts() {
            datasetType.get(ID_TYPE_ALL).setSuffix(" (" + "5" + ")");
            datasetType.get(ID_TYPE_MY).setSuffix(" (" + "3" + ")");
            dropdownType.flush();
            dropdownLabel.flush();
        }
        void flushAllLabels() {
            flushLabels(datasetAllLabel);
        }
        void flushMyLabels() {
            flushLabels(datasetMyLabel);
        }
        private void flushLabels(List<DropdownItemObject> targetList) {
            while (targetList.size() > 1) targetList.remove(targetList.size() - 1);
            for (int i = 0, n = 5; i < n; i++) {
                int id = labels.get(i).getId();
                String name = labels.get(i).getName();
                if (TextUtils.isEmpty(name)) continue;
                int topicsCount = labels.get(i).getCount();
                // 只有all才做0数量过滤,因为my的返回数据总是0
                if (topicsCount == 0 && targetList == datasetAllLabel) continue;
                DropdownItemObject item = new DropdownItemObject(name, id, String.valueOf(id));
                if (targetList == datasetAllLabel)
                    item.setSuffix("(5)");
                targetList.add(item);
            }
            updateLabels(targetList);
        }
    }
}

有问题请指正。
源代码地址