Android 實現形態各異的雙向側滑菜單 自定義控件來襲

jopen 8年前發布 | 17K 次閱讀 Android開發 移動開發

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/39670935,本文出自:【張鴻洋的博客】

1、概述

關于自定義控件側滑已經寫了兩篇了~~今天決定把之前的單向改成雙向,當然了,單純的改動之前的代碼也沒意思,今天不僅會把之前的單向改為雙向,還會多添加一種側滑效果,給大家帶來若干種形態各異的雙向側滑菜單,不過請放心,代碼會很簡單~~然后根據這若干種,只要你喜歡,相信你可以打造任何絢(bian)麗(tai)效果的雙向側滑菜單~~

首先回顧一下,之前寫過的各種側滑菜單,為了不占據篇幅,就不貼圖片了:

1、最普通的側滑效果,請參考:Android 自定義控件打造史上最簡單的側滑菜單

2、仿QQ5.0側滑效果,請參考:Android 高仿 QQ5.0 側滑菜單效果 自定義控件來襲

3、菜單在內容之后的側滑效果,請參考:Android 高仿 QQ5.0 側滑菜單效果 自定義控件來襲

2、目標效果


1、最普通的雙向側滑

是不是很模糊,嗯,沒辦法,電腦顯卡弱。。。。

2、抽屜式雙向側滑

3、菜單在內容之下的雙向側滑

湊合看下,文章最后會提供源碼下載,大家可以安裝體驗一下~

所有的代碼的內容區域都是一個ListView,兩側菜單都包含按鈕,基本的沖突都檢測過~~~當然如果有bug在所難免,請直接留言;如果你解決了某些未知bug,希望你也可以留言,或許可以幫助到其他人~~

下面就開始我們的代碼了。

 

3、代碼是最好的老師

1、布局文件

既然是雙向菜單,那么我們的布局文件是這樣的:

<com.zhy.view.BinarySlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.zhy_bin_slidingmenu02"
    android:id="@+id/id_menu"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:scrollbars="none"
    zhy:rightPadding="100dp" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >

        <include layout="@layout/layout_menu" />

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="@drawable/eee"
            android:gravity="center"
            android:orientation="horizontal" >

            <ListView
                android:id="@android:id/list"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >
            </ListView>
        </LinearLayout>

        <include layout="@layout/layout_menu2" />
    </LinearLayout>

</com.zhy.view.BinarySlidingMenu>

 

最外層是我們的自定義的BinarySlidingMenu,內部一個水平方向的LinearLayout,然后是左邊的菜單,內容區域,右邊的菜單布局~~

關鍵就是我們的BinarySlidingMenu

2、BinarySlidingMenu的構造方法

/**
     * 屏幕寬度
     */
    private int mScreenWidth;

    /**
     * dp 菜單距離屏幕的右邊距
     */
    private int mMenuRightPadding;

    public BinarySlidingMenu(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        mScreenWidth = ScreenUtils.getScreenWidth(context);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.BinarySlidingMenu, defStyle, 0);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++)
        {
            int attr = a.getIndex(i);
            switch (attr)
            {
            case R.styleable.BinarySlidingMenu_rightPadding:
                // 默認50
                mMenuRightPadding = a.getDimensionPixelSize(attr,
                        (int) TypedValue.applyDimension(
                                TypedValue.COMPLEX_UNIT_DIP, 50f,
                                getResources().getDisplayMetrics()));// 默認為10DP
                break;
            }
        }
        a.recycle();
    }
我們在構造方法中,獲取我們自定義的一個屬性rightPadding,然后賦值給我們的成員變量mMenuRightPadding;關于如何自定義屬性參考側滑菜單的第一篇博文,這里就不贅述了。

3、onMeasure

