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