1. 程式人生 > >Android開發技巧——使用Dialog實現仿QQ的ActionSheet選單

Android開發技巧——使用Dialog實現仿QQ的ActionSheet選單

最近看到有人用Dialog來實現QQ的仿ActionSheet的自定義選單,對於自己沒實現過的一些控制元件,看著也想實現一下。於是動手了一下,發現也不難,和大家分享一下。

在這裡我也是用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();
                }
            });

這篇部落格由於主要是寫實現的過程,所以有點長。實際上程式碼並不複雜,ActionSheet的全部程式碼加註釋才170行。
專案地址(包括執行效果的錄製視訊):http://zdz.la/2pz0Ys

下一篇將寫一下如何把它寫成一個可複用的控制元件。

相關推薦

Android開發技巧——使用Dialog實現仿QQ的ActionSheet選單

最近看到有人用Dialog來實現QQ的仿ActionSheet的自定義選單,對於自己沒實現過的一些控制元件,看著也想實現一下。於是動手了一下,發現也不難,和大家分享一下。 在這裡我也是用Dialog來實現,程式碼不多,這裡說一下實現的過程。 選單的佈

Android開發技巧——實現可複用的ActionSheet選單

本文原創,轉載請註明出處: 對於要實現的可複用的控制元件庫,我需要它具備以下兩點: 可新增遠端依賴(不考慮Eclipse中的使用) 可靈活配置 分離庫的實現程式碼 對於第一點,需要做的就是在Android Studio中新建一個libra

Android開發技巧——定製仿微信圖片裁剪控制元件

拍照——裁剪,或者是選擇圖片——裁剪,是我們設定頭像或上傳圖片時經常需要的一組操作。上篇講了Camera的使用,這篇講一下我對圖片裁剪的實現。 背景 下面的需求都來自產品。 裁剪圖片要像微信那樣,拖動和放大的是圖片,裁剪框不動。 裁剪框外的內容要有半透

Android開發技巧——五分鐘實現二維碼識別

/** * This activity opens the camera and does the actual scanning on a background * thread. It draws a viewfinder to help the user place the barcode cor

Android RadioGroup+ViewPager+ActionBar實現仿微信6.0介面(底部滑動選單欄+導航欄)

轉載請註明原文地址:http://blog.csdn.net/anyfive/article/details/41296341 本文主要使用RadioGroup+ViewPager來實現滑動介面,使用ActionBar來實現頂部選單欄。先上圖(使用GifCam錄製)。

Android開發分享功能實現步驟

集成 sha har sse 分享 功能 秘鑰 步驟 過去 參考mob官網(http://www.mob.com/) 分享實現步驟:1.mob官網賬號註冊登錄2.進入後臺,進入ShareSDK,添加應用,生成秘鑰3.參照mob官網集成文檔,下載SDK,進入ShareSDK

Android開發圓形ImageView實現

radi appcompat con code roi contex draw ttr extends 1、自定義屬性,在value文件夾下新建attrs文件,聲明如下屬性 <declare-styleable name="CircleImageView">

Android開發技巧之:QQ第三方登入(二)

接 android QQ第三方登入(一)  獲取登入使用者名稱資訊,這邊先抱怨一下,官方API有點坑 Constants原始碼類下就是找不到GRAPH_SIMPLE_USER_INFO這個屬性!無語! 根據官方提供返回的的JSONObject資訊解析:

Android開發技巧之:QQ第三方登入(一)

使用的是Android_SDK_V2.9.1,建議使用最新版; 官方下載:SDK下載  Android studio 中新增到 然後在點選build.gradle檔案新增 配置AndroidManifest 在應用的Andr

Android開發技巧不同狀態的Button

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android開發技巧(一):程式碼日誌跟蹤後臺顯示Log.i();

1.Activity中使用Log.i(); private static final String TAG = “GoodsSourceActivity”;//定義常量標記 Log.i(TAG,”—–TEST-BUNDLE——-“+uuid);//日誌列印,i級別 2.Fragme

Android開發技巧之xml tools屬性詳解

我們知道,在 Android 開發過程中,我們的資料常常來自於服務端,只有在執行時才能獲得資料展示,因此在佈局 XML 的編寫過程中,由於缺少資料,我們很難直接看到填充資料後的佈局效果,那這個時候你一般是怎麼做的呢? 經常看到一些小夥伴的做法是在佈局檔案中臨時

Android開發技巧——大圖裁剪

本篇內容是接上篇《Android開發技巧——定製仿微信圖片裁剪控制元件》 的,先簡單介紹對上篇所封裝的裁剪控制元件的使用,再詳細說明如何使用它進行大圖裁剪,包括對旋轉圖片的裁剪。 裁剪控制元件的簡單使用 XML程式碼 使用如普通控制元件一樣,首先在

Android開發技巧——去掉TextView中autolink的下劃線

我們知道,在佈局檔案中設定textview的autolink及其型別,這時textivew上會顯示link的顏色,並且文字下面會有一條下劃線,表示可以點選。而在我們在點選textview時,應用將根據我們所設定的型別跳轉到對應的介面。但是有時,我們因介面需求,需要去掉介面上

Android 如何讓Dialog實現背景透明

View inflate = LayoutInflater.from(context).inflate(R.layout.qrcode, null); ImageView qrcode = (ImageView) inflate.findViewById(R.id.iv_qr

android 自定義ViewGroup實現仿淘寶的商品詳情頁

最近公司在新版本上有一個需要, 要在首頁新增一個滑動效果, 具體就是仿照X寶的商品詳情頁, 拉到頁面底部時有一個粘滯效果,  如下圖 X東的商品詳情頁,如果使用者繼續向上拉的話就進入商品圖文描述介面: 剛開始是想拿來主義,直接從網上找個現成的demo來用, 但是網上無一

Android ViewPager和Fragment實現仿微信導航介面及滑動效果

1 先看看實現的效果: ps:上面每一幀Fragment中,包含是來自網路的圖片; 實現ViewPager+Fragment的頁面滑動和底部導航原理 主佈局檔案如下: <?xml version="1.0" encoding="utf-8"?> <L

Android開發之RecyclerView實現流式佈局

RecyclerView是什麼? RecycleView的出現, 替代了ListView, 沒了OnitemClickListener,; LayoutManager負責計算佈局; Adapter 負責適配,還增加了ViewHolder;RecycleView

Android開發之AIDL實現遠端服務程序通訊(IPC)

首先什麼是AIDL呢,它是Android系統中的一種介面定義語言,用於約束兩個程序間的通訊規則,供編譯器生成程式碼。 實現Android裝置上的兩個程序間通訊(IPC),程序之間的通訊資訊首先會被轉換成AIDL協議資訊,然後傳送給對方;對方接收到AIDL協議資

android開發——用GridView實現省市縣三級聯動

前段時間寫了一個DEMO,裡面用到省市縣三級聯動的功能,稍微整理了一下。以下程式碼中大部分資料來源於網上的下拉列表的省市縣三級聯動的例子,這裡我作出了改動,用的是GridView來顯示省市縣,程式碼有點繁瑣,但是顯示效果還是不錯的。 顯示效果如下圖所示:未點選按