Android常见多条件筛选菜单(美团、58)

1.简单实现


1.1布局分析:

  自定义ListPopuScreenMenuView 继承自 LinearLayout,大致分为三个部分:上面头部TabLinearLayout;中间的菜单内容menuContainerFrameLayout;下面的半透明的translucentView。但是为了配合动画效果我们要对布局稍作修改,需要把menuContainerFrameLayout和translucentView放进一个FrameLayout中:

public class ListPopuScreenMenuView extends LinearLayout{
    private Context mContext;
    // 顶部菜单布局
    private LinearLayout mTabMenuView;
    // 内容都放在这里面
    private FrameLayout mMenuContainerView;
    // 遮罩半透明View,点击可关闭HuiDropDownMenu
    private View mMaskView;
    // 遮罩颜色
    private int mMaskColor = 0x88888888;
    // menu的高度
    protected int mMenuContainerHeight = 0;

    public ListPopuScreenMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        initAttribute(attrs);
        initLayout();
    }

    /**
     * 初始化自定义属性
     */
    private void initAttribute(AttributeSet attrs) {
        // TODO 设置自定义属性
    }

    /**
    * 初始化布局
    **/
    private void initLayout() {
        // 垂直排列
        setOrientation(VERTICAL);

        // 初始化tabMenuView并添加到this
        mTabMenuView = new LinearLayout(mContext);
        LayoutParams params = new LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        mTabMenuView.setOrientation(HORIZONTAL);
        mTabMenuView.setBackgroundResource(R.drawable.menu_tab_bg);
        mTabMenuView.setLayoutParams(params);
        addView(mTabMenuView);

        // 中间部分包括阴影和menu内容
        FrameLayout mMiddleView = new FrameLayout(mContext);
        mMiddleView.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        addView(mMiddleView);

        // 先初始化遮罩半透明View
        mMaskView = new View(getContext());
        mMaskView.setBackgroundColor(mMaskColor);
        mMaskView.setOnClickListener(this);
        mMiddleView.addView(mMaskView);
        mMaskView.setVisibility(GONE);

        // 后初始化containerView并将其添加到mMiddleView
        mMenuContainerView = new FrameLayout(mContext);
        mMiddleView.addView(mMenuContainerView);
        mMenuContainerView.setBackgroundColor(Color.WHITE);
        mMenuContainerView.setVisibility(GONE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mMenuContainerHeight = MeasureSpec.getSize(heightMeasureSpec) * 75 / 100;
        if(mMenuContainerHeight != 0 && mMenuContainerView.getHeight()<=0){
            // 高度占整个父View的75%
            mMenuContainerView.getLayoutParams().height = mMenuContainerHeight;
        }
    }
}
1.2内容的添加:

提供两个方法,我们往头部的TabLinearLayout中添加子Tab因为可以是TextView也可以是其他我们干脆就添加View:addTabView(View tabView);往中间的menuFrameLayout中添加菜单内容View:addMenuView(View menuView)

/**
* 添加Tab头部
* @param tabView
*/
public void addTabView(View tabView) {
    mTabMenuView.addView(tabView);
    LinearLayout.LayoutParams tabParams = (LayoutParams) tabView
            .getLayoutParams();
    // 权重设为1,每个子View占的宽度一样的
    tabParams.weight = 1;
}
/**
* 添加菜单View
*/
public void addMenuView(View menuView) {
    mMenuContainerView.addView(menuView);
    menuView.setVisibility(GONE);
}

到了这一步可以在activity中测试一下看看效果是否ok。

1.3处理Tab点击

  我们在添加头部Tab的时候顺便要给他设置点击事件,根据转态来控制什么时候菜单该打开,什么时候菜单该关闭,什么时候该要改变菜单布局。

// 当前tab点击的位置
protected int mCurrentPosition = -1;
// 动画是否正在执行
private boolean mAnimationExcute = false;

/**
* 添加Tab头部
* @param tabView
*/
public void addTabView(View tabView) {
    // ... 此处省略(上面有)
    // 设置Tag就知道我点击的是哪一个位置
    tabView.setTag(mTabViews.size());
    // 把tabView存入到列表中,当点击的时候就可以根据位置取出来
    mTabViews.add(tabView);
    switchTabViewClick(tabView);
}

