1. 程式人生 > >SlidingMenu原始碼分析及拓展:監聽拉出選單時的滾動事件,將主頁變暗

SlidingMenu原始碼分析及拓展:監聽拉出選單時的滾動事件,將主頁變暗

這些效果以前都寫過,但是之前沒有積累的習慣,導致每次開始一個新專案時又要重新去翻閱資料、api,挺浪費時間的,所以打算記錄一下,養成好習慣。

SlidingMenu與DrawerLayout:

SlidingMenu是一個非常火的側拉選單開源元件,基本上99%的專案都要用上他,Material Design之後出了一個DrawerLayout抽屜元件,也非常好用,兩者對比了一下,DrawerLayout只能實現抽屜的效果,選單拉出時覆蓋在activity上面從而擋住了activity左邊的部分試圖,而slidingmenu的效果是推拉,會隨著選單的拉出慢慢的將activity平移推出螢幕,主要看需求吧。

效果演示
這裡寫圖片描述

首先簡單回顧一下slidingmenu的基本用法吧,以程式碼為主,不過多解釋:

SlidingMenu menu = new SlidingMenu(this);  
        //設定菜單方向  
        menu.setMode(SlidingMenu.LEFT_RIGHT);  
        //設定滑動的範圍  
        menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);  
        //設定滑動的寬度  
        menu.setBehindOffset(200);  
        //設定陰影部分的圖片  
menu.setShadowDrawable(R.color.colorPrimaryDark); menu.setShadowWidth(10); //設定淡入淡出的效果 menu.setFadeEnabled(true); menu.setFadeDegree(0.4f); //附加到activity menu.attachToActivity(this,SlidingMenu.SLIDING_CONTENT); //設定左右滑動的選單 menu.setMenu(R.layout.slide_method_left);

這個不是今天的重點,側拉選單是初始化出來了,但是如果我要監聽選單的滑出的距離來實時的設定某些view的值,該怎麼實現呢?翻閱api及sliding原始碼,就這有這3個監聽,根本不能滿足我們的需要:

沒辦法只能去翻閱原始碼咯,原始碼太多,從哪開始入手呢,擒賊先禽王,誰滑動先找誰!

/**
     * Set the behind view (menu) content to the given View.
     *
     * @param view The desired content to display.
     */
    public void setMenu(View v) {
        mViewBehind.setContent(v);
    }

//找到初始化的地方
mViewBehind = new CustomViewBehind(context);
        mViewAbove = new CustomViewAbove(context);

mViewBehind就是我們的選單view,那mViewAbove是什麼呢?其實看名字就已經猜到了,behind代表在下面的選單,那麼above應該是上面的主頁面,抱著這個想法我們去證實一下自己的猜測:

還記得menu.attachToActivity(this,SlidingMenu.SLIDING_CONTENT)這個方法嗎?是讓activity依附與slidingmenu,他有兩種模式:

case SLIDING_WINDOW:
            mActionbarOverlay = false;
            ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
            ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
            // save ActionBar themes that have transparent assets
            decorChild.setBackgroundResource(background);
            decor.removeView(decorChild);
            decor.addView(this);
            setContent(decorChild);
            break;
        case SLIDING_CONTENT:
            mActionbarOverlay = actionbarOverlay;
            // take the above view out of
            ViewGroup contentParent = (ViewGroup)activity.findViewById(android.R.id.content);
            View content = contentParent.getChildAt(0);
            contentParent.removeView(content);
            contentParent.addView(this);
            setContent(content);
            // save people from having transparent backgrounds
            if (content.getBackground() == null)
                content.setBackgroundResource(background);
            break;

/**
     * Set the above view content to the given View.
     *
     * @param view The desired content to display.
     */
    public void setContent(View view) {
        mViewAbove.setContent(view);
        showContent();
    }

程式碼中我們可以明確的看到,不管是那種模式,都是找到當前activity的根佈局,然後先把裡面的view刪掉,然後把this也就是slidingmenu放入根佈局中,最後將我們原來的這個主頁view設定給了mViewAbove!!是不是也就是證實了我們的猜測,mViewAbove就是我們的主頁,我們再找找mViewAbove、mViewBehind的一些相關事件,看能發現些什麼。

一路find,終於有了發現:

mViewAbove.setOnPageChangeListener(new OnPageChangeListener() {
            public static final int POSITION_OPEN = 0;
            public static final int POSITION_CLOSE = 1;
            public static final int POSITION_SECONDARY_OPEN = 2;

            public void onPageScrolled(int position, float positionOffset,
                    int positionOffsetPixels) {
            }

            public void onPageSelected(int position) {
                if (position == POSITION_OPEN && mOpenListener != null) {
                    mOpenListener.onOpen();
                } else if (position == POSITION_CLOSE && mCloseListener != null) {
                    mCloseListener.onClose();
                } else if (position == POSITION_SECONDARY_OPEN && mSecondaryOpenListner != null ) {
                    mSecondaryOpenListner.onOpen();
                }
            }
        });

這個是不是很熟悉,有點想viewpage的滑動事件,就在這裡列印log試試效果吧!

public void onPageScrolled(int position, float positionOffset,
                    int positionOffsetPixels) {
                Log.i("TAG","positionOffset:"+positionOffset+" , positionOffsetPixels:"+positionOffsetPixels);
            }

列印結果:
這裡寫圖片描述

對了,就是它了!當主頁向左滑動,偏移量數值在慢慢的減少,找到了根源就好辦事咯,我給他加一個滾動監聽回撥,不就能解決我們的問題了嗎,這是我自己加的程式碼:

private OnScrollDistanceListener mOnScrollDistanceListener;

public void setOnScrollDistanceListener(OnScrollDistanceListener listener){
        this.mOnScrollDistanceListener = listener;
    }

    /**
     * 監聽SlidingMenu移動距離
     */
    public interface OnScrollDistanceListener{
        void  onPageScrolled( float positionOffset,
                                        int positionOffsetPixels);
    }

public void onPageScrolled(int position, float positionOffset,
                    int positionOffsetPixels) {
                if(mOnScrollDistanceListener != null){
                    mOnScrollDistanceListener.onPageScrolled(Math.abs(positionOffset),Math.abs(positionOffsetPixels));
                }
            }

有了移動的距離,是不是該給主頁view設定半透明黑色遮障了,大家一定第一個想到的就是getWindow().getAttributes();然後設定alpha,這樣搞了你會發現,什麼鬼!我明明只想將滑出時的主頁變成灰色,為什麼我的選單也變灰了。要知道getWindow()指的是整個activity介面,在剛剛的原始碼中我們已經看到slidingmenu已經將選單和主頁都放在了activity中,那麼當然是全部都變灰了!!!

有人在想了,那我們來設定主頁的alpha行嗎?行,這會變色的範圍定位準了,但是alpha是從不透明到透明的一個過度,不透明的時候介面變成了白色,而不是我們需要的灰色,這怎麼搞?其實很簡單,用最原始的辦法,給主頁佈局最上層加一個view,大小與整個主頁相等,然後通過設定它的背景色和透明度來實現,雖然聽上去low一點,但是暫時真的沒有找到更好的方法來實現:

<!--遮障view-->
<View
        android:id="@+id/v_filter"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#494949"
        android:visibility="gone"
        />
 //滑動距離監聽
        slidingMenu.setOnScrollDistanceListener(new SlidingMenu.OnScrollDistanceListener() {
            @Override
            public void onPageScrolled(float positionOffset, int positionOffsetPixels) {
                setActivityFilter(positionOffset);
            }
        });


    /**
     * 設定activity濾鏡效果
     */
    private void setActivityFilter(float positionOffset) {
        float alpha = positionOffset * 0.7f;
        iv_filter.setAlpha(alpha);
        if (alpha <= 0) {
            if (iv_filter.isShown())
                iv_filter.setVisibility(View.GONE);
        } else {
            if (!iv_filter.isShown())
                iv_filter.setVisibility(View.VISIBLE);
        }
    }