Android 自定义分层级筛选控件

今天下午写了一个分层级筛选控件,效果如下
这里写图片描述
该控件由两部分组成:
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,效果就是上面看到的效果了

源码下载地址

github:https://github.com/shuangmin/ExpandableView

先写个雏形在这里,后续维护b

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值