編輯:關於Android編程
最近的項目中用到了類似美團中的下拉多選菜單,在實際開發過程中,也發現了一些問題,主要歸納如下:
1.當菜單較為復雜時,如果不能設計好代碼邏輯,將造成控件難於維護
2.美團菜單可以連續點擊頂部tab,切換不同菜單,而我使用的popupWindow似乎在展開一個菜單時點擊其他tab,菜單就會收回。
本文將針對如上兩個問題進行一些討論,最終給出較為合理的解決方案。
由於菜單涉及多級多項,如果把UI和其他邏輯堆在一起寫,必然會造成代碼過於龐大,甚至沒有辦法擴展,更談不上及時變更需求。
這裡我采用了組合控件和ViewHolder結合的辦法來處理耦合的問題。
組合控件的特點是可以直接定義在xml裡無需做其他任何多余的操作,ViewHolder則可以靈活地提供View,並將這些View貼到需要的地方。
基於上述特征,我將固定的菜單欄設計為組合控件,提供各項菜單的tab,而將菜單的具體內容使用ViewHolder封裝,在需要的時候從ViewHoder中拿到View,貼到我們需要放置的地方。同時,每個菜單中的UI邏輯也會被封裝到ViewHolder中,這樣,如果我們需要修改需求,直接改動對應的ViewHolder的代碼,而不會影響其他代碼。
這樣我們代碼就可以將復雜的UI邏輯分成相互獨立的小塊,想改哪裡改哪裡,媽媽再也不用擔心產品經理為難我了…………
翻閱網上很多仿制的美團菜單例程,幾乎都沒有真正和美團app的菜單一樣,我們可以查看官方app,點擊一個tab展開菜單,當在點擊下一個tab時,菜單並沒有收回,而是顯示了當前tab對應的內容。
由於很多demo都是使用popupWindow作為菜單的載體,而我在實際操作過程中發現popupWindow作為模態對話框非常難控制,而且還會引起其他問題,總之,我認為此處使用使用popupWindow並不合適。我在給菜單欄下面放了一塊空布局,當向空布局中添加View時,空布局擴大,也就形成了下拉菜單的效果。
那麼有同學要問了,這樣的話不就會影響下面的其他布局的位置了?是的,所以我們的菜單欄必須放在RelativeLayout或者FrameLayout這類結構中,而且必須放在其頂層。
了解了上述兩個問題的解決方法,我們就可以大概勾勒一樣我們的控件大體的模樣了。如下圖:
點擊TAB1,TAB2,TAB3,內容View被對應的Holder中維護的View替換,我們清空內容View中的view時,由於這個View是包裹內容的,內容為空時高度自然變成0,也就是菜單收起的狀態。我們可以為每個Holder設置相應的回調接口,這樣我們的菜單View就能根據Holder的變化實時做出響應。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="代碼實現">代碼實現
ViewHolder用來維護一個我們手動inflate出來的View,並提供刷新數據的方法。我們可以以此為基類,封裝UI邏輯,ViewHolder間也可以靈活替換。
/**
* 自繪控件封裝類
* Created by vonchenchen on 2015/11/3 0003.
*/
public abstract class BaseWidgetHolder {
protected View mRootView;
protected Context mContext;
public abstract View initView();
public abstract void refreshView(T data);
public BaseWidgetHolder(Context context){
mContext = context;
mRootView = initView();
mRootView.setTag(this);
}
public View getRootView(){
return mRootView;
}
}
這個View就是菜單欄主View,包括了三個TAB和下面的內容View,我們只需在工程中直接將這個類放入我們的布局文件中就可以了,注意,必須放在RelativieLayout或者FrameLayout中,而且必須是最頂層,否則內容View展開時會“擠”到其他布局。此處我們采用這種方式而不是popupWindow是因為popupWindow焦點改變可能會觸發消失,這樣無法實現點擊不同的tab時,連續切換菜單的效果。
/**
*
* 搜索菜單欄
* Created by vonchenchen on 2016/4/5 0005.
*/
public class SelectMenuView extends LinearLayout{
private static final int TAB_SUBJECT = 1;
private static final int TAB_SORT = 2;
private static final int TAB_SELECT = 3;
private Context mContext;
private View mSubjectView;
private View mSortView;
private View mSelectView;
private View mRootView;
private View mPopupWindowView;
private RelativeLayout mMainContentLayout;
private View mBackView;
/** type1 */
private SubjectHolder mSubjectHolder;
/** type2 */
private SortHolder mSortHolder;
/** type3 */
private SelectHolder mSelectHolder;
/** 與外部通信傳遞數據的接口 */
private OnMenuSelectDataChangedListener mOnMenuSelectDataChangedListener;
private RelativeLayout mContentLayout;
private TextView mSubjectText;
private ImageView mSubjectArrowImage;
private TextView mSortText;
private ImageView mSortArrowImage;
private TextView mSelectText;
private ImageView mSelectArrowImage;
private List mGroupList;
private List mPrimaryList;
private List mJuniorList;
private List mHighList;
private List> mSubjectDataList;
private int mTabRecorder = -1;
public SelectMenuView(Context context) {
super(context);
this.mContext = context;
this.mRootView = this;
init();
}
public SelectMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
this.mRootView = this;
init();
}
private void init(){
mGroupList = new ArrayList();
mGroupList.add("A");
mGroupList.add("B");
mGroupList.add("C");
mPrimaryList = new ArrayList();
mPrimaryList.add("A1");
mPrimaryList.add("A2");
mPrimaryList.add("A3");
mJuniorList = new ArrayList();
mJuniorList.add("B1");
mJuniorList.add("B2");
mJuniorList.add("B3");
mJuniorList.add("B4");
mJuniorList.add("B5");
mJuniorList.add("B6");
mJuniorList.add("B7");
mJuniorList.add("B8");
mJuniorList.add("B9");
mHighList = new ArrayList();
mHighList.add("C1");
mHighList.add("C2");
mHighList.add("C3");
mHighList.add("C4");
mHighList.add("C5");
mHighList.add("C6");
mHighList.add("C7");
mHighList.add("C8");
mHighList.add("C9");
mSubjectDataList = new ArrayList>();
mSubjectDataList.add(mGroupList);
mSubjectDataList.add(mPrimaryList);
mSubjectDataList.add(mJuniorList);
mSubjectDataList.add(mHighList);
//type1
mSubjectHolder = new SubjectHolder(mContext);
mSubjectHolder.refreshData(mSubjectDataList, 0, -1);
mSubjectHolder.setOnRightListViewItemSelectedListener(new SubjectHolder.OnRightListViewItemSelectedListener() {
@Override
public void OnRightListViewItemSelected(int leftIndex, int rightIndex, String text) {
if(mOnMenuSelectDataChangedListener != null){
int grade = leftIndex+1;
int subject = getSubjectId(rightIndex);
mOnMenuSelectDataChangedListener.onSubjectChanged(grade+"", subject+"");
}
dismissPopupWindow();
//Toast.makeText(UIUtils.getContext(), text, Toast.LENGTH_SHORT).show();
mSubjectText.setText(text);
}
});
//type2
mSortHolder = new SortHolder(mContext);
mSortHolder.setOnSortInfoSelectedListener(new SortHolder.OnSortInfoSelectedListener() {
@Override
public void onSortInfoSelected(String info) {
if(mOnMenuSelectDataChangedListener != null){
mOnMenuSelectDataChangedListener.onSortChanged(info);
}
dismissPopupWindow();
mSortText.setText(getSortString(info));
//Toast.makeText(UIUtils.getContext(), info, Toast.LENGTH_SHORT).show();
}
});
//type3
mSelectHolder = new SelectHolder(mContext);
mSelectHolder.setOnSelectedInfoListener(new SelectHolder.OnSelectedInfoListener() {
@Override
public void OnselectedInfo(String gender, String type) {
if(mOnMenuSelectDataChangedListener != null){
mOnMenuSelectDataChangedListener.onSelectedChanged(gender, type);
}
dismissPopupWindow();
//Toast.makeText(UIUtils.getContext(), gender+" "+type, Toast.LENGTH_SHORT).show();
}
});
}
private int getSubjectId(int index){
return index;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
View.inflate(mContext, R.layout.layout_search_menu, this);
mSubjectText = (TextView) findViewById(R.id.subject);
mSubjectArrowImage = (ImageView) findViewById(R.id.img_sub);
mSortText = (TextView) findViewById(R.id.comprehensive_sorting);
mSortArrowImage = (ImageView) findViewById(R.id.img_cs);
mSelectText = (TextView) findViewById(R.id.tv_select);
mSelectArrowImage = (ImageView) findViewById(R.id.img_sc);
mContentLayout = (RelativeLayout) findViewById(R.id.rl_content);
mPopupWindowView = View.inflate(mContext, R.layout.layout_search_menu_content, null);
mMainContentLayout = (RelativeLayout) mPopupWindowView.findViewById(R.id.rl_main);
//mBackView = mPopupWindowView.findViewById(R.id.ll_background);
mSubjectView = findViewById(R.id.ll_subject);
mSortView = findViewById(R.id.ll_sort);
mSelectView = findViewById(R.id.ll_select);
//點擊 type1 彈出菜單
mSubjectView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(mOnMenuSelectDataChangedListener != null){
mOnMenuSelectDataChangedListener.onViewClicked(mSubjectView);
}
handleClickSubjectView();
}
});
//點擊 type2 彈出菜單
mSortView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(mOnMenuSelectDataChangedListener != null){
mOnMenuSelectDataChangedListener.onViewClicked(mSortView);
}
handleClickSortView();
}
});
//點擊 type3 彈出菜單
mSelectView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(mOnMenuSelectDataChangedListener != null){
mOnMenuSelectDataChangedListener.onViewClicked(mSelectView);
}
handleClickSelectView();
}
});
//點擊黑色半透明部分,菜單收回
mContentLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dismissPopupWindow();
}
});
}
private void handleClickSubjectView(){
//清空內容View中的View
mMainContentLayout.removeAllViews();
//將我們已經創建好的ViewHolder拿出,取出其中的View貼到內容View中
mMainContentLayout.addView(mSubjectHolder.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
//處理彈窗動作
popUpWindow(TAB_SUBJECT);
}
private void handleClickSortView(){
mMainContentLayout.removeAllViews();
mMainContentLayout.addView(mSortHolder.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
popUpWindow(TAB_SORT);
}
private void handleClickSelectView(){
mMainContentLayout.removeAllViews();
mMainContentLayout.addView(mSelectHolder.getRootView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
popUpWindow(TAB_SELECT);
}
private void popUpWindow(int tab){
if(mTabRecorder != -1) {
resetTabExtend(mTabRecorder);
}
extendsContent();
setTabExtend(tab);
mTabRecorder = tab;
}
private void extendsContent(){
mContentLayout.removeAllViews();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mContentLayout.addView(mPopupWindowView, params);
}
private void dismissPopupWindow(){
mContentLayout.removeAllViews();
setTabClose();
}
public void setOnMenuSelectDataChangedListener(OnMenuSelectDataChangedListener onMenuSelectDataChangedListener){
this.mOnMenuSelectDataChangedListener = onMenuSelectDataChangedListener;
}
public interface OnMenuSelectDataChangedListener{
void onSubjectChanged(String grade, String subjects);
void onSortChanged(String sortType);
void onSelectedChanged(String gender, String classType);
void onViewClicked(View view);
//篩選菜單,當點擊其他處菜單收回後,需要更新當前選中項
void onSelectedDismissed(String gender, String classType);
}
private void setTabExtend(int tab){
if(tab == TAB_SUBJECT){
mSubjectText.setTextColor(getResources().getColor(R.color.blue));
mSubjectArrowImage.setImageResource(R.mipmap.ic_up_blue);
}else if(tab == TAB_SORT){
mSortText.setTextColor(getResources().getColor(R.color.blue));
mSortArrowImage.setImageResource(R.mipmap.ic_up_blue);
}else if(tab == TAB_SELECT){
mSelectText.setTextColor(getResources().getColor(R.color.blue));
mSelectArrowImage.setImageResource(R.mipmap.ic_up_blue);
}
}
private void resetTabExtend(int tab){
if(tab == TAB_SUBJECT){
mSubjectText.setTextColor(getResources().getColor(R.color.gray));
mSubjectArrowImage.setImageResource(R.mipmap.ic_down);
}else if(tab == TAB_SORT){
mSortText.setTextColor(getResources().getColor(R.color.gray));
mSortArrowImage.setImageResource(R.mipmap.ic_down);
}else if(tab == TAB_SELECT){
mSelectText.setTextColor(getResources().getColor(R.color.gray));
mSelectArrowImage.setImageResource(R.mipmap.ic_down);
}
}
private void setTabClose(){
mSubjectText.setTextColor(getResources().getColor(R.color.text_color_gey));
mSubjectArrowImage.setImageResource(R.mipmap.ic_down);
mSortText.setTextColor(getResources().getColor(R.color.text_color_gey));
mSortArrowImage.setImageResource(R.mipmap.ic_down);
mSelectText.setTextColor(getResources().getColor(R.color.text_color_gey));
mSelectArrowImage.setImageResource(R.mipmap.ic_down);
}
private String getSortString(String info){
if(SortHolder.SORT_BY_NORULE.equals(info)){
return "sort1";
}else if(SortHolder.SORT_BY_EVALUATION.equals(info)){
return "sort2";
}else if(SortHolder.SORT_BY_PRICELOW.equals(info)){
return "sort3";
}else if(SortHolder.SORT_BY_PRICEHIGH.equals(info)){
return "sort4";
}else if(SortHolder.SORT_BY_DISTANCE.equals(info)){
return "sort5";
}
return "sort1";
}
public void clearAllInfo(){
//清除控件內部選項
mSubjectHolder.refreshData(mSubjectDataList, 0, -1);
mSortHolder.refreshView(null);
mSelectHolder.refreshView(null);
//清除菜單欄顯示
mSubjectText.setText("type1");
mSortText.setText("type2");
}
}
以下是demo的實現效果,點擊不同tab,下面菜單實現連續切換:
代碼地址 https://git.oschina.net/vonchenchen/menu_demo.git
xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:a
前言: 周末真的是除了睡覺還是睡覺啊O(∩_∩)O~,打開博客,看到別人大牛寫的東西的時候,感覺差距好大啊,自己要學習的東西太多太多了,不管怎樣,現在還是
package com.example.xh.myapplication;import android.content.ComponentName;import andr
Toast大家都很熟,不多說。直接上圖上代碼。 具體代碼如下:main.xm