onMeasure中肯定是對側滑菜單的寬度、高度等進行設置:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        /**
         * 顯示的設置一個寬度
         */
        if (!once)
        {

            mWrapper = (LinearLayout) getChildAt(0);
            mLeftMenu = (ViewGroup) mWrapper.getChildAt(0);
            mContent = (ViewGroup) mWrapper.getChildAt(1);
            mRightMenu = (ViewGroup) mWrapper.getChildAt(2);

            mMenuWidth = mScreenWidth - mMenuRightPadding;
            mHalfMenuWidth = mMenuWidth / 2;
            mLeftMenu.getLayoutParams().width = mMenuWidth;
            mContent.getLayoutParams().width = mScreenWidth;
            mRightMenu.getLayoutParams().width = mMenuWidth;

        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

可以看到我們分別給左側、右側的菜單設置了寬度(mScreenWidth - mMenuRightPadding);

寬度設置完成以后,肯定就是定位了,把左邊的菜單弄到左邊去,右邊的菜單放置到右邊,中間依然是我們的內容區域,那么請看onLayout方法~

4、onLayout

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);
        if (changed)
        {
            // 將菜單隱藏
            this.scrollTo(mMenuWidth, 0);
            once = true;
        }

    }

哈,出奇的簡單,因為我們的內部是個橫向的線性布局,所以直接把左側滑出去即可~~定位也完成了,那么此時應該到了我們的處理觸摸了。

5、onTouchEvent

@Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        int action = ev.getAction();
        switch (action)
        {
        // Up時,進行判斷,如果顯示區域大于菜單寬度一半則完全顯示,否則隱藏
        case MotionEvent.ACTION_UP:
            int scrollX = getScrollX();
            // 如果是操作左側菜單
            if (isOperateLeft)
            {
                // 如果影藏的區域大于菜單一半,則影藏菜單
                if (scrollX > mHalfMenuWidth)
                {
                    this.smoothScrollTo(mMenuWidth, 0);
                    // 如果當前左側菜單是開啟狀態,且mOnMenuOpenListener不為空,則回調關閉菜單
                    if (isLeftMenuOpen && mOnMenuOpenListener != null)
                    {
                        // 第一個參數true:打開菜單,false:關閉菜單;第二個參數 0 代表左側;1代表右側
                        mOnMenuOpenListener.onMenuOpen(false, 0);
                    }
                    isLeftMenuOpen = false;

                } else
                // 關閉左側菜單
                {
                    this.smoothScrollTo(0, 0);
                    // 如果當前左側菜單是關閉狀態,且mOnMenuOpenListener不為空,則回調打開菜單
                    if (!isLeftMenuOpen && mOnMenuOpenListener != null)
                    {
                        mOnMenuOpenListener.onMenuOpen(true, 0);
                    }
                    isLeftMenuOpen = true;
                }
            }

            // 操作右側
            if (isOperateRight)
            {
                // 打開右側側滑菜單
                if (scrollX > mHalfMenuWidth + mMenuWidth)
                {
                    this.smoothScrollTo(mMenuWidth + mMenuWidth, 0);
                } else
                // 關閉右側側滑菜單
                {
                    this.smoothScrollTo(mMenuWidth, 0);
                }
            }

            return true;
        }
        return super.onTouchEvent(ev);
    }


依然是簡單~~~我們只需要關注ACTION_UP,然后得到手指抬起后的scrollX,然后我們通過一個布爾值,判斷用戶現在操作是針對左側菜單,還是右側菜單?

如果是操作左側,那么判斷scorllX是否超過了菜單寬度的一半,然后做相應的操作。

如果是操作右側,那么判斷scrollX與 mHalfMenuWidth + mMenuWidth ( 注意下,右側菜單完全影藏的時候,scrollX 就等于 mMenuWidth ),然后做對應的操作。

我們還給左側的菜單加上了一個回調:

if (isLeftMenuOpen && mOnMenuOpenListener != null)
{
//第一個參數true:打開菜單,false:關閉菜單;第二個參數 0 代表左側;1代表右側
mOnMenuOpenListener.onMenuOpen(false, 0);
}

掃一眼我們的回調接口:

/**
     * 回調的接口
     * @author zhy
     *
     */
    public interface OnMenuOpenListener
    {
        /**
         * 
         * @param isOpen true打開菜單,false關閉菜單
         * @param flag 0 左側, 1右側
         */
        void onMenuOpen(boolean isOpen, int flag);
    }

右側菜單我沒有添加回調,大家按照左側的形式自己添加下就ok ; 

好了,接下來,看下我們判斷用戶操作是左側還是右側的代碼寫在哪。

