Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ym——Android仿QQ5.0側滑菜單ResideMenu源碼分析

ym——Android仿QQ5.0側滑菜單ResideMenu源碼分析

編輯:關於Android編程

原創博客地址:點擊傳送

AndroidResideMenu

 

\

先看看如何使用:

把項目源碼下載下來導入工程,可以看到

\

ResideMenu為引用工程,再看看如何使用這個引用工程來構建出ResideMenu,

1.先new一個ResideMenu對象

 

resideMenu = new ResideMenu(this);
2.設置它的背景圖片

 

 

resideMenu.setBackground(R.drawable.menu_background);
3.綁定當前Activity

 

 

resideMenu.attachToActivity(this);
4.設置監聽

 

 

resideMenu.setMenuListener(menuListener);
可以監聽菜單打開和關閉狀態

 

 

    private ResideMenu.OnMenuListener menuListener = new ResideMenu.OnMenuListener() {
        @Override
        public void openMenu() {
            Toast.makeText(mContext, Menu is opened!, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void closeMenu() {
            Toast.makeText(mContext, Menu is closed!, Toast.LENGTH_SHORT).show();
        }
    };
5.設置內容縮放比例(0.1~1f)

 

 

//valid scale factor is between 0.0f and 1.0f. leftmenu'width is 150dip. 
        resideMenu.setScaleValue(0.6f);
6.創建子菜單

 

 

 // create menu items;
        itemHome     = new ResideMenuItem(this, R.drawable.icon_home,     Home);
        itemProfile  = new ResideMenuItem(this, R.drawable.icon_profile,  Profile);
        itemCalendar = new ResideMenuItem(this, R.drawable.icon_calendar, Calendar);
        itemSettings = new ResideMenuItem(this, R.drawable.icon_settings, Settings);
7.設置點擊事件及將剛創建的子菜單添加到側換菜單中(可以看到它是通過常量來控制子菜單的添加位置)

 

 

itemHome.setOnClickListener(this);
        itemProfile.setOnClickListener(this);
        itemCalendar.setOnClickListener(this);
        itemSettings.setOnClickListener(this);

        resideMenu.addMenuItem(itemHome, ResideMenu.DIRECTION_LEFT);
        resideMenu.addMenuItem(itemProfile, ResideMenu.DIRECTION_LEFT);
        resideMenu.addMenuItem(itemCalendar, ResideMenu.DIRECTION_RIGHT);
        resideMenu.addMenuItem(itemSettings, ResideMenu.DIRECTION_RIGHT);
8.設置title按鈕的點擊事件,設置左右菜單的開關

 

 

// You can disable a direction by setting ->
        // resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);

        findViewById(R.id.title_bar_left_menu).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                resideMenu.openMenu(ResideMenu.DIRECTION_LEFT);
            }
        });
        findViewById(R.id.title_bar_right_menu).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                resideMenu.openMenu(ResideMenu.DIRECTION_RIGHT);
            }
        });
9.還重寫了dispatchTouchEvent

 

 

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return resideMenu.dispatchTouchEvent(ev);
    }
10.菜單關閉方法

 

 

resideMenu.closeMenu();

 

11.屏蔽菜單方法

 

// You can disable a direction by setting ->
        // resideMenu.setSwipeDirectionDisable(ResideMenu.DIRECTION_RIGHT);

 

使用方法已經說完了,接下來,看看它的源碼,先看看源碼的項目結構。

\

 

很多人初學者都曾糾結,看源碼,如何從何看起,我個人建議從上面使用的順序看起,並且在看的時候要帶個問題去看去思考,這樣更容易理解。

上面的第一步是,創建ResideMenu對象,我們就看看ResideMenu的構造。

 

public ResideMenu(Context context) {
        super(context);
        initViews(context);
    }
從上面代碼,看到構造裡面就一個初始化view,思考問題:如何初始化view及初始化了什麼view。

 

 

private void initViews(Context context){
        LayoutInflater inflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.residemenu, this);
        scrollViewLeftMenu = (ScrollView) findViewById(R.id.sv_left_menu);
        scrollViewRightMenu = (ScrollView) findViewById(R.id.sv_right_menu);
        imageViewShadow = (ImageView) findViewById(R.id.iv_shadow);
        layoutLeftMenu = (LinearLayout) findViewById(R.id.layout_left_menu);
        layoutRightMenu = (LinearLayout) findViewById(R.id.layout_right_menu);
        imageViewBackground = (ImageView) findViewById(R.id.iv_background);
    }
原理分析:從上面的代碼可以看到,加載了一個residemenu的布局,先看布局

 

 



<framelayout android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
    

    

    
        

        
    

    
        

        
    

</framelayout>
布局顯示效果

 

