今天下午写了一个分层级筛选控件,效果如下
该控件由两部分组成:
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,效果就是上面看到的效果了
源码下载地址