最近看到有人用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. [Centos]openvpn 服务端的安装(easy-rsa3)

    VPN在办公和fan墙领域有着广泛的应用,  我们小办公网最近可能会用到,先学学来着 vpn的server需要有公网ip,客户端可以在多种环境下使用 概念 PKI:Public Key Infrast ...

  2. ROS_Kinetic_x ROS栅格地图庫 Grid Map Library

    源自:https://github.com/ethz-asl/grid_map Grid Map Overview This is a C++ library with ROS interface t ...

  3. Solr 5.5.0 + tomcat 7.0.69 + zookeeper-3.4.6 Cloud部署

    Solr介绍:Solr是一个独立的企业级搜索应用服务器,Solr基于Lucene的全文搜索服务器,同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置.可扩展并对查询性能进行了 ...

  4. Android中常用开发工具类—持续更新...

    一.自定义ActionBar public class ActionBarTool { public static void setActionBarLayout(Activity act,Conte ...

  5. java中List接口的实现类 ArrayList,LinkedList,Vector 的区别 list实现类源码分析

    java面试中经常被问到list常用的类以及内部实现机制,平时开发也经常用到list集合类,因此做一个源码级别的分析和比较之间的差异. 首先看一下List接口的的继承关系: list接口继承Colle ...

  6. scala学习笔记1(表达式)

    <pre name="code" class="plain">//Scala中的 main 函数需要存在于 object 对象中,我们需要一个obj ...

  7. Dynamics CRM 2015 Online Update1 UI界面的更新变化

    听说出  Dynamics CRM 2015 Online  Update1了,立马跑去申请了个30天试用版简单的看了下,UI上的变化还是让人耳目一新的,也可能是被CRM2013的UI蹂躏太久了没 ...

  8. (NO.00004)iOS实现打砖块游戏(七):关卡类的实现

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 关卡游戏的精髓都集中在游戏的关卡里,其中包含了游戏的所有要素,至 ...

  9. UE4利用Save Game创建全局变量

    因为盲目的做了一个UE4的项目,没有用到UE4的无缝加载,我只能在一个个关卡中手动切换,然后每次的数据都会重置,这对于项目来说,造成了体验感的极度下降. 然而我查了一下怎样在UE4中创建全局变量,找到 ...

  10. XMPP即时通讯资料记录

    几天开始研究XMPP即时通讯的技术,来实现移动应用的计时聊天功能.记录下参考的博客地址,还挺详细的. http://blog.csdn.net/fhbystudy/article/details/16 ...