6、onScrollChanged

@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt)
    {
        super.onScrollChanged(l, t, oldl, oldt);

        if (l > mMenuWidth)
        {
            isOperateRight = true;
            isOperateLeft = false;
        } else
        {
            isOperateRight = false;
            isOperateLeft = true;
        }
    }

如果看過前兩篇,對這個方法應該很眼熟了吧。我們直接通過 l 和 菜單寬度進行比較, 如果大于菜單寬度,那么肯定是想操作右側菜單,否則那么就是想操作左側菜單;

到此,我們的雙向側滑菜單已經大功告成了,至于你信不信,反正我有效果圖。看效果圖前,貼一下MainActivity的代碼:

7、MainActivity

package com.zhy.zhy_bin_slidingmenu02;

import java.util.ArrayList;
import java.util.List;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Toast;

import com.zhy.view.BinarySlidingMenu;
import com.zhy.view.BinarySlidingMenu.OnMenuOpenListener;

public class MainActivity extends ListActivity
{
    private BinarySlidingMenu mMenu;
    private List<String> mDatas = new ArrayList<String>();

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        mMenu = (BinarySlidingMenu) findViewById(R.id.id_menu);
        mMenu.setOnMenuOpenListener(new OnMenuOpenListener()
        {
            @Override
            public void onMenuOpen(boolean isOpen, int flag)
            {
                if (isOpen)
                {

                    Toast.makeText(getApplicationContext(),
                            flag == 0 ? "LeftMenu Open" : "RightMenu Open",
                            Toast.LENGTH_SHORT).show();
                } else
                {

                    Toast.makeText(getApplicationContext(),
                            flag == 0 ? "LeftMenu Close" : "RightMenu Close",
                            Toast.LENGTH_SHORT).show();
                }

            }
        });
        // 初始化數據
        for (int i = 'A'; i <= 'Z'; i++)
        {
            mDatas.add((char) i + "");
        }
        // 設置適配器
        setListAdapter(new ArrayAdapter<String>(this, R.layout.item, mDatas));
    }
}

沒撒好說的,為了方便直接繼承了ListActivity,然后設置了一下回調,布局文件一定要有ListView,為了測試我們是否有沖突~~不過有咱們也不怕~

效果圖:

當然了,最簡單的雙向側滑怎么能滿足大家的好(Zhao)奇(Nue)心呢,所以我們準備玩點神奇的花樣~~

4、打造抽屜式雙向側滑

我們在onScrollChanged添加兩行代碼~~為mContent設置一個屬性動畫

@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt)
    {
        super.onScrollChanged(l, t, oldl, oldt);

        if (l > mMenuWidth)
        {
            isOperateRight = true;
            isOperateLeft = false;
        } else
        {
            isOperateRight = false;
            isOperateLeft = true;
        }

        float scale = l * 1.0f / mMenuWidth;
        ViewHelper.setTranslationX(mContent, mMenuWidth * (scale - 1));

    }
簡單分析一下哈:

1、scale,在滑動左側菜單時:值為1.0~0.0;mMenuWidth * (scale - 1) 的值就是從 0.0~ -mMenuWidth(注意:負的) ; 那么mContent的向偏移量,就是0到mMenuWidth ;也就是說,整個滑動的過程,我們強制讓內容區域固定了。

2、scale,在滑動右側菜單時:值為:1.0~2.0;mMenuWidth * (scale - 1) 的值就是從 0.0~ mMenuWidth(注意:正數) ;那么mContent的向偏移量,就是0到mMenuWidth ;也就是說,整個滑動的過程,我們強制讓內容區域固定了。

 

好了,內容固定了,那么我們此刻的兩邊菜單應該是在內容之上顯示出來~~這不就是我們的抽屜效果么~

嗯,這次木有效果圖了,因為測試結果發現,左側的菜單會被內容區域遮蓋住,看不到;右側菜單符合預期效果;因為,左側菜單滑動出來以后,被內容區域遮蓋住了,這個也很容易理解,畢竟我們的布局,內容在左側菜單后面,肯定會擋住它的。那么,怎么辦呢?

起初,我準備使用bringToFont方法,在拖動的時候,讓菜單在上面~~~不過呢,問題大大的,有興趣可以試試~~