\
從布局文件,以及顯示效果我們可以看到,它是一個幀布局,第一個ImageView是背景,第二個ImageView是.9的陰影效果的圖片(看下面的圖),

兩個(ScrollView包裹著一個LinerLayout),可以從上面圖看到結構分別是左菜單和右菜單


/

 

1.初始化布局以及布局文件分析完畢,2.接下來是設置背景圖,初始化view的時候就已經拿到了背景控件,所以設置背景圖也是非常好實現的事情了。

 

 public void setBackground(int imageResrouce){
        imageViewBackground.setImageResource(imageResrouce);
    }
3.綁定activity,思考問題:它做了什麼?

 

 

 

   /**
     * use the method to set up the activity which residemenu need to show;
     *
     * @param activity
     */
    public void attachToActivity(Activity activity){
        initValue(activity);
        setShadowAdjustScaleXByOrientation();
        viewDecor.addView(this, 0);
        setViewPadding();
    }
原理分析:綁定activity做了4件事情,分別是:

 

1.初始化參數:

 

    private void initValue(Activity activity){
        this.activity   = activity;
        leftMenuItems   = new ArrayList();
        rightMenuItems  = new ArrayList();
        ignoredViews    = new ArrayList();
        viewDecor = (ViewGroup) activity.getWindow().getDecorView();
        viewActivity = new TouchDisableView(this.activity);

        View mContent   = viewDecor.getChildAt(0);
        viewDecor.removeViewAt(0);
        viewActivity.setContent(mContent);
        addView(viewActivity);

        ViewGroup parent = (ViewGroup) scrollViewLeftMenu.getParent();
        parent.removeView(scrollViewLeftMenu);
        parent.removeView(scrollViewRightMenu);
    }

 

2.正對橫豎屏縮放比例進行調整

 

  private void setShadowAdjustScaleXByOrientation(){
        int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            shadowAdjustScaleX = 0.034f;
            shadowAdjustScaleY = 0.12f;
        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            shadowAdjustScaleX = 0.06f;
            shadowAdjustScaleY = 0.07f;
        }
    }

 

3.添加當前view

 

viewDecor.addView(this, 0);

 

4.設置view邊距

 

/**
     * we need the call the method before the menu show, because the
     * padding of activity can't get at the moment of onCreateView();
     */
    private void setViewPadding(){
        this.setPadding(viewActivity.getPaddingLeft(),
                viewActivity.getPaddingTop(),
                viewActivity.getPaddingRight(),
                viewActivity.getPaddingBottom());
    }
4.設置監聽,思考問題:它什麼時候調用監聽,原理分析:動畫監聽開始執行動畫掉哦那個openMenu動畫結束調用closeMenu,從此我們可以想到,但它調用openMenu(int direction)和closeMenu()都會設置這個監聽。

 

 

private Animator.AnimatorListener animationListener = new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            if (isOpened()){
                showScrollViewMenu();
                if (menuListener != null)
                    menuListener.openMenu();
            }
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            // reset the view;
            if(isOpened()){
                viewActivity.setTouchDisable(true);
                viewActivity.setOnClickListener(viewActivityOnClickListener);
            }else{
                viewActivity.setTouchDisable(false);
                viewActivity.setOnClickListener(null);
                hideScrollViewMenu();
                if (menuListener != null)
                    menuListener.closeMenu();
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };
5.設置內容縮放比例(0.1~1f),細心的同學會發現在當縮完成後還可以在往裡面拉到更小,有種彈性的感覺,挺有趣的。但是有些人的需求不想要有這種彈性效果,我們可以通過修改源碼修改這個彈性效果,找到getTargetScale這個方法,修改下面0.5這個數值。使用時設置了0.6的縮放比例,默認下面的彈性參數是0.5所以我們當縮完成後還可以在往裡面拉0.1的比例。

 

 

    private float getTargetScale(float currentRawX){
        float scaleFloatX = ((currentRawX - lastRawX) / getScreenWidth()) * 0.75f;
        scaleFloatX = scaleDirection == DIRECTION_RIGHT ? - scaleFloatX : scaleFloatX;

        float targetScale = ViewHelper.getScaleX(viewActivity) - scaleFloatX;
        targetScale = targetScale > 1.0f ? 1.0f : targetScale;
        targetScale = targetScale < 0.5f ? 0.5f : targetScale;
        return targetScale;
    }

 

默認縮放比例:

 

 //valid scale factor is between 0.0f and 1.0f.
    private float mScaleValue = 0.5f;
 AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