private void switchTabViewClick(final View tabView) {
    tabView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
        // 动画是否执行完毕
        if(mAnimationExcute)return;
        // 根据tag拿到点击的位置
        int clickPosition = (int) tabView.getTag();
        // 处理点击
        tabClick(clickPosition,tabView);
        }
    });
}

/**
* 头部点击
*/
private void tabClick(int position, View tabView) {
    if(mCurrentPosition == position){
        // 如果当前点击的位置 == 之前点击的位置 关闭菜单,mCurrentPosition置为-1
        closeMenu(tabView);
    }
    else{
        if(mCurrentPosition == -1){
            // 显示当前的菜单
            mMenuContainerView.getChildAt(position).setVisibility(VISIBLE);
            // 打开菜单
            openMenu(tabView,position);
        }else{
            // 如果菜单是打开的改变布局
            exchangeLayout(position);
        }
        // 当前位置 == 点击位置
        mCurrentPosition = position;
    }
}
1.4处理Menu动画

1.openMenu():menu内容View和半透明View设置显示,menuContainerView设置位移动画,半透明View设置Alpha动画;
2.closeMenu():menu内容View和半透明View设置隐藏,menuContainerView设置位移动画,半透明View设置Alpha动画;
3.exchangeLayout():将之前显示的menuView设置隐藏,当前点击位置的menuView设置显示。

// 菜单是否打开
protected boolean mMenuIsOpen = false;

/**
* 关闭菜单
*/
public void closeMenu(View tabView) {
    mMenuIsOpen = false;
    mMenuContainerView.setVisibility(View.GONE);
    mMaskView.setVisibility(GONE);
    Animation menuOutAnimation = AnimationUtils.loadAnimation(getContext(),R.anim.dd_menu_out);
    Animation maskOutAnimation = AnimationUtils.loadAnimation(getContext(),R.anim.dd_mask_out);
    menuOutAnimation.setAnimationListener(this);
    mMenuContainerView.setAnimation(menuOutAnimation);
    mMaskView.setAnimation(maskOutAnimation);
}

/**
* 改变显示布局
*/
private void exchangeLayout(int position) {
    mMenuContainerView.getChildAt(position).setVisibility(VISIBLE);
    mMenuContainerView.getChildAt(mCurrentPosition).setVisibility(GONE);
}

/**
* 动画开始执行
*/
@Override
public void onAnimationStart(Animation animation) {
    mAnimationExcute = true;
}

/**
* 动画结束
*/
@Override
public void onAnimationEnd(Animation animation) {
    mAnimationExcute = false;
    if(!mMenuIsOpen){
        // 如果是关闭菜单,隐藏当前menuView
        mMenuContainerView.getChildAt(mCurrentPosition).setVisibility(GONE);
        // 当前位置 == -1
        mCurrentPosition = -1;
    }
}

/**
* 动画重复
*/
@Override
public void onAnimationRepeat(Animation animation) {
    mAnimationExcute = true;
}

到目前为止基本实现了功能,但是有几个问题:
1.点击的时候我要改变TabView的布局背景或是字体颜色,关闭的时候要还原等等;
2.View的形式千变万化各式各样如果在Activity中添加,那么activity的代码要有多少行呢?什么都放在activity中代码可读性就不说了。
3.等等 …
4.赶紧想办法解决
  

2.Android源码设计模式之Adapter适配器模式


2.1Android中适配器的运用

  ListView这个控件可以说是天天打交道,一般的用法大致如下:

// 代码省略
 ListView myListView = (ListView)findViewById(listview_id);
 // 设置适配器
 myListView.setAdapter(new MyAdapter(context, myDatas));

// 适配器
public class MyAdapter extends BaseAdapter{
        private LayoutInflater mInflater;
        List<String> mDatas ; 

        public MyAdapter(Context context, List<String> datas){
            this.mInflater = LayoutInflater.from(context);
            mDatas = datas ;
        }
        @Override
        public int getCount() {
            return mDatas.size();
        }

        @Override
        public String getItem(int pos) {
            return mDatas.get(pos);
        }