于是乎,我換了個方法,我將BinarySlidingMenu內部的Linearlayout進行了自定義,現在布局文件是這樣的:

<com.zhy.view.BinarySlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:zhy="http://schemas.android.com/apk/res/com.zhy.zhy_bin_slidingmenu03"
    android:id="@+id/id_menu"
    android:layout_width="wrap_content"
    android:layout_height="fill_parent"
    android:scrollbars="none"
    zhy:rightPadding="100dp" >

    <com.zhy.view.MyLinearLayout
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >

        <include layout="@layout/layout_menu" />

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="@drawable/eee"
            android:gravity="center"
            android:orientation="horizontal" >

            <ListView
                android:id="@android:id/list"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" >
            </ListView>
        </LinearLayout>

        <include layout="@layout/layout_menu2" />
    </com.zhy.view.MyLinearLayout>

</com.zhy.view.BinarySlidingMenu>

MyLinearlayout的代碼:

package com.zhy.view;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;

public class MyLinearLayout extends LinearLayout
{

    public MyLinearLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
//      Log.e("TAG", "MyLinearLayout");
        setChildrenDrawingOrderEnabled(true);
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i)
    {
//      Log.e("tag", "getChildDrawingOrder" + i + " , " + childCount);

        if (i == 0)
            return 1;
        if (i == 2)
            return 2;
        if (i == 1)
            return 0;
        return super.getChildDrawingOrder(childCount, i);

    }

}

在構造方法設置setChildrenDrawingOrderEnabled(true);然后getChildDrawingOrder復寫一下繪制子View的順序,讓內容(i==0)始終是最先繪制。

現在再運行,效果圖:

效果是不是很贊,請允許我把圖挪過來了~~~

現在,還有最后一個效果,如果讓,菜單在內容之下呢?

5、打造菜單在內容之下的雙向側滑

不用說,大家都能想到,無非就是在onScrollChanged改改屬性動畫唄,說得對!

1、改寫onScrollChanged方法

@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt)
    {
        super.onScrollChanged(l, t, oldl, oldt);

        if (l > mMenuWidth)
        {
            // 1.0 ~2.0 1.0~0.0
            // (2-scale)
            float scale = l * 1.0f / mMenuWidth;
            isOperateRight = true;
            isOperateLeft = false;
            ViewHelper.setTranslationX(mRightMenu, -mMenuWidth * (2 - scale));

        } else
        {
            float scale = l * 1.0f / mMenuWidth;
            isOperateRight = false;
            isOperateLeft = true;
            ViewHelper.setTranslationX(mLeftMenu, mMenuWidth * scale);

        }
    }

也就是拉的時候,盡量讓菜單保證在內容之下~~~代碼自己琢磨下

2、改寫MyLinearLayout

當然了,僅僅這些是不夠的,既然我們的樣式變化了,那么改寫View的繪制順序肯定也是必須的。

看下我們的MyLinearLayout

package com.zhy.view;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;

public class MyLinearLayout extends LinearLayout
{

    public MyLinearLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        Log.e("TAG", "MyLinearLayout");
        setChildrenDrawingOrderEnabled(true);
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i)
    {

        if (i == 0)
            return 0;
        if (i == 2)
            return 1;
        if (i == 1)
            return 2;
        return super.getChildDrawingOrder(childCount, i);

    }

}
效果圖:

 

到此,我們的形態各異的雙向側滑就結束了~~~

從最普通的雙向,到抽屜式,再到我們的菜單在內容之下的側滑都已經搞定;希望大家通過這三個側滑,可以舉一反三,打造各種變態的側滑效果~~~~

最后我把3個側滑的源碼都會共享出來,大家自行下載:

 

Android普通雙向側滑

 

Android抽屜式雙向側滑

 

Android菜單在內容之下的雙向側滑

 

ps:本人測試手機,小米2s,盡量真機進行測試。

 

----------------------------------------------------------------------------------------------------------

博主部分視頻已經上線,如果你不喜歡枯燥的文本,請猛戳(初錄,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0側滑


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

來自: http://blog.csdn.net//lmj623565791/article/details/39670935

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!