Android給所有Activity添加全局自定義菜單

最近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個積分才能下載- -   我最近也是木有積分下載資源啦,走過路過的好心人打賞兩個積分唄!!!如果也有像樓主這樣的積分窮人,如果需要資源的話可以留下你的郵箱,我看到回覆會第一時間發送附件給你的- -哭

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章