参照美团app下拉式的二级列表菜单,公司项目中也有这种菜单的需求
1,结构分析
首先,我们给出这个下来菜单需要的组建。我们用线框图来分析。
1)如上图所示,最外围的是一个Activity,顶部包含了一个View的容器,这个容器主要是装载ToggleButton来实现诸如美团里面的“美食,全城,理我最近,刷选”这一行。这一行一点就会弹出对应的下来菜单。
2)下拉菜单是如何实现的呢?,这里我们利用了PopupWindow来实现这一弹出式窗口。然后我们在弹出式窗口里面再定义我们的下来列表项,是单列还是二级菜单,都是由里面来定。
3)不同的菜单,需要一级或者需要二级,在这里根据我的需求而变动。我们在PopupWindow上面加一个自定义的LeftView,或者是MiddleView,RightView。
主要是一个ToggleButton,你弹出一个窗口,你就定制一个窗口。
3)视图里面嵌入ListView,就形成了列表项。
2,项目结构
本项目的项目结构如图所示:
1) Adapter。适配器,主要是为ListView提供数据适配的。
2)MainActivity。主活动页面。
3)ExpandTabView。本项目的核心类,它包含ToggleButton容器和PopupWindow,是控制弹出窗口的核心类。
4)ViewLeft,ViewMiddle,ViewRight。是弹出里面嵌套的类,实现不同的列表菜单。
先分析布局文件
选择器,用于点击不同区域之后改变颜色。
几个重要布局
主要布局文件
activity_main.xml
<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.view.ExpandTabView android:id="@+id/expandtab_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello_world" /> </RelativeLayout>
顶部按钮布局 ,背景设置为选择器
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="@dimen/expand_tab_item_height" android:textColor="@color/black" android:gravity="center_vertical" android:paddingLeft="10dp" android:textSize="17sp" android:background="@drawable/choose_eara_item_selector" > </TextView>
顶部ToggleButton,有弹出菜单和默认两种状态
<?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="wrap_content" android:layout_weight="1" android:gravity="center" android:text="ToggleButton" android:textOff="@null" android:textOn="@null" android:background="@drawable/expand_tab_selector" android:singleLine="true" android:textColor="@color/black"> </ToggleButton>
expand_tab_selector.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:Android="http://schemas.android.com/apk/res/android"> <item Android:drawable="@drawable/choosebar_press_down" Android:state_pressed="true"/> <item Android:drawable="@drawable/choosebar_press_up" Android:state_checked="true"/> <!-- focused --> <item Android:drawable="@drawable/choosebar_down"/> <!-- default --> </selector>
view_distance.xml,单列菜单布局
<?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:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:background="@drawable/choosearea_bg_left" > <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:cacheColorHint="@color/no_color" android:divider="#dcdcdc" android:dividerHeight="1px" android:paddingLeft="4dp" android:paddingRight="4dp" > </ListView> </merge>
注意这里的android:cacheColorHint属性
ListView是常用的显示控件,默认背景是和系统窗口一样的透明色,如果给ListView加上背景图片,或者背景颜色时,滚动时listView会黑掉,原因是,滚动时,列表里面的view重绘时,用的依旧是系统默认的透明色,颜色值为#FF191919,要改变这种情况,只需要调用listView的setCacheColorHint(0),颜色值设置为0或者xml文件中listView的属性 Android:cacheColorHint="#00000000"即可,滚动时,重绘View的时候就不会有背景颜色。
<merge />标签用于减少View树的层次来优化Android的布局。
Merge了解更多,可参考:
Android 性能优化 四 布局优化merge标签的使用
Re-using Layouts with <include/>
Android Layout Tricks #2: Reusing layouts
include与merge标签结合使用例子
Android Layout Tricks #3: Optimize by merging
《Android应用性能优化》 第8章 图形
更多优化相关的文章详见:《 Android 基础学习文章汇总》 第三部分 性能优化
view_regoin.xml 双列菜单布局文件
<?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="@dimen/expand_tab_eara_height" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:background="@drawable/choosearea_bg_mid" android:orientation="horizontal" > <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="@color/no_color" android:paddingLeft="4dp" android:layout_weight="1" android:scrollbars="none" android:dividerHeight="1px" android:divider="#dcdcdc" android:background="@color/white" > </ListView> <ListView android:id="@+id/listView2" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="@color/no_color" android:paddingRight="4dp" android:layout_weight="1" android:divider="@null" android:background="@color/choose_eara_item_press_color" > </ListView> </merge>
接下来看看ExpandTabView.java
package com.example.view; import java.util.ArrayList; import com.example.expandtabview.R; import android.app.Activity; import android.content.Context; 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.TextView; import android.widget.ToggleButton; /** * 菜单控件头部,封装了下拉动画,动态生成头部按钮个数 * * @author yueyueniao */ public class ExpandTabView extends LinearLayout implements OnDismissListener { private ToggleButton selectedButton; private ArrayList<String> mTextArray = new ArrayList<String>();//用于保存字符串的数组 private ArrayList<RelativeLayout> mViewArray = new ArrayList<RelativeLayout>();//用于保存RelativeLayout的数组 private ArrayList<ToggleButton> mToggleButton = new ArrayList<ToggleButton>();//保存顶部可变按钮 private Context mContext;//上下文环境 private final int SMALL = 0; private int displayWidth; private int displayHeight; private PopupWindow popupWindow; private int selectPosition; public ExpandTabView(Context context) { super(context); init(context); } public ExpandTabView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** * 根据选择的位置设置tabitem显示的值 */ public void setTitle(String valueText, int position) { if (position < mToggleButton.size()) { mToggleButton.get(position).setText(valueText); } } public void setTitle(String title){ } /** * 根据选择的位置获取tabitem显示的值 */ public String getTitle(int position) { if (position < mToggleButton.size() && mToggleButton.get(position).getText() != null) { return mToggleButton.get(position).getText().toString(); } return ""; } /** * 设置tabitem的个数和初始值 */ public void setValue(ArrayList<String> textArray, ArrayList<View> viewArray) { if (mContext == null) { return; } LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mTextArray = textArray; for (int i = 0; i < viewArray.size(); i++) { final RelativeLayout r = new RelativeLayout(mContext); int maxHeight = (int) (displayHeight * 0.7); //参数指定了子 View 的宽度和高度 RelativeLayout.LayoutParams rl = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, maxHeight); rl.leftMargin = 10; rl.rightMargin = 10; //Adds a child view with the specified layout parameters r.addView(viewArray.get(i), rl); mViewArray.add(r); r.setTag(SMALL); //添加ToggleButton ToggleButton tButton = (ToggleButton) inflater.inflate(R.layout.toggle_button, this, false); addView(tButton); View line = new TextView(mContext); line.setBackgroundResource(R.drawable.choosebar_line); if (i < viewArray.size() - 1) { LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(2, LinearLayout.LayoutParams.MATCH_PARENT); addView(line, lp); } mToggleButton.add(tButton); tButton.setTag(i); tButton.setText(mTextArray.get(i)); r.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onPressBack(); } }); r.setBackgroundColor(mContext.getResources().getColor(R.color.popup_main_background)); tButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // initPopupWindow(); ToggleButton tButton = (ToggleButton) view; if (selectedButton != null && selectedButton != tButton) { selectedButton.setChecked(false); } selectedButton = tButton; selectPosition = (Integer) selectedButton.getTag(); //开始弹出菜单 startAnimation(); if (mOnButtonClickListener != null && tButton.isChecked()) { mOnButtonClickListener.onClick(selectPosition); } } }); } } private void startAnimation() { //初次点击,生成popWindow if (popupWindow == null) { popupWindow = new PopupWindow(mViewArray.get(selectPosition), displayWidth, displayHeight); popupWindow.setAnimationStyle(R.style.PopupWindowAnimation); popupWindow.setFocusable(false); popupWindow.setOutsideTouchable(true); } //如果已经check if (selectedButton.isChecked()) { if (!popupWindow.isShowing()) { //如果popupWindow没有打开,则打开popupwindow showPopup(selectPosition); } else { //如果popupWindow打开,关闭menu popupWindow.setOnDismissListener(this); popupWindow.dismiss(); hideView(); } } //如果没有checked else { if (popupWindow.isShowing()) { popupWindow.dismiss(); hideView(); } } } private void showPopup(int position) { View tView = mViewArray.get(selectPosition).getChildAt(0); if (tView instanceof ViewBaseAction) { ViewBaseAction f = (ViewBaseAction) tView; f.show(); } if (popupWindow.getContentView() != mViewArray.get(position)) { popupWindow.setContentView(mViewArray.get(position)); } popupWindow.showAsDropDown(this, 0, 0); } /** * 如果菜单成展开状态,则让菜单收回去 */ public boolean onPressBack() { if (popupWindow != null && popupWindow.isShowing()) { popupWindow.dismiss(); hideView(); if (selectedButton != null) { selectedButton.setChecked(false); } return true; } else { return false; } } private void hideView() { View tView = mViewArray.get(selectPosition).getChildAt(0); if (tView instanceof ViewBaseAction) { ViewBaseAction f = (ViewBaseAction) tView; f.hide(); } } private void init(Context context) { mContext = context; displayWidth = ((Activity) mContext).getWindowManager().getDefaultDisplay().getWidth(); displayHeight = ((Activity) mContext).getWindowManager().getDefaultDisplay().getHeight(); setOrientation(LinearLayout.HORIZONTAL); } @Override public void onDismiss() { showPopup(selectPosition); popupWindow.setOnDismissListener(null); } private OnButtonClickListener mOnButtonClickListener; /** * 设置tabitem的点击监听事件 */ public void setOnButtonClickListener(OnButtonClickListener l) { mOnButtonClickListener = l; } /** * 自定义tabitem点击回调接口 */ public interface OnButtonClickListener { public void onClick(int selectPosition); } }
ViewBaseAction 接口
package com.example.view; public interface ViewBaseAction { /** * 菜单隐藏操作 */ public void hide(); /** * 菜单显示操作 */ public void show(); }
代码动态实现单列左侧菜单 ViewLeft.java
package com.example.view; import com.example.adapter.TextAdapter; import com.example.expandtabview.R; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Toast; public class ViewLeft extends RelativeLayout implements ViewBaseAction{ private ListView mListView; private final String[] items = new String[] { "item1", "item2", "item3", "item4", "item5", "item6" };//显示字段 private final String[] itemsVaule = new String[] { "1", "2", "3", "4", "5", "6" };//隐藏id private OnSelectListener mOnSelectListener; private TextAdapter adapter; private String mDistance; private String showText = "item1"; private Context mContext; public String getShowText() { return showText; } public ViewLeft(Context context) { super(context); init(context); } public ViewLeft(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public ViewLeft(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { mContext = context; LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.view_distance, this, true); setBackgroundDrawable(getResources().getDrawable(R.drawable.choosearea_bg_mid)); mListView = (ListView) findViewById(R.id.listView); //设置适配器 adapter = new TextAdapter(context, items, R.drawable.choose_item_right, R.drawable.choose_eara_item_selector); //设置列表字体大小 adapter.setTextSize(17); if (mDistance != null) { for (int i = 0; i < itemsVaule.length; i++) { if (itemsVaule[i].equals(mDistance)) { // 设置选中的position,但不通知刷新 adapter.setSelectedPositionNoNotify(i); showText = items[i]; break; } } } mListView.setAdapter(adapter); adapter.setOnItemClickListener(new TextAdapter.OnItemClickListener() { @Override public void onItemClick(View view, int position) { if (mOnSelectListener != null) { showText = items[position]; mOnSelectListener.getValue(itemsVaule[position], items[position]); } } }); } public void setOnSelectListener(OnSelectListener onSelectListener) { mOnSelectListener = onSelectListener; } public interface OnSelectListener { public void getValue(String distance, String showText); } @Override public void hide() { } @Override public void show() { } }
实现双列中间菜单,ViewMiddle.java
package com.example.view; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import android.content.Context; import android.graphics.Region; import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.ListView; import com.example.adapter.TextAdapter; import com.example.expandtabview.R; public class ViewMiddle extends LinearLayout implements ViewBaseAction { private ListView regionListView; private ListView plateListView; private ArrayList<String> groups = new ArrayList<String>(); private LinkedList<String> childrenItem = new LinkedList<String>(); private SparseArray<LinkedList<String>> children = new SparseArray<LinkedList<String>>(); private TextAdapter plateListViewAdapter; private TextAdapter earaListViewAdapter; private OnSelectListener mOnSelectListener; private int tEaraPosition = 0; private int tBlockPosition = 0; private String showString = "不限"; public ViewMiddle(Context context) { super(context); init(context); } public ViewMiddle(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public void updateShowText(String showArea, String showBlock) { if (showArea == null || showBlock == null) { return; } for (int i = 0; i < groups.size(); i++) { if (groups.get(i).equals(showArea)) { earaListViewAdapter.setSelectedPosition(i); childrenItem.clear();//Removes all elements from this LinkedList, leaving it empty. if (i < children.size()) { childrenItem.addAll(children.get(i)); } tEaraPosition = i; break; } } for (int j = 0; j < childrenItem.size(); j++) { if (childrenItem.get(j).replace("不限", "").equals(showBlock.trim())) { plateListViewAdapter.setSelectedPosition(j); tBlockPosition = j; break; } } setDefaultSelect(); } private void init(Context context) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.view_region, this, true); regionListView = (ListView) findViewById(R.id.listView); plateListView = (ListView) findViewById(R.id.listView2); setBackgroundDrawable(getResources().getDrawable( R.drawable.choosearea_bg_left)); //左边父菜单有10个选项,右边子菜单有15个选项 for(int i=0;i<10;i++){ groups.add(i+"行"); LinkedList<String> tItem = new LinkedList<String>(); for(int j=0;j<15;j++){ tItem.add(i+"行"+j+"列"); } children.put(i, tItem); } earaListViewAdapter = new TextAdapter(context, groups, R.drawable.choose_item_selected, R.drawable.choose_eara_item_selector); earaListViewAdapter.setTextSize(17); earaListViewAdapter.setSelectedPositionNoNotify(tEaraPosition); regionListView.setAdapter(earaListViewAdapter); earaListViewAdapter .setOnItemClickListener(new TextAdapter.OnItemClickListener() { @Override public void onItemClick(View view, int position) { if (position < children.size()) { childrenItem.clear(); childrenItem.addAll(children.get(position)); plateListViewAdapter.notifyDataSetChanged(); } } }); if (tEaraPosition < children.size()) childrenItem.addAll(children.get(tEaraPosition)); plateListViewAdapter = new TextAdapter(context, childrenItem, R.drawable.choose_item_right, R.drawable.choose_plate_item_selector); plateListViewAdapter.setTextSize(15); plateListViewAdapter.setSelectedPositionNoNotify(tBlockPosition); plateListView.setAdapter(plateListViewAdapter); //点击事件 plateListViewAdapter .setOnItemClickListener(new TextAdapter.OnItemClickListener() { @Override public void onItemClick(View view, final int position) { showString = childrenItem.get(position); if (mOnSelectListener != null) { mOnSelectListener.getValue(showString); } } }); if (tBlockPosition < childrenItem.size()) showString = childrenItem.get(tBlockPosition); if (showString.contains("不限")) { showString = showString.replace("不限", ""); } setDefaultSelect(); } public void setDefaultSelect() { regionListView.setSelection(tEaraPosition); plateListView.setSelection(tBlockPosition); } public String getShowText() { return showString; } public void setOnSelectListener(OnSelectListener onSelectListener) { mOnSelectListener = onSelectListener; } public interface OnSelectListener { public void getValue(String showText); } @Override public void hide() { // TODO Auto-generated method stub } @Override public void show() { // TODO Auto-generated method stub } }
右布局代码跟左布局类似
适配器
package com.example.adapter; import java.util.List; import com.example.expandtabview.R; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; public class TextAdapter extends ArrayAdapter<String> { private Context mContext; private List<String> mListData; private String[] mArrayData; private int selectedPos = -1; private String selectedText = ""; private int normalDrawbleId; private Drawable selectedDrawble; private float textSize; private OnClickListener onClickListener; private OnItemClickListener mOnItemClickListener; //sId,nId为图片id,mListData为列表内容 public TextAdapter(Context context, List<String> listData, int sId, int nId) { super(context, R.string.no_data, listData); mContext = context; mListData = listData; selectedDrawble = mContext.getResources().getDrawable(sId); normalDrawbleId = nId; init(); } private void init() { onClickListener = new OnClickListener() { @Override public void onClick(View view) { //得到Tag,根据Tag得到位置 selectedPos = (Integer) view.getTag(); setSelectedPosition(selectedPos); if (mOnItemClickListener != null) { mOnItemClickListener.onItemClick(view, selectedPos); } } }; } public TextAdapter(Context context, String[] arrayData, int sId, int nId) { super(context, R.string.no_data, arrayData); mContext = context; mArrayData = arrayData; selectedDrawble = mContext.getResources().getDrawable(sId); normalDrawbleId = nId; init(); } /** * 设置选中的position,并通知列表刷新 */ public void setSelectedPosition(int pos) { if (mListData != null && pos < mListData.size()) { selectedPos = pos; selectedText = mListData.get(pos); notifyDataSetChanged(); } else if (mArrayData != null && pos < mArrayData.length) { selectedPos = pos; selectedText = mArrayData[pos]; notifyDataSetChanged(); } } /** * 设置选中的position,但不通知刷新 */ public void setSelectedPositionNoNotify(int pos) { selectedPos = pos; if (mListData != null && pos < mListData.size()) { selectedText = mListData.get(pos); } else if (mArrayData != null && pos < mArrayData.length) { selectedText = mArrayData[pos]; } } /** * 获取选中的position */ public int getSelectedPosition() { if (mArrayData != null && selectedPos < mArrayData.length) { return selectedPos; } if (mListData != null && selectedPos < mListData.size()) { return selectedPos; } return -1; } /** * 设置列表字体大小 */ public void setTextSize(float tSize) { textSize = tSize; } @Override public View getView(int position, View convertView, ViewGroup parent) { TextView view; if (convertView == null) { view = (TextView) LayoutInflater.from(mContext).inflate(R.layout.choose_item, parent, false); } else { view = (TextView) convertView; } // view.setTag(position); String mString = ""; if (mListData != null) { if (position < mListData.size()) { mString = mListData.get(position); } } else if (mArrayData != null) { if (position < mArrayData.length) { mString = mArrayData[position]; } } if (mString.contains("不限")) view.setText("不限"); else view.setText(mString); view.setTextSize(TypedValue.COMPLEX_UNIT_SP,textSize); if (selectedText != null && selectedText.equals(mString)) { view.setBackgroundDrawable(selectedDrawble);//设置选中的背景图片 } else { view.setBackgroundDrawable(mContext.getResources().getDrawable(normalDrawbleId));//设置未选中状态背景图片 } view.setPadding(20, 0, 0, 0); view.setOnClickListener(onClickListener); return view; } public void setOnItemClickListener(OnItemClickListener l) { mOnItemClickListener = l; } /** * 重新定义菜单选项单击接口 */ public interface OnItemClickListener { public void onItemClick(View view, int position); } }
MainActivity.java
package com.example.expandtabview; import java.util.ArrayList; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Toast; import com.example.view.ExpandTabView; import com.example.view.ViewLeft; import com.example.view.ViewMiddle; import com.example.view.ViewRight; public class MainActivity extends Activity { private ExpandTabView expandTabView; private ArrayList<View> mViewArray = new ArrayList<View>(); private ViewLeft viewLeft; private ViewMiddle viewMiddle; private ViewRight viewRight; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initVaule(); initListener(); } private void initView() { expandTabView = (ExpandTabView) findViewById(R.id.expandtab_view); viewLeft = new ViewLeft(this); viewMiddle = new ViewMiddle(this); viewRight = new ViewRight(this); } private void initVaule() { mViewArray.add(viewLeft); mViewArray.add(viewMiddle); mViewArray.add(viewRight); ArrayList<String> mTextArray = new ArrayList<String>(); mTextArray.add("距离"); mTextArray.add("区域"); mTextArray.add("距离"); expandTabView.setValue(mTextArray, mViewArray); expandTabView.setTitle(viewLeft.getShowText(), 0); expandTabView.setTitle(viewMiddle.getShowText(), 1); expandTabView.setTitle(viewRight.getShowText(), 2); } private void initListener() { viewLeft.setOnSelectListener(new ViewLeft.OnSelectListener() { @Override public void getValue(String distance, String showText) { onRefresh(viewLeft, showText); } }); viewMiddle.setOnSelectListener(new ViewMiddle.OnSelectListener() { @Override public void getValue(String showText) { onRefresh(viewMiddle,showText); } }); viewRight.setOnSelectListener(new ViewRight.OnSelectListener() { @Override public void getValue(String distance, String showText) { onRefresh(viewRight, showText); } }); } private void onRefresh(View view, String showText) { expandTabView.onPressBack(); int position = getPositon(view); if (position >= 0 && !expandTabView.getTitle(position).equals(showText)) { expandTabView.setTitle(showText, position); } Toast.makeText(MainActivity.this, showText, Toast.LENGTH_SHORT).show(); } private int getPositon(View tView) { for (int i = 0; i < mViewArray.size(); i++) { if (mViewArray.get(i) == tView) { return i; } } return -1; } @Override public void onBackPressed() { if (!expandTabView.onPressBack()) { finish(); } } }