        @Override
        public long getItemId(int pos) {
            return pos;
        }

        // 解析、设置、缓存convertView以及相关内容
        @Override
        public View getView(int position, View convertView, ViewGroup parent) { 
            ViewHolder holder = null;
            // Item View的复用
            if (convertView == null) {
                holder = new ViewHolder();  
                convertView = mInflater.inflate(R.layout.my_listview_item, null);
                // 获取title
                holder.title = (TextView)convertView.findViewById(R.id.title);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.title.setText(mDatas.get(position));
            return convertView;
        }
    }

这看起来似乎还挺麻烦的,看到这里我们不禁要问,ListView为什么要使用Adapter模式呢?
我们知道,作为最重要的View,ListView需要能够显示各式各样的视图,每个人需要的显示效果各不相同,显示的数据类型、数量等也千变万化。那么如何隔离这种变化尤为重要。

Android的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter的接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的Item View。通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出为View ( getView函数 ),这样就很好的应对了Item View的可变性。

至于内部是如何运作的我打算再写一篇:Android设计模式源码解析之适配器(Adapter)模式
  

2.2本View中Adapter适配器的运用

  需要改变tabView显示效果需要 3,显示多少条 1,根据position位置得到tabView 1,根据position位置得到menuView 1 , 总共6方法

    public abstract class MenuBaseAdapter {
    /**
     * 关闭菜单:用于改变tabView显示状态
     * @param tabView
     */
    public abstract void overrideCloseMenu(View tabView);

    /**
     * 改变布局:
     * @param cureentView 当前点击的View
     * @param oldView 之前的View
     */
    public abstract void overrideExchangeLayout(View cureentView, View oldView,
        int currentPostion,int oldPosition);

    /**
     * 打开菜单:用于改变tabView显示状态
     */
    public abstract void overrideOpenMenu(View tabView,int position);

    /**
     * 得到多少条
     */
    public abstract int getCount();  

    /**
     * 得到Menu的内容
     */
    public abstract View getMenuView(int position,
        FrameLayout menuContainerView,ListPopuScreenMenuView parent);  

    /**
     * 得到Table的内容
     */
    public abstract View getTabView(int position,
        LinearLayout tabContainerView,ListPopuScreenMenuView parent); 

    /**
     * 关闭筛选菜单菜单
     */
    public void closeScreenMenu(View tabView){
        mObservable.closeScreenMenu(tabView);
    }

ListPopuScreenMenuView中添加setAdapter(MenuAdapter adapter)

/**
* 设置View适配器
*/
public void setAdapter(MenuBaseAdapter adapter){
    if(adapter == null){
        throw new NullPointerException("adapter is null...");
    }
    if(mAdapter != null){
        this.mAdapter = null;
        this.mTabMenuView.removeAllViews();
        this.mMenuContainerView.removeAllViews();
    }

    mAdapter = adapter;

    int count = mAdapter.getCount();
    for (int index = 0; index < count; index++) {
        // 添加Tab
        View childTabView = mAdapter.getTabView(index, mTabMenuView, this);
        if(childTabView != null){
            addTabView(childTabView);
        }
        // 添加menu
        View childMenuView = mAdapter.getMenuView(index, mMenuContainerView, this);
        if(childMenuView != null){
            addMenuView(childMenuView);
        }
    }
}

/**
* 关闭菜单
*/
public void closeMenu(View tabView) {
    // ... 省略
    if(mAdapter != null)
            mAdapter.overrideCloseMenu(tabView);
}


/**
* 打开菜单
*/
public void openMenu(View tabView,int position) {
    // ... 省略
    if(mAdapter != null)
            mAdapter.overrideOpenMenu(tabView, position);
}

/**
* 改变布局
*/
public void exchangeLayout(View tabView) {
    // ... 省略
    if(mAdapter != null)
            mAdapter.overrideExchangeLayout(cureentView, oldView,currentPostion,oldPosition);
}

最后只有一个问题了我们要在adapter中关闭menu,可是我们adapter中根本没有ListPopuScreenMenuView,但是我们还有一个没有用那就是观察者模式,利用观察者模式就很容易实现了。
附源码地址:http://download.csdn.net/detail/z240336124/9402725

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值