/**
     * a helper method to build scale down animation;
     *
     * @param target
     * @param targetScaleX
     * @param targetScaleY
     * @return
     */
    private AnimatorSet buildScaleDownAnimation(View target,float targetScaleX,float targetScaleY){

        AnimatorSet scaleDown = new AnimatorSet();
        scaleDown.playTogether(
                ObjectAnimator.ofFloat(target, scaleX, targetScaleX),
                ObjectAnimator.ofFloat(target, scaleY, targetScaleY)
        );

        scaleDown.setInterpolator(AnimationUtils.loadInterpolator(activity,
                android.R.anim.decelerate_interpolator));
        scaleDown.setDuration(250);
        return scaleDown;
    }
6.創建子菜單,看下子菜單的構造,我們通過上面的學習,原理分析:我們可以猜測到,無非就是加載布局設置內容

 

 

public ResideMenuItem(Context context, int icon, String title) {
        super(context);
        initViews(context);
        iv_icon.setImageResource(icon);
        tv_title.setText(title);
    }

    private void initViews(Context context){
        LayoutInflater inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.residemenu_item, this);
        iv_icon = (ImageView) findViewById(R.id.iv_icon);
        tv_title = (TextView) findViewById(R.id.tv_title);
    }
布局文件:

 

 





    

    

 

顯示效果圖:

\

7.子菜單添加到側換菜單中(可以看到它是通過常量來控制子菜單的添加位置)原理分析:根據不同的常量來區分添加不同菜單的子菜單

 

 /**
     * add a single items;
     *
     * @param menuItem
     * @param direction
     */
    public void addMenuItem(ResideMenuItem menuItem, int direction){
        if (direction == DIRECTION_LEFT){
            this.leftMenuItems.add(menuItem);
            layoutLeftMenu.addView(menuItem);
        }else{
            this.rightMenuItems.add(menuItem);
            layoutRightMenu.addView(menuItem);
        }
    }
8.設置title按鈕的點擊事件,設置左右菜單的開關,原理分析:先設置了縮放方向然後在設置動畫,正如我們上面想的一樣還設置了動畫監聽。

 

 

    /**
     * show the reside menu;
     */
    public void openMenu(int direction){

        setScaleDirection(direction);

        isOpened = true;
        AnimatorSet scaleDown_activity = buildScaleDownAnimation(viewActivity, mScaleValue, mScaleValue);
        AnimatorSet scaleDown_shadow = buildScaleDownAnimation(imageViewShadow,
        		mScaleValue + shadowAdjustScaleX, mScaleValue + shadowAdjustScaleY);
        AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 1.0f);
        scaleDown_shadow.addListener(animationListener);
        scaleDown_activity.playTogether(scaleDown_shadow);
        scaleDown_activity.playTogether(alpha_menu);
        scaleDown_activity.start();
    }
設置縮放方向及計算x,y軸位置。

 

 

    private void setScaleDirection(int direction){

        int screenWidth = getScreenWidth();
        float pivotX;
        float pivotY = getScreenHeight() * 0.5f;

        if (direction == DIRECTION_LEFT){
            scrollViewMenu = scrollViewLeftMenu;
            pivotX  = screenWidth * 1.5f;
        }else{
            scrollViewMenu = scrollViewRightMenu;
            pivotX  = screenWidth * -0.5f;
        }

        ViewHelper.setPivotX(viewActivity, pivotX);
        ViewHelper.setPivotY(viewActivity, pivotY);
        ViewHelper.setPivotX(imageViewShadow, pivotX);
        ViewHelper.setPivotY(imageViewShadow, pivotY);
        scaleDirection = direction;
    }
9.重寫dispatchTouchEvent,問題思考:如何到根據手指滑動自動縮放

 

