最近看到有人用Dialog来实现QQ的仿ActionSheet的自定义菜单,对于自己没实现过的一些控件,看着也想实现一下。于是动手了一下,发现也不难,和大家分享一下。
本文原创,转载请注明出处:http://blog.csdn.net/maosidiaoxian/article/details/46119197
在这里我也是用Dialog来实现,代码不多,这里说一下实现的过程。
菜单的布局文件
首先我们写先一下菜单的布局文件,很简单,一个ListView菜单再加一个取消的Button。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/menu_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:listSelector="@android:color/transparent"/>
<Button
android:id="@+id/menu_cancel"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="取消"/>
</LinearLayout>
在这里我们先是写一下最基本的布局文件,因为我急着想知道实现上的可行性,所以背景那些暂未修改。
继承Dialog实现自己的菜单
我们的对话框有几个特点,一是弹出的位置在底部,二是没有对话框的那些windowFrame层也没有标题和contentOverlay层,并且背景透明。
所以我们要先写一个Dialog的Style,继承自系统主题:
<style name="ActionSheetDialog" parent="android:Theme.Dialog">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
</style>
接下来我们需要写一个类继承Dialog,来实现自己的弹出菜单。在构造方法中调用super(Context context, int theme)
方法。并且我们尝试设置gravity,来使它显示在底部。
public class ActionSheet extends Dialog {
private Button mCancel;
private ListView mMenuItems;
private ArrayAdapter<String> mAdapter;
public ActionSheet(Context context) {
super(context, R.style.ActionSheetDialog);
getWindow().setGravity(Gravity.BOTTOM);
initView(context);
}
private void initView(Context context) {
View rootView = View.inflate(context, R.layout.dialog_action_sheet, null);
mCancel = (Button) rootView.findViewById(R.id.menu_cancel);
mMenuItems = (ListView) rootView.findViewById(R.id.menu_items);
mAdapter = new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1);
mMenuItems.setAdapter(mAdapter);
this.setContentView(rootView);
}
public ActionSheet addMenuItem(String items) {
mAdapter.add(items);
return this;
}
public void toggle() {
if (isShowing()) {
dismiss();
} else {
show();
}
}
@Override
public void show() {
mAdapter.notifyDataSetChanged();
super.show();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
dismiss();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
在该类当中,我们需要拦截MENU键,处理按下MENU时菜单消失。
写一个Activity来验证可行性
然后写我们的Activity,来显示我们的Dialog,看是否如我们所想。
public class MainActivity extends ActionBarActivity {
private ActionSheet mActionSheet;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 创建MENU
*/
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("menu").setVisible(false);// 必须创建一项,设为false之后ActionBar上不会出现菜单按钮。
return super.onCreateOptionsMenu(menu);
}
/**
* 拦截MENU事件,显示自己的菜单
*/
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
if (mActionSheet == null) {
mActionSheet = new ActionSheet(this);
mActionSheet.addMenuItem("Test1").addMenuItem("Test2");
}
mActionSheet.show();
return true;
}
}
需要注意的是,要显示我们自己的菜单,只重写Activity的onKeyDown
在那里显示是实现不了的。需要继承自onCreateOptionsMenu
方法并添加一项菜单,然后才可以在onMenuOpened
当中显示。
网上传的方法是说在onCreateOptionsMenu
添加一项,然后在onMenuOpened
中弹出我们的菜单并返回true
。但是这样写有一个问题,就是在ActionBar的右边还是会有一个菜单键。在各种尝试中,我发现了一个很简单的解决此问题的方法。就是在onCreateOptionsMenu
中添加了一项菜单之后,设为不可见。接下来在onMenuOpened
弹出菜单之后,返回true和false都可以,都不会显示系统原来的菜单了。
修改我们的菜单
上面的代码跑起来,主要的效果确实如我们所想,所以接下来我们就需要对菜单的外观进行大的修改,来让它更像是QQ的菜单。
背景
首先,是菜单背景。菜单的背景共有四种,分别是在四个角中,仅上面圆角,仅下面圆角,都为圆角,都不为圆角。其次,背景都是半透明的。所以我们先在colors.xml中定义背景的颜色,包括正常状态时的颜色及按下去状态的颜色。
<color name="menu_item_normal">#c9ffffff</color>
<color name="menu_item_pressed">#d5dadada</color>
接着在drawable里编写这四个背景的selector。
menu_iten_top.xml,仅上面是圆角的背景。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<corners android:topLeftRadius="@dimen/list_corner"
android:topRightRadius="@dimen/list_corner"/>
<solid android:color="@color/menu_item_pressed"/>
</shape>
</item>
<item>
<shape>
<corners android:topLeftRadius="@dimen/list_corner"
android:topRightRadius="@dimen/list_corner"/>
<solid android:color="@color/menu_item_normal"/>
</shape>
</item>
</selector>
menu_item_middle.xml,都不为圆角:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/menu_item_pressed"/>
</shape>
</item>
<item>
<shape >
<solid android:color="@color/menu_item_normal"/>
</shape>
</item>
</selector>
menu_item_bottom.xml,仅下面是圆角:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<corners android:bottomLeftRadius="@dimen/list_corner"
android:bottomRightRadius="@dimen/list_corner"/>
<solid android:color="@color/menu_item_pressed"/>
</shape>
</item>
<item>
<shape>
<corners android:bottomLeftRadius="@dimen/list_corner"
android:bottomRightRadius="@dimen/list_corner"/>
<solid android:color="@color/menu_item_normal"/>
</shape>
</item>
</selector>
menu_item_single.xml,均为圆角:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<corners android:radius="@dimen/list_corner"/>
<solid android:color="@color/menu_item_pressed"/>
</shape>
</item>
<item>
<shape>
<corners android:radius="@dimen/list_corner"/>
<solid android:color="@color/menu_item_normal"/>
</shape>
</item>
</selector>
其中取消按扭使用的是均为圆角的背景,所以回到菜单的布局文件中,对其修改。并且把ListView的listSelector设为透明,添加分割线,改完如下:
<ListView
android:id="@+id/menu_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#c9dddddd"
android:dividerHeight="1px"
android:listSelector="@android:color/transparent"/>
<Button
android:id="@+id/menu_cancel"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="@drawable/menu_item_single"
android:text="取消"
android:textColor="@color/menu_text"/>
接着修改ListView的每一项的背景,我们需要重写我们的Adapter,设置背景。在此之前,先定ListView的item的布局文件:
menu_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="@color/menu_text"
android:gravity="center"
android:minHeight="45dp" />
定义了文字颜色为蓝色:
<color name="menu_text">#f12162ff</color>
同时设置取消按钮的文字也是这个颜色。
重写Adapter,代码如下:
mAdapter = new ArrayAdapter<String>(context, R.layout.menu_item) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
setBackground(position, view);
return view;
}
private void setBackground(int position, View view) {
int count = getCount();
if (count == 1) {
view.setBackgroundResource(R.drawable.menu_item_single);
} else if (position == 0) {
view.setBackgroundResource(R.drawable.menu_item_top);
} else if (position == count - 1) {
view.setBackgroundResource(R.drawable.menu_item_bottom);
} else {
view.setBackgroundResource(R.drawable.menu_item_middle);
}
}
};
写完之后,给Activity的下面加点文字,看看背景透明度是否如我们的所想。这下子看起来很像了。但还是觉得有所欠缺,没错,我们还缺少动画。
动画
编写两个动画,一个是显示时弹出的,一个是消失的。
弹出动画:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="100%"
android:toYDelta="0"
android:duration="350">
</translate>
消失动画:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="0%"
android:toYDelta="100%"
android:duration="350">
</translate>
然后回到ActionSheet类,把我们的rootView重构为成员变量 ,因为我们的动画要加在它身上。同时,需要定义几个成员变量,分别是显示和消失的动画以及表示正在消失的boolean
变量。
private View mRootView;
private Animation mShowAnim;
private Animation mDismissAnim;
private boolean isDismissing;
然后是初始化动画变量,重写show和dismiss方法,加入播放动画的代码。注意,对父类的dismiss调用是在弹出动画结束之后才调用的,所以加入一个isDismissing表示这段过程,并添加一个私有方法dismissMe来调用父类的dismiss方法。代码如下:
private void initAnim(Context context) {
mShowAnim = AnimationUtils.loadAnimation(context, R.anim.translate_up);
mDismissAnim = AnimationUtils.loadAnimation(context, R.anim.translate_down);
mDismissAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
dismissMe();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
这是初始化动画的代码,该方法在initView中调用。然后是显示和隐藏菜单的代码:
@Override
public void show() {
mAdapter.notifyDataSetChanged();
super.show();
mRootView.startAnimation(mShowAnim);
}
@Override
public void dismiss() {
if(isDismissing) {
return;
}
isDismissing = true;
mRootView.startAnimation(mDismissAnim);
}
private void dismissMe() {
super.dismiss();
isDismissing = false;
}
加上动画之后,更逼真了些吧。但我们还漏了一个很重要的东西 。事件!
事件
首先,在ActionSheet里面定义一个接口:
interface MenuListener {
void onItemSelected(int position, String item);
void onCancel();
}
添加MenuListener
变量。
private MenuListener mMenuListener;
public MenuListener getMenuListener() {
return mMenuListener;
}
public void setMenuListener(MenuListener menuListener) {
mMenuListener = menuListener;
}
各种事件回调:
//取消按钮的事件
mCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel();
}
});
// 菜单的事件
mMenuItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mMenuListener != null) {
mMenuListener.onItemSelected(position, mAdapter.getItem(position));
dismiss();
}
}
});
// 对话框取消的回调
setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if(mMenuListener != null) {
mMenuListener.onCancel();
}
}
});
这下就基本完成了。
运行
然后再对我们的Activity略加修改,加入事件回调。
mActionSheet.setMenuListener(new ActionSheet.MenuListener() {
@Override
public void onItemSelected(int position, String item) {
Toast.makeText(MainActivity.this, item, Toast.LENGTH_SHORT).show();
}
@Override
public void onCancel() {
Toast.makeText(MainActivity.this, "onCancel", Toast.LENGTH_SHORT).show();
}
});
运行,效果如下(由于我是通过Android Studio屏幕录制先录成MP4再在线转换为GIF的,GIF有些大,所以我就不贴图了):http://v.youku.com/v_show/id_XOTY2MTM0ODM2.html
这篇博客由于主要是写实现的过程,所以有点长。实际上代码并不复杂,ActionSheet的全部代码加注释才170行。
项目地址(包括运行效果的录制视频):http://zdz.la/2pz0Ys
下一篇将写一下如何把它写成一个可复用的控件。