Android开发技巧——使用Dialog实现仿QQ的ActionSheet菜单
最近看到有人用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
下一篇将写一下如何把它写成一个可复用的控件。
Android开发技巧——使用Dialog实现仿QQ的ActionSheet菜单的更多相关文章
- 【Android开发】【布局】 仿QQ的UI
Demo地址
- Android开发技巧——实现可复用的ActionSheet菜单
在上一篇<Android开发技巧--使用Dialog实现仿QQ的ActionSheet菜单>中,讲了这种菜单的实现过程,接下来将把它改成一个可复用的控件库. 本文原创,转载请注明出处: h ...
- Android仿QQ ios dialog,仿QQ退出向上菜单
Android仿QQ ios dialog,仿QQ退出向上菜单 EasyDialog两种模式 仿QQ退出向上菜单,自己定义向上菜单 github地址:https://gith ...
- Android开发技巧——自定义控件之使用style
Android开发技巧--自定义控件之使用style 回顾 在上一篇<Android开发技巧--自定义控件之自定义属性>中,我讲到了如何定义属性以及在自定义控件中获取这些属性的值,也提到了 ...
- Android开发技巧——大图裁剪
本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...
- Android开发技巧——使用PopupWindow实现弹出菜单
在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...
- Android开发技巧——高亮的用户操作指南
Android开发技巧--高亮的用户操作指南 2015-12-15补记: 发现使用PopupWindow进行遮罩层的显示,在华为P7上会有问题.具体表现为:画出来的高亮部分会偏下.原因为:通过view ...
- 50个android开发技巧
50个android开发技巧 http://blog.csdn.net/column/details/androidhacks.html
- Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命
Android特效专辑(六)--仿QQ聊天撒花特效,无形装逼,最为致命 我的关于特效的专辑已经在CSDN上申请了一个专栏--http://blog.csdn.net/column/details/li ...
随机推荐
- JAVA面向对象-----多态
多态的概述 1:什么是多态 一个对象的多种状态 (老师)(员工)(儿子) 教师 a =老钟; 员工 b= 老钟; 2:多态体现 1:Father类 1:非静态成员变量x 2:静态成员变量y 3:非静态 ...
- JAVA面向对象-----instanceof 关键字
instanceof 关键字 1:快速演示instanceof Person p=new Person(); System.out.println( p instanceof Person); 2:i ...
- qsort函数应用大全
七种qsort排序方法 <本文中排序都是采用的从小到大排序> 一.对int类型数组排序 int num[100]; Sample: int cmp ( const void *a , c ...
- spring @Qualifier注解使用
@Autowired是根据类型进行自动装配的.如果当Spring上下文中存在多个UserDao类型的bean时,就会抛出BeanCreationException异常;如果Spring上下文中不存在U ...
- Zookeeper总概
zookeeper是一个开源的分布式协调服务.是典型的分布式数据一致性的解决方案. zookeeper可以保证以下分布式一致性的特性 1. 顺序性:同一客户端发起的事务请求,最终会严格的按照发出顺序应 ...
- 14 Fragment 注意点
API 过时问题 API 23过时 public void onAttach(Activity activity)替换为public void onAttach(Context context) 注意 ...
- Spark技术内幕:Executor分配详解
当用户应用new SparkContext后,集群就会为在Worker上分配executor,那么这个过程是什么呢?本文以Standalone的Cluster为例,详细的阐述这个过程.序列图如下: 1 ...
- C语言--static修饰变量
Static在C语言里面有两个作用,第一个是修饰变量,第二个是修饰函数. 1.Static修饰变量 按照作用范围的不同,变量分为局部变量和全局变量.如果用static修饰变量,不论这个变量是全局的还是 ...
- 定义范围中的备选方案生成、横向思维、创建WBS、工作包定义、WBS、确认范围过程和实施质量过程的关系、联合应用设计和质量功能展开QFD
- 最简单的基于FFmpeg的视频编码器-更新版(YUV编码为HEVC(H.265))
===================================================== 最简单的基于FFmpeg的视频编码器文章列表: 最简单的基于FFMPEG的视频编码器(YUV ...