今天下午写了一个分层级筛选控件,效果如下
该控件由两部分组成:
1.上面一排的筛选标题按钮(就是四个toggleButton,根据筛选项的数量动态追加)
2.点击筛选按钮弹出来的筛选内容(一个Popupwindow,它包含一个Gridview和一个Button)
需求开发点:
1.单个筛选项内容视图的生成,也就是那个Popupwindow的内容的生成
2.主控件的实现,根据筛选项的数量动添加上面一排内容(这里是四个筛选项),并且关联好每一个筛选项。
1.单个弹出内容视图的生成
我说的单个视图指的是下面红框框部分
首先分析,单个视图包含什么?
1)一个Gridview
2)一个底部Button
3)可筛选的数据List<\string>指的是A学校、B学校这些
4)其实上面的标题(大学、院系等)也归结为单个弹出视图的一部分,所以也得有一个变量叫title
总结: 所以单个视图的生成至少有以上四个成员变量,所以我们的单个视图实现如下:
package com.example.expandableview;
import java.util.ArrayList;
import java.util.List;
import com.example.expandableview.adapter.MyBaseAdapter;
import com.example.expandableview.adapter.ViewHolder;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.GridView;
import android.widget.LinearLayout;
public class ExpandleItemView extends LinearLayout {
/**显示在toggleButton的标题文字*/
public String mTitle;
/** 底部按钮 */
private Button mBottomBtn;
/** 展示要筛选的数据*/
private GridView mGridView;
/** 筛选的数据内容*/
private List<String> mGridviewDatas;
public ExpandleItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ExpandleItemView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public ExpandleItemView(Context context) {
this(context, null);
}
public ExpandleItemView(String title, Context context,List<String> datas) {
this(context);
setTitle(title);
mGridviewDatas = datas;
init();
}
private void init() {
setBackgroundColor(getResources().getColor(android.R.color.white));
/**将布局inflate到此视图中*/
LayoutInflater.from(getContext()).inflate(R.layout.expand_item_layout, this, true);
setOrientation(LinearLayout.VERTICAL);
mGridView = (GridView) findViewById(R.id.gridview);
mBottomBtn = (Button) findViewById(R.id.btn_all);
/**自己写的通用适配器,传入数据项和layoutid,一句话就使用了*/
mGridView.setAdapter(new MyBaseAdapter<String>(mGridviewDatas, R.layout.gridview_item, getContext()) {
@Override
protected void convert(ViewHolder viewHolder, String t) {
viewHolder.setBtnText(R.id.item_text, t);
}
});
/**每一个子项回调给监听者*/
mGridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if(mOnExpandItemClick != null)
{
mOnExpandItemClick.onItemClick(position);
}
}
});
/**底部按钮的点击事件回调给监听者*/
mBottomBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(mOnExpandItemClick != null)
{
mOnExpandItemClick.onBottomClick();
}
}
});
}
public String getTitle() {
return mTitle == null ? new String() : mTitle;
}
public void setTitle(String mTitle) {
this.mTitle = mTitle;
}
public List<String> getmGridviewDatas() {
return mGridviewDatas == null ? new ArrayList<String>() : mGridviewDatas;
}
public void setmGridviewDatas(List<String> mGridviewDatas) {
this.mGridviewDatas = mGridviewDatas;
}
/**
* 累加子类的高度作为自身的高度
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int cCount = getChildCount();
int desireWidth = MeasureSpec.getSize(widthMeasureSpec);
int desireHeight = 0;
for (int i = 0; i < cCount; i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
desireHeight += child.getMeasuredHeight();
}
setMeasuredDimension(desireWidth, desireHeight);
}
/**
* 点击item事件回调给监听者
* @author rander
*/
public interface OnExpandItemClick
{
void onItemClick(int position);
void onBottomClick();
}
private OnExpandItemClick mOnExpandItemClick;
public void setOnExpandItemClick(OnExpandItemClick onExpandItemClick) {
this.mOnExpandItemClick = onExpandItemClick;
}
}
该视图的布局R.layout.expand_item_layout如下:
<?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"
android:orientation="vertical" >
<GridView
android:id="@+id/gridview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:columnWidth="90dp"
android:gravity="center"
android:horizontalSpacing="10dp"
android:numColumns="auto_fit"
android:stretchMode="columnWidth"
android:descendantFocusability="blocksDescendants"
android:verticalSpacing="10dp" />
<include
layout="@layout/line" />
<Button
android:id="@+id/btn_all"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/bottom_selector"
android:text="全部"
android:textColor="@color/grey"
android:textSize="18dp" />
<include layout="@layout/line" />
</merge>
Gridview的Item布局R.layout.gridview_item,就是一个Button
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_text"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:gravity="center"
android:focusable="false"
android:clickable="false"
android:background="@drawable/btn_selctor"
>
</Button>
分析好了,写起代码来还是爽歪歪吧。
2.主体控件的实现
我说的主体控件就是这个控件的功能的实现了。
首先:分析主控件包含什么
1)有多少个筛选项List<\View>,这个View就是我们上面定义的ExpandleItemView,针对父类编程,兼容性好。也有人说所有的筛选项复用一个View就行了,是可以,但是没有必要给自己找麻烦。
2)上面有一排按钮,所以需要List<\View>来保存,我这里使用的是ToggleButton,针对父类编程,用View。
3)我们点击的时候总要记住当前筛选的是哪一项,所以我定义了一个ToggleButton记录当前的筛选项mSelectToggleBtn
4)内容的弹出使用PopupWindow,当然需要一个变量
5)还有两个变量,有没有发现选中和不选中的标题颜色是不一样的,所以定义两个变量记录选中的标题颜色和不选中的标题颜色。
总结:其实主控件有这也属性就行啦,当然这里我还定义了两个变量记录了PopupWindow的宽高属性,必要性不是很大。
OK,实现代码如下:
package com.example.expandableview;
import java.util.ArrayList;
import java.util.List;
import com.example.expandableview.ExpandleItemView.OnExpandItemClick;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.PopupWindow.OnDismissListener;
import android.widget.RelativeLayout;
import android.widget.ToggleButton;
public class ExpandableView extends LinearLayout implements OnExpandItemClick {
/** 记录选中的ToggleButton */
private ToggleButton mSelectToggleBtn;
/** 筛选 */
private List<View> mToggleButtons = new ArrayList<>();
/** 筛选项集合 */
private List<View> mPopupviews;
/** popupwindow展示的宽 */
private int mDisplayWidth;
/** popupwindow展示的高 */
private int mDisplayHeight;
/** 筛选内容用PopupWindow弹出来 */
private PopupWindow mPopupWindow;
private Context mContext;
/** toggleButton正常的字体颜色 */
int mNormalTextColor = getResources().getColor(R.color.grey);
/** toggleButton被选中的类型字体颜色 */
int mSelectTextColor = Color.RED;
public ExpandableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public ExpandableView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public ExpandableView(Context context) {
this(context, null);
}
private void init() {
setOrientation(LinearLayout.HORIZONTAL);
mDisplayWidth = getResources().getDisplayMetrics().widthPixels;
mDisplayHeight = getResources().getDisplayMetrics().heightPixels;
mContext = getContext();
setBackgroundResource(R.drawable.choosearea_bg_right);
}
/**
* 初始化数据和布局,做的工作如下: 1.根据筛选项的数量,动态增加上面一排ToggleButton 2.设置每一个ToggleButton的监听事件
* 3.toggleButton.setTag(i)这一句非常重要,我们取View数据都是根据这个tag取的 4.
*
* @param views
*/
public void initViews(List<ExpandleItemView> views) {
mPopupviews = new ArrayList<>();
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i < views.size(); i++) {
ExpandleItemView view = views.get(i);
view.setOnExpandItemClick(this);
final RelativeLayout r = new RelativeLayout(mContext);
RelativeLayout.LayoutParams rl = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
r.addView(view, rl);
mPopupviews.add(r);
final ToggleButton toggleButton = (ToggleButton) inflater.inflate(R.layout.toggle_button, this, false);
toggleButton.setText(view.getTitle());
mToggleButtons.add(toggleButton);
addView(toggleButton);
toggleButton.setTag(i);
toggleButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
/** 记录选中的ToggleButton,有了这个什么都好办 */
mSelectToggleBtn = toggleButton;
showPopWindow();
}
});
/**
* 点击popupwindow外部,就隐藏popupwindow,这个r是点击事件包裹了一个ExpandleItemView
* 如果用户所点之处为ExpandleItemView所在范围,点击事件由ExpandleItemView,如果点到
* ExpandleItemView外面,则有r处理,处理方式就是收缩
*/
r.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onPressBack();
}
private void onPressBack() {
hidePopWindow();
}
});
}
}
/**
* 隐藏popupWindow,并且重置ToggleButton字体颜色
*/
private void hidePopWindow() {
if (mPopupWindow != null) {
mPopupWindow.dismiss();
}
if (mSelectToggleBtn != null) {
mSelectToggleBtn.setTextColor(mNormalTextColor);
mSelectToggleBtn.setChecked(false);
}
}
/**
* 显示popupWindow
*/
private void showPopWindow() {
if (null == mPopupWindow) {
mPopupWindow = new PopupWindow(mPopupviews.get((int) mSelectToggleBtn.getTag()), mDisplayWidth,
mDisplayHeight);
/** 监听popupWindow的收缩,并重置字体颜色 */
mPopupWindow.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss() {
if (mSelectToggleBtn != null) {
mSelectToggleBtn.setTextColor(mNormalTextColor);
mSelectToggleBtn.setChecked(false);
}
}
});
mPopupWindow.setAnimationStyle(R.style.PopupWindowAnimation);
mPopupWindow.setFocusable(true);
mPopupWindow.setOutsideTouchable(true);
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());
} else {
mPopupWindow.setContentView(mPopupviews.get((int) mSelectToggleBtn.getTag()));
}
if (mPopupWindow.isShowing()) {
hidePopWindow();
} else {
/** 显示的时候,设为选中颜色 */
mSelectToggleBtn.setTextColor(mSelectTextColor);
mPopupWindow.showAsDropDown(mToggleButtons.get(0), 0, 0);
}
}
/**
* Item项选中的回调 注意Tag的使用 筛选项视图是根据tag拿的,因为mSelectToggleBtn的tag就是视图的索引
* mSelectToggleBtn显示筛选的内容
*/
@Override
public void onItemClick(int position) {
hidePopWindow();
if (null != mSelectToggleBtn) {
int selectBtnIndex = (int) mSelectToggleBtn.getTag();
mSelectToggleBtn
.setText(((ExpandleItemView) (((RelativeLayout) mPopupviews.get(selectBtnIndex)).getChildAt(0)))
.getmGridviewDatas().get(position));
}
}
/**
* 底部按钮点击的回调 mSelectToggleBtn显示筛选的内容
*/
@Override
public void onBottomClick() {
hidePopWindow();
if (null != mSelectToggleBtn) {
int selectBtnIndex = (int) mSelectToggleBtn.getTag();
mSelectToggleBtn
.setText((((ExpandleItemView) (((RelativeLayout) mPopupviews.get(selectBtnIndex)).getChildAt(0)))
.getTitle()));
}
}
}
上面使用的单个ToggleButton的视图R.layout.toggle_button如下:
<?xml version="1.0" encoding="utf-8"?>
<ToggleButton xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_weight="1"
android:background="@null"
android:drawablePadding="-10dp"
android:drawableRight="@drawable/index_icon_targetdown_grey"
android:gravity="center"
android:paddingRight="20dp"
android:singleLine="true"
android:textColor="@color/grey"
android:textOff="@null"
android:textOn="@null"
android:textSize="16sp"
android:textStyle="bold" >
</ToggleButton>
接下来就是使用了,使用的布局如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.expandableview.ExpandableView
android:id="@+id/expandview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
</com.example.expandableview.ExpandableView>
</RelativeLayout>
Activity代码如下:
package com.example.expandableview;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
private ExpandableView mExpandableView;
private Map<String, ExpandleItemView> mExpandleItemViews;
private String[] mColleages = { "A学校", "B学校", "C学校", "D学校", "E学校", "F学校", "G学校", "H学校", "I学校", "J学校" };
private String[] mDepartments = { "A系", "B系", "C系", "D系", "E系", "F系", "G系", "H系" , "I系" };
private String[] mProfessions = { "A专业", "B专业", "C专业" , "D专业" , "E专业" , "F专业" };
private String[] mClasses = { "A班", "B班", "C班", "D班" , "E班" };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mExpandableView = (ExpandableView) findViewById(R.id.expandview);
mExpandleItemViews = new LinkedHashMap<>();
//不要问我为什么这么用,因为我想用LinkedHashMap
mExpandleItemViews.put("大学", new ExpandleItemView("大学", this, Arrays.asList(mColleages)));
mExpandleItemViews.put("院系", new ExpandleItemView("院系", this, Arrays.asList(mDepartments)));
mExpandleItemViews.put("专业", new ExpandleItemView("专业", this, Arrays.asList(mProfessions)));
mExpandleItemViews.put("班级", new ExpandleItemView("班级", this, Arrays.asList(mClasses)));
mExpandableView.initViews(new ArrayList<>(mExpandleItemViews.values()));
}
}
使用起来如此easy,效果就是上面看到的效果了