Filter DropDownMenu---带过滤功能的下拉菜单
转自:chenfuduo.me 效果还是很不错的,要是封装一下就更好了。
带过滤功能的下拉菜单在美团网移动端,腾讯课堂移动端都可以看到,本篇就是实现这个功能。
效果图
1
分析
从上面的效果图中可以看出,我们可以这样做:
-
自定义顶部的每一个按钮(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);
}
}
}
有问题请指正。
源代码地址