Android开发技巧——使用Dialog实现仿QQ的ActionSheet菜单

时间:2021-09-22 10:03:40

最近看到有人用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

下一篇将写一下如何把它写成一个可复用的控件。

参考博客:http://blog.csdn.net/bbld_/article/details/39124097

Android开发技巧——使用Dialog实现仿QQ的ActionSheet菜单的更多相关文章

  1. Android开发技巧——实现可复用的ActionSheet菜单

    在上一篇<Android开发技巧--使用Dialog实现仿QQ的ActionSheet菜单>中,讲了这种菜单的实现过程,接下来将把它改成一个可复用的控件库. 本文原创,转载请注明出处: h ...

  2. Android仿QQ ios dialog&comma;仿QQ退出向上菜单

    Android仿QQ ios dialog,仿QQ退出向上菜单 EasyDialog两种模式 仿QQ退出向上菜单,自己定义向上菜单              github地址:https://gith ...

  3. Android开发技巧——自定义控件之使用style

    Android开发技巧--自定义控件之使用style 回顾 在上一篇<Android开发技巧--自定义控件之自定义属性>中,我讲到了如何定义属性以及在自定义控件中获取这些属性的值,也提到了 ...

  4. Android开发技巧——大图裁剪

    本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...

  5. Android开发技巧——使用PopupWindow实现弹出菜单

    在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...

  6. Android开发技巧——高亮的用户操作指南

    Android开发技巧--高亮的用户操作指南 2015-12-15补记: 发现使用PopupWindow进行遮罩层的显示,在华为P7上会有问题.具体表现为:画出来的高亮部分会偏下.原因为:通过view ...

  7. 50个android开发技巧

    50个android开发技巧 http://blog.csdn.net/column/details/androidhacks.html

  8. Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命

    Android特效专辑(六)--仿QQ聊天撒花特效,无形装逼,最为致命 我的关于特效的专辑已经在CSDN上申请了一个专栏--http://blog.csdn.net/column/details/li ...

  9. Android开发技巧——自定义控件之增加状态

    Android开发技巧--自定义控件之增加状态 题外话 这篇本该是上周四或上周五写的,无奈太久没写博客,前几段把我的兴头都用完了,就一拖再拖,直到今天.不想把这篇拖到下个月,所以还是先硬着头皮写了. ...

随机推荐

  1. linux下sendmail邮件系统安装操作记录

    电子邮件系统的组成:1)邮件用户代理(Mail User Agent , MUA),MUA是一个邮件系统的客户端程序,它提供了阅读,发送和接受电子邮件的用户接口. 最常用的 MUA 有: linux ...

  2. Linux搭建一个FTP服务器

    1.安装vsftp 2.配置vsftpd.conf, vim /etc/vsftpd.conf 下面说说里面比较重要的选项 1 anonymous_enable=NO #不允许匿名用户 2 3 loc ...

  3. Java学习笔记(3)

    “当你定义出一组类的父型时,你可以用子型的任何类来填补任何需要或期待父型的位置” “运用多态时,引用类型可以是实际对象类型的父类”Animal myDog = new Dog(); 三种方法可以防止某 ...

  4. C&num;中堆和栈的区别分析&lpar;有待更新总结&rpar;

    转载:http://blog.csdn.net/zevin/article/details/5721495 一.预备知识-程序的内存分配 一个由C/C++编译的程序占用的内存分为以下几个部分 1.栈区 ...

  5. JS对象与json字符串格式

    <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8&quo ...

  6. 2&period;配置Spring&plus;SpringMvc&plus;Mybatis&lpar;分库or读写分离&rpar;--Intellij IDAE 2016&period;3&period;5

    建立好maven多模块项目后,开始使用ssm传统的框架: 1.打开总工程下的pom.xml文件:添加如下代码: <!--全局的所有版本号定义--> <properties> & ...

  7. java中xxe漏洞修复方法

    java中禁止外部实体引用的设置方法不止一种,这样就导致有些开发者修复的时候采用的错误的方法 之所以写这篇文章是有原因的!最早是有朋友在群里发了如下一个pdf, 而当时已经是2019年1月末了,应该不 ...

  8. jquery的优良继承方法

    说一下好处:这个封装函数可以可以实现子类继承父类原型对象里面的所有方法和属性,但是也留了第二条路,去继承父类构造函数的里面的东西. 两个参数分别是子类的构造函数,后面是父类构造函数 $.inherit ...

  9. vue2项目结构搭建

    vue2项目结构初搭建与项目基本流程 一.开始项目结构搭建的重要性 决定项目是否能够健康成长 决定了项目是否利于多人协作开发 决定了项目是否利于后期维护 决定了项目是否性能良好 决定了代码是否重用率降 ...

  10. 白话&dollar;resource,&dollar;resource中的增删改查

    前言 $resource详解,在学习angular的过程中,我们已经知道,$http能十分便捷的为我们实现与后端的数据交互,格式如下: $http({method:'GET'},url:'XX').t ...