如果還不了解,dispatchTouchEvent這個函數如何調用?什麼時候調用?請先看看http://blog.csdn.net/cym492224103/article/details/39179311

 

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        float currentActivityScaleX = ViewHelper.getScaleX(viewActivity);
        if (currentActivityScaleX == 1.0f)
            setScaleDirectionByRawX(ev.getRawX());

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                lastActionDownX = ev.getX();
                lastActionDownY = ev.getY();
                isInIgnoredView = isInIgnoredView(ev) && !isOpened();
                pressedState    = PRESSED_DOWN;
                break;

            case MotionEvent.ACTION_MOVE:
                if (isInIgnoredView || isInDisableDirection(scaleDirection))
                    break;

                if(pressedState != PRESSED_DOWN &&
                        pressedState != PRESSED_MOVE_HORIZANTAL)
                    break;

                int xOffset = (int) (ev.getX() - lastActionDownX);
                int yOffset = (int) (ev.getY() - lastActionDownY);

                if(pressedState == PRESSED_DOWN) {
                    if(yOffset > 25 || yOffset < -25) {
                        pressedState = PRESSED_MOVE_VERTICAL;
                        break;
                    }
                    if(xOffset < -50 || xOffset > 50) {
                        pressedState = PRESSED_MOVE_HORIZANTAL;
                        ev.setAction(MotionEvent.ACTION_CANCEL);
                    }
                } else if(pressedState == PRESSED_MOVE_HORIZANTAL) {
                    if (currentActivityScaleX < 0.95)
                        showScrollViewMenu();

                    float targetScale = getTargetScale(ev.getRawX());
                    ViewHelper.setScaleX(viewActivity, targetScale);
                    ViewHelper.setScaleY(viewActivity, targetScale);
                    ViewHelper.setScaleX(imageViewShadow, targetScale + shadowAdjustScaleX);
                    ViewHelper.setScaleY(imageViewShadow, targetScale + shadowAdjustScaleY);
                    ViewHelper.setAlpha(scrollViewMenu, (1 - targetScale) * 2.0f);

                    lastRawX = ev.getRawX();
                    return true;
                }

                break;

            case MotionEvent.ACTION_UP:

                if (isInIgnoredView) break;
                if (pressedState != PRESSED_MOVE_HORIZANTAL) break;

                pressedState = PRESSED_DONE;
                if (isOpened()){
                    if (currentActivityScaleX > 0.56f)
                        closeMenu();
                    else
                        openMenu(scaleDirection);
                }else{
                    if (currentActivityScaleX < 0.94f){
                        openMenu(scaleDirection);
                    }else{
                        closeMenu();
                    }
                }

                break;

        }
        lastRawX = ev.getRawX();
        return super.dispatchTouchEvent(ev);
    }
上面代碼量有點多,看上去有點暈,接下來我們來分別從按下、移動、放開、來原理分析:

 

 

MotionEvent.ACTION_DOWN:

記錄了X,Y軸的坐標點,判斷是否打開,設置了按下的狀態為PRESSED_DOWN

 

MotionEvent.ACTION_MOVE:

拿到當前X,Y減去DOWN下記錄下來的X,Y,這樣得到了移動的X,Y,

然後判斷如果如果移動的X,Y大於25或者小於-25就改變按下狀態為PRESSED_MOVE_VERTICAL

如果移動的X,Y大於50或者小於-50就改變狀態為PRESSED_MOVE_HORIZANTAL

狀態為PRESSED_MOVE_HORIZANTAL就改變菜單主視圖內容以及陰影圖片大小,在改變的同時還設置了當前菜單的透明度。

 

MotionEvent.ACTION_UP:

判斷是否菜單是否打開狀態,在獲取當前縮放的X比例,

判斷比例小於0.56f,則關閉菜單,反正開啟菜單。

看完後,我們在回去看看代碼,就會發現其實也不過如此~!

10.菜單關閉方法,同樣也設置了動畫監聽之前的想法也是成立的。

 

 /**
     * close the reslide menu;
     */
    public void closeMenu(){

        isOpened = false;
        AnimatorSet scaleUp_activity = buildScaleUpAnimation(viewActivity, 1.0f, 1.0f);
        AnimatorSet scaleUp_shadow = buildScaleUpAnimation(imageViewShadow, 1.0f, 1.0f);
        AnimatorSet alpha_menu = buildMenuAnimation(scrollViewMenu, 0.0f);
        scaleUp_activity.addListener(animationListener);
        scaleUp_activity.playTogether(scaleUp_shadow);
        scaleUp_activity.playTogether(alpha_menu);
        scaleUp_activity.start();
    }
11.屏蔽菜單方法

 

 

public void setSwipeDirectionDisable(int direction){
        disabledSwipeDirection.add(direction);
    }
private boolean isInDisableDirection(int direction){
        return disabledSwipeDirection.contains(direction);
    }
原理分析:在重寫dispatchTouchEvent的時候,細心的同學應該會看到,ACTION_MOVE下面有個判斷

 

 

if (isInIgnoredView || isInDisableDirection(scaleDirection))
如果這個方向的菜單被屏蔽了,就滑不出來了。

 

最後我們會發現我們一直都沒說到TouchDisableView,其實initValue的時候就初始化了,它就是viewActivity,是我們的內容視圖。

\

我們來看看它做了什麼?

 

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = getDefaultSize(0, widthMeasureSpec);
        int height = getDefaultSize(0, heightMeasureSpec);
        setMeasuredDimension(width, height);

        final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);
        final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);
        mContent.measure(contentWidth, contentHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int width = r - l;
        final int height = b - t;
        mContent.layout(0, 0, width, height);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mTouchDisabled;
    }

    void setTouchDisable(boolean disableTouch) {
        mTouchDisabled = disableTouch;
    }

    boolean isTouchDisabled() {
        return mTouchDisabled;
    }
動態設置寬高,設置事件是否傳遞下去的flag。

 

好了,源碼分析已完畢,喜歡這篇文章的就請關注我吧~!

 








 

 

 

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved