1. 程式人生 > >Android常見多條件篩選選單(美團、58)

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