Android给所有Activity添加全局自定义菜单

来源:互联网 发布:独家算法必中六红 编辑:程序博客网 时间:2024/06/03 18:01
最近UI大大突发奇想,说是要给2.0搞个全局的菜单按钮,要巴拉巴拉.......

这个按钮的大致需求是在屏幕右上角新增一个Button,点击之后在屏幕顶部与右侧召唤一系列的功能按钮,

我把这部分代码抽出来随便写了一个demo,需求实现上图:


(在点击按钮2的时候如果已经处于登录界面就提示用户不需要再次跳转,下面会有说明)

需求拿到手,心想:还好之前title是用的一个自定义View,随便在右边加个按钮不就妥了!!结果还是太天真啊,首先如果要实现如上的效果,这个布局肯定不好写,其次这尼玛有得页面根本没有title啊,然后联想到之前做过一个全局的悬浮按钮,但是这货需要悬浮窗的权限,如果用户没有开启就特么显示不出来~~~

然后第一想法就是各种度- -,结果发现网上根本没有这样的需求,莫非是我找的姿势不对?这就有点尴尬了,那就只能自己造轮子了...

额额,废话说了一大堆,开始切入正题吧- -

要在所有Activity添加统一的全局menu,那么很容易想到的就是在BaseActivity中做操作,既然是要添加统一的视图,那么就要在BaseActivity的setContentView()方法中添加一个menu视图了。我的做法如下:

    @Override    public void setContentView(int layoutResID) {        super.setContentView(layoutResID);        ((ViewGroup) getWindow().getDecorView()).addView(menu);    }

这个getDecorView:这个方法是获取顶级视图

注意点1:addView添加入的视图应该是默认在左上角,和group里面原有的视图无关

注意点2:getDecorView既然是顶级视图,它包含整个屏幕,包括标题栏

注意点3:根据实际测试发现,标题栏的左上角位置的坐标才是坐标原点位置


此处引用了http://blog.csdn.net/rnZuoZuo/article/details/44959873的介绍,如果对这个方法有兴趣的童鞋可以自行查找资料哈,这里就不详细介绍了~~

我们这里menu是一个自定义View,继承了RelativeLayout,加载了一个很简单的RelativeLayout,该布局中将所有的按钮全部添加到屏幕右上角:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/menu_parent"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingRight="10dp"    android:paddingTop="45dp">    <Button        android:id="@+id/btn_one"        android:layout_width="50dp"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_marginRight="15dp"        android:layout_marginTop="17dp"        android:background="@null"        android:drawableTop="@mipmap/assist_1"        android:paddingBottom="5dp"        android:text="按钮1"        android:textSize="10sp" />    <Button        android:id="@+id/btn_two"        android:layout_width="50dp"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_marginRight="15dp"        android:layout_marginTop="17dp"        android:background="@null"        android:drawableTop="@mipmap/assist_2"        android:paddingBottom="5dp"        android:text="按钮2"        android:textSize="10sp" />    <Button        android:id="@+id/btn_three"        android:layout_width="50dp"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_marginRight="15dp"        android:layout_marginTop="17dp"        android:background="@null"        android:drawableTop="@mipmap/assist_2"        android:paddingBottom="5dp"        android:text="按钮2"        android:textSize="10sp" />    <Button        android:id="@+id/btn_four"        android:layout_width="50dp"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_marginRight="15dp"        android:layout_marginTop="17dp"        android:background="@null"        android:drawableTop="@mipmap/assist_3"        android:paddingBottom="5dp"        android:text="按钮3"        android:textSize="10sp" />    <Button        android:id="@+id/btn_five"        android:layout_width="50dp"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_marginRight="15dp"        android:layout_marginTop="17dp"        android:background="@null"        android:drawableTop="@mipmap/assist_4"        android:paddingBottom="5dp"        android:text="按钮4"        android:textSize="10sp" />    <Button        android:id="@+id/btn_six"        android:layout_width="50dp"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_marginRight="15dp"        android:layout_marginTop="17dp"        android:background="@null"        android:drawableTop="@mipmap/assist_5"        android:paddingBottom="5dp"        android:text="按钮5"        android:textSize="10sp" />    <Button        android:id="@+id/btn_seven"        android:layout_width="50dp"        android:layout_height="wrap_content"        android:layout_alignParentRight="true"        android:layout_marginRight="15dp"        android:layout_marginTop="17dp"        android:background="@null"        android:drawableTop="@mipmap/assist_6"        android:paddingBottom="5dp"        android:text="按钮7"        android:textSize="10sp" />    <Button        android:id="@+id/btn_main"        android:layout_width="80dp"        android:layout_height="80dp"        android:layout_alignParentRight="true"        android:layout_centerHorizontal="true"        android:background="@mipmap/xhdpi"        android:clickable="true" /></RelativeLayout>


