最近看到有人用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开发】【布局】 仿QQ的UI

    Demo地址

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

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

  3. Android仿QQ ios dialog,仿QQ退出向上菜单

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

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

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

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

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

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

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

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

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

  8. 50个android开发技巧

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

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

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

随机推荐

  1. SceneKit做一个旋转的地球效果

    SceneKit可以用寥寥几行帮你完成很多OpenGL复杂的3D设置代码,下面本猫就带大家完成一个旋转的3D地球的场景. 首先需要地球表面图片,将其导入到Xcode中: 我们用SceneKit内置的几 ...

  2. Apache Beam—透视Google统一流式计算的野心

    Google是最早实践大数据的公司,目前大数据繁荣的生态很大一部分都要归功于Google最早的几篇论文,这几篇论文早就了以Hadoop为开端的整个开源大数据生态,但是很可惜的是Google内部的这些系 ...

  3. Ext JS 6开发实例(一)

    很久没写文章了,主要原因和大家差不多,都要为生活奔忙,搞了两个小项目.这两个小项目很凑巧,都可以使用Ext JS来开发,这正是练习使用Ext JS 6的好机会,自然不会错过. 很多读者可能会问,为什么 ...

  4. 从二进制数据流中构造GDAL可以读取的图像数据

    在很多时候,我们的图像数据往往都不是文件方式存储在磁盘上,而是可能从网络或者数据库中获取的是二进制的图像数据流.最简单的方式和最容易想到的方式就是将这个文件流保存到磁盘上形成一个文件,然后再使用GDA ...

  5. Android Service详解

    service作为四大组件值得我们的更多的关注 在Android中,Activity主要负责前台页面的展示,Service主要负责需要长期运行的任务.例如,一个从service播放音乐的音乐播放器,应 ...

  6. python脚本程序,传入参数*要用单引号'*'

    *号作为python脚本的传入参数时,必须用单引号'',才能正确传入.如python test.py 2014 '*' age python test.py 2014 * age是错误的. 比如 te ...

  7. mysql进阶(二十四)防御SQL注入的方法总结

    防御SQL注入的方法总结 这篇文章主要讲解了防御SQL注入的方法,介绍了什么是注入,注入的原因是什么,以及如何防御,需要的朋友可以参考下. SQL注入是一类危害极大的攻击形式.虽然危害很大,但是防御却 ...

  8. 关于React Native 火热的话题,从入门到原理

    本文授权转载,作者:bestswifter(简书) React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原 ...

  9. Java 8时间和日期API 20例

    本文由 ImportNew - Sandy 翻译自 javarevisited.欢迎加入翻译小组.转载请见文末要求. 伴随lambda表达式.streams以及一系列小优化,Java 8 推出了全新的 ...

  10. Linux下触摸屏驱动程序分析

    [摘要: 本文以linux3.5--Exynos4412仄台,剖析触摸屏驱动焦点内容.Linux下触摸屏驱动(以ft5x06_ts为例)须要懂得以下学问: 1. I2C协定 2. Exynos4412 ...