当然你可以做成动态添加选项按钮,增加拓展性,因为我比较懒,这里就不做这一步了,就只分享我项目用到的,实际上在打算写博客的时候我考虑到这个需求完全可以做成
动态配置的,包括menu的形式与item的个数配置等等都能做成动态的,我后面会提到。


眼尖的童鞋肯定会发现我上面的menu有一个展开与收缩的动画效果,感觉这个menu关键的就是实现这个动画就行了,贴出show()与dismiss()动画实现代码:
show()

     int n = btns.size();        for (int i = 0; i < n; i++) {            float curTranslationX = btns.get(i).getTranslationX();            float curTranslationY = btns.get(i).getTranslationY();            PropertyValuesHolder valuesHolderX, valuesHolderY;            if (i <= 1) {//打横的item                valuesHolderX = PropertyValuesHolder.ofFloat("translationX", curTranslationX,                         ((i - n + 5) * w / 5));//X轴相对当前控件的位置                valuesHolderY = PropertyValuesHolder.ofFloat("translationY", curTranslationY, 0);            } else {//打竖的item                valuesHolderX = PropertyValuesHolder.ofFloat("translationX", curTranslationX, 0);                valuesHolderY = PropertyValuesHolder.ofFloat("translationY", curTranslationY,                         (-(i - n) * h / 7));//Y轴相对当前控件的位置            }            animatorX = ObjectAnimator.ofPropertyValuesHolder(btns.get(i), valuesHolderX, valuesHolderY);            animatorX.setDuration(250);            animatorX.setStartDelay(i * 50);            animatorX.setInterpolator(new AnticipateOvershootInterpolator());            animatorX.start();        }


dismiss()
int n = btns.size();        for (int i = 0; i < n; i++) {            //标记各按钮初始位置        float curTranslationX = btns.get(i).getTranslationX();        float curTranslationY = btns.get(i).getTranslationY();        PropertyValuesHolder valuesHolderX = PropertyValuesHolder.ofFloat("translationX",        curTranslationX, initX);        PropertyValuesHolder valuesHolderY = PropertyValuesHolder.ofFloat("translationY",        curTranslationY, initY);        animatorX = ObjectAnimator.ofPropertyValuesHolder(btns.get(i), valuesHolderX, valuesHolderY);        animatorX.setDuration(250);        animatorX.setStartDelay(i * 50);        animatorX.setInterpolator(new AnticipateOvershootInterpolator());        animatorX.start();        }


btns是所有子item的集合,animator是动画类,如果对属性动画不熟悉的童鞋看此处的代码可能有点懵逼,建议多看看相关资料,我这里不做多介绍,valuesHolder是相对于menu主button的位置,w,h分别是屏幕的宽高。

menu的展示形式就是在这两个方法中定义的,如果你需要其他的展示动画,完全可以按照你自己的逻辑来进行修改,你可以做一个旋转围绕圆心的菜单,也能做成90度的卫星菜单等等,一切按需求来...

最后看一下这个menu的住button的点击事件:

   btn_start.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                if (!isShowing) {                    if (mChangeListener != null) {                        mChangeListener.show();//添加菜单显示监听                    }                    show();                    rl_parent.setFocusable(true);                    rl_parent.setClickable(true);                    rl_parent.setBackgroundColor(getResources().getColor(R.color.menuShow));                    isShowing = !isShowing;                } else {                    if (mChangeListener != null) {                        mChangeListener.dismiss();//添加菜单隐藏监听                    }                    dismiss();                    rl_parent.setFocusable(false);                    rl_parent.setClickable(false);                    rl_parent.setBackgroundColor(getResources().getColor(R.color.menuDismiss));                    isShowing = !isShowing;                }            }        });


isShowing是全局的是否展示的状态,rl_parent是整个menu布局的跟布局,当menu展开的时候我们需要menu下一层的控件失去焦点,就类似于Dialog设置了setCancelable(false)的效果,所以就需要设置rl_parent在展开的时候获取焦点,在隐藏的时候取消焦点。实际上menu的展开与隐藏只是视觉上的效果,通过activity的setcontentview中addview的方式添加进去的menu是一直存在与activity的最上层的,这里只是在show()与dismiss()时分别给rl_parent设置了透明度不同的背景色而已


这里的mChangeListener是menu内部的接口,如果你的页面需要对menu的展示与隐藏进行监听,可以实现这个接口,就是一般的接口回调,这里没有什么好讲的了,包括menu的每个子item的点击事件也是通过这样的方法实现的,下面贴出两个接口:

   public interface MenuChangeListener {        public void show();        public void dismiss();    }    public interface MenuItemClickListener {        void menuOneItemClick();        void menuTwoItemClick();        void menuThreeItemClick();        void menuFourItemClick();        void menuFiveItemClick();        void menuSixItemClick();        void menuSevenItemClick();    }


实际上并不一定每个item的点击事件都要写一个回调,如果某一个item的事件是固定的,那么可以直接在menu类中实现该方法,而不用对外暴露。

因为我的项目中item的所有事件都是在baseActivity中实现的,但是如果说特定的item在具体的activity中需要响应不同的事件,

我的做法是在BaseActivity定义一个临时的Activity变量current_act,然后在每一个子Activity中都将当前的activity对象赋值给current_act,

demo中的BaseActivity代码如下

package com.aking.globalmenumaster.activity;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.ViewGroup;import android.widget.Toast;import com.aking.globalmenumaster.GlobalMenu;/** * Created by aking on 2017/4/6. */public class BaseActivity extends AppCompatActivity {    private GlobalMenu menu;    public Activity current_act;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        menu = new GlobalMenu(this);        menu.setMenuItemClickListener(new GlobalMenu.MenuItemClickListener() {            @Override            public void menuOneItemClick() {                startActivity(new Intent(BaseActivity.this, FullscreenActivity.class));            }            @Override            public void menuTwoItemClick() {                if (current_act.getClass() == LoginActivity.class) {                    Toast.makeText(BaseActivity.this, "我已经在登录页面了", Toast.LENGTH_LONG).show();                } else {                    startActivity(new Intent(BaseActivity.this, LoginActivity.class));                }            }            @Override            public void menuThreeItemClick() {                startActivity(new Intent(BaseActivity.this, Main2Activity.class));            }            @Override            public void menuFourItemClick() {                startActivity(new Intent(BaseActivity.this, Main22Activity.class));            }            @Override            public void menuFiveItemClick() {                startActivity(new Intent(BaseActivity.this, Main23Activity.class));            }            @Override            public void menuSixItemClick() {                startActivity(new Intent(BaseActivity.this, ScrollingActivity.class));            }            @Override            public void menuSevenItemClick() {                startActivity(new Intent(BaseActivity.this, MainActivity.class));            }        });    }    @Override    public void setContentView(int layoutResID) {        super.setContentView(layoutResID);        ((ViewGroup) getWindow().getDecorView()).addView(menu);    }    @Override    protected void onPause() {        super.onPause();        if (menu.isShowing()) {            menu.dismiss();        }    }}


在menuTwoItemClick()的事件中判断栈顶的activity是否是登录页面,如果是就不跳转,提示用户。如果说需要在某一个activity点击menu的item需要带一些参数到另外的页面,我的想法是也同样在BaseActivty中定义一个临时的变量用来存储这些参数,或者使用 SharedPreferences等等,因为我的项目中并没有这样的需求,所以我这里没有写,但我觉得很可能会有这样的需求....

需要说明的是new GlobalMenu(this);  这里初始化menu的时候传入的上下文对象一定要是Activity对象,因为我在menu类中需要通过这个上下文对象获取屏幕宽高,当然这里存在可优化的空间...

这里还存在一个已知的问题是,如果menu的主Button是一个有缝隙的矢量图的时候,在收缩的时候覆盖在下面的item会有一部分显示出来,影响界面,这里提供三种解决方案:

1>在dismiss()中为收缩动画注册一个监听器,当收缩动画完成之后,隐藏收起来的item,在show()中再显示item,代码实现如下:

animatorX.addListener(new Animator.AnimatorListener() {                @Override                public void onAnimationStart(Animator animation) {                }                @Override                public void onAnimationEnd(Animator animation) {                    *//*此处添加一个动画监听,只有在动画结束时才隐藏其他按钮                    * 但这样做会有一个bug,就是在用户点击菜单主按钮频率过快的时候会出现,menu处于show的状态,但是子item并没有展开或者展开的个数不对                    *                    * *//*                    if (btns.get(finalI).getVisibility() == VISIBLE) {                        btns.get(finalI).setVisibility(INVISIBLE);                    }                }                @Override                public void onAnimationCancel(Animator animation) {                }                @Override                public void onAnimationRepeat(Animator animation) {                }            });


这样做的局限性我已经在注释中提到了,在我的源码中,这部分的代码是注释掉的。

2>不用在dismiss()注册动画监听,只需要在show()的时候显示,dismiss()的时候隐藏,这样做的后果就是看不到收缩的动画;

3>第三种方案是我个人觉得最完美的,那就是叫UI大大切一张完美的图,能完全覆盖掉后面的item,这样就不用控制他们的显示与隐藏了,哈哈哈...

时间有限,就介绍这么多了,感觉这里面内容也并不多,相信看了此篇文章的童鞋也能轻而易举的定制出属于自己的全局menu啦,最后附上代码下载连接:

http://download.csdn.net/detail/u011907407/9807354

这里需要1个积分才能下载- -   我最近也是木有积分下载资源啦,走过路过的好心人打赏两个积分呗!!!如果也有像楼主这样的积分穷人,如果需要资源的话可以留下你的邮箱,我看到回复会第一时间发送附件给你的- -哭

3 0
原创粉丝点击