首页 > 代码库 > Android高手进阶篇4-实现侧滑菜单框架,一分钟集成到项目中

Android高手进阶篇4-实现侧滑菜单框架,一分钟集成到项目中

先来看下面的这张效果图:

上面这张效果图是百度影音的,现在在Android上很流行,最初是由facebook自己实现的,而后各大应用有跟风之势,那么这种侧滑效果是如何实现的呢?

网上现在这种侧滑菜单的例子很对,也有开源的框架sliderMenu,而且可以定义很多样式,但大部分例子,都只是实现了这种类似效果,没有实现一种可移植的框架,仅仅是单页面效果而已,而且集成起来复杂,鉴于此,我自己实现了一套侧滑菜单的框架:

1、最常用的支持左右策划

2、多个页面切换也好不费力,页面切换的逻辑已经实现好了,集成进来,只需要关注自己项目的业务逻辑

3、支持多个页面集成

4、支持退出业务逻辑

 

先上我自己实现的效果图:

 

下面 说一下实现原理:

     布局文件采用FrameLayout, 在一个FrameLayout下有二个子布局,一个是菜单,另一个是LeftSliderLayout,而LeftSliderLayout下面可以放二个子布局:第一个是阴影布局(左边阴影),第二个是要拖动的内容。,当向右拖动LeftSliderLayout时,就显示露出菜单布局。而向左拖动LeftSliderLayout时,就覆盖菜单布局。

 1.FrameLayout的布局文件local_media_fragment.xml

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent" >    <include android:id="@+id/main_layout_below" layout="@layout/main_layout_below" />    <com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout        android:id="@+id/main_slider_layout"        android:layout_width="fill_parent"        android:layout_height="fill_parent" >        <!-- Shadow Child -->        <ImageView                android:layout_width="15px"                android:layout_height="fill_parent"                android:contentDescription="@null"                android:scaleType="fitXY"                android:src="@drawable/main_side_shadow" />        <!-- Main Child -->        <include android:id="@+id/main_slider_main" layout="@layout/local_media" />    </com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout></FrameLayout>

上面 xml 中main_layout_below是对应的左边菜单Menu布局文件(这个布局文件是固定的),local_media是你要的拖动布局

2、LeftSliderLayout.java代码

package com.zhaoxufeng.leftsliderlayout.lib;import android.content.Context;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.widget.Scroller;public class LeftSliderLayout extends ViewGroup {    private static final String TAG = "LeftSliderLayout"  ;    private Scroller mScroller;    private VelocityTracker mVelocityTracker;    /**     * Constant value for touch state     * TOUCH_STATE_REST : no touch     * TOUCH_STATE_SCROLLING : scrolling     */    private static final int TOUCH_STATE_REST = 0;    private static final int TOUCH_STATE_SCROLLING = 1;    private int mTouchState = TOUCH_STATE_REST;        /**     * Distance in pixels a touch can wander before we think the user is scrolling     */    private int mTouchSlop;        /**     * Values for saving axis of the last touch event.     */    private float mLastMotionX;    private float mLastMotionY;        /**     * Values for VelocityTracker to compute current velocity.     * VELOCITY_UNITS in dp     * mVelocityUnits in px     */    private static final int VELOCITY_UNITS = 1000;    private int mVelocityUnits;            /**     * The minimum velocity for determining the direction.     * MINOR_VELOCITY in dp     * mMinorVelocity in px     */    private static final float MINOR_VELOCITY = 150.0f;    private int mMinorVelocity;                                        /**     * The width of Sliding distance from left.      * And it should be the same with the width of the View below SliderLayout in a FrameLayout.     * DOCK_WIDTH in dp     * mDockWidth in px     */    private static final float SLIDING_WIDTH = 270.0f;    private int mSlidingWidth;                                            /**     * The default values of shadow.     * VELOCITY_UNITS in dp     * mVelocityUnits in px     */    private static final float DEF_SHADOW_WIDTH = 10.0f;            private int mDefShadowWidth;                                    /**     * Value for checking a touch event is completed.     */    private boolean mIsTouchEventDone = false;                        /**     * Value for checking slider is open.     */    private boolean mIsOpen = false;                                /**     * Value for saving the last offset of scroller ’ x-axis.     */    private int mSaveScrollX = 0;                                    /**     * Value for checking slider is allowed to slide.     */    private boolean mEnableSlide = true;                            private View mMainChild = null;    private OnLeftSliderLayoutStateListener mListener = null;    /**     * Instantiates a new LeftSliderLayout.     *     * @param context the associated Context     * @param attrs AttributeSet     */    public LeftSliderLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    /**     * Instantiates a new LeftSliderLayout.     *     * @param context the associated Context     * @param attrs AttributeSet     * @param defStyle Style     */    public LeftSliderLayout(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        mScroller = new Scroller(context);        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();                /**         * Convert values in dp to values in px;         */        final float fDensity = getResources().getDisplayMetrics().density;        mVelocityUnits = (int) (VELOCITY_UNITS * fDensity + 0.5f);        mMinorVelocity = (int) (MINOR_VELOCITY * fDensity + 0.5f);        mSlidingWidth = (int) (SLIDING_WIDTH * fDensity + 0.5f);        mDefShadowWidth = (int) (DEF_SHADOW_WIDTH * fDensity + 0.5f);    }        @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);                // check Measure Mode is Exactly.        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);        if (widthMode != MeasureSpec.EXACTLY) {            throw new IllegalStateException("LeftSliderLayout only canmCurScreen run at EXACTLY mode!");        }        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        if (heightMode != MeasureSpec.EXACTLY) {            throw new IllegalStateException("LeftSliderLayout only can run at EXACTLY mode!");        }        // measure child views        int nCount = getChildCount();        for (int i = 2; i < nCount; i++) {            removeViewAt(i);        }        nCount = getChildCount();        if (nCount > 0) {            if (nCount > 1) {                mMainChild = getChildAt(1);                getChildAt(0).measure(widthMeasureSpec, heightMeasureSpec);            } else {                mMainChild = getChildAt(0);            }            mMainChild.measure(widthMeasureSpec, heightMeasureSpec);        }                // Set the scrolled position         scrollTo(mSaveScrollX, 0);    }        @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        final int nCount = getChildCount();        if (nCount <= 0) {            return;        }                // Set the size and position of Main Child        if (mMainChild != null) {            mMainChild.layout(                l,                t,                l + mMainChild.getMeasuredWidth(),                t + mMainChild.getMeasuredHeight());        }                // Set the size and position of Shadow Child        if (nCount > 1) {            int nLeftChildWidth = 0;            View leftChild = getChildAt(0);            ViewGroup.LayoutParams layoutParams = leftChild.getLayoutParams();            if (layoutParams.width == ViewGroup.LayoutParams.FILL_PARENT                    || layoutParams.width == ViewGroup.LayoutParams.MATCH_PARENT) {                nLeftChildWidth = mDefShadowWidth;            } else {                nLeftChildWidth = layoutParams.width;            }            leftChild.layout(                    l - nLeftChildWidth,                    t,                    l,                    t + leftChild.getMeasuredHeight());        }    }        @Override    public void computeScroll() {        if (mScroller.computeScrollOffset()) {            Log.d(TAG,"computeScroll exeuted:" + "x:" + mScroller.getCurrX() + "Y:" + mScroller.getCurrY())  ;            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());            postInvalidate();        }    }    @Override    public boolean onTouchEvent(MotionEvent event) {                 int nCurScrollX = getScrollX();                // check touch point is in the rectangle of Main Child        if (mMainChild != null                && mTouchState != TOUCH_STATE_SCROLLING                && mIsTouchEventDone) {            Rect rect = new Rect();            mMainChild.getHitRect(rect);            if (!rect.contains((int)event.getX() + nCurScrollX, (int)event.getY())) {                return false;            }        }                if (mVelocityTracker == null) {            mVelocityTracker = VelocityTracker.obtain();        }        mVelocityTracker.addMovement(event);                final int action = event.getAction();        final float x = event.getX();                switch (action) {        case MotionEvent.ACTION_DOWN: {            if (!mScroller.isFinished()) {                mScroller.abortAnimation();            }                        mIsTouchEventDone = false;            mLastMotionX = x;            break;        }        case MotionEvent.ACTION_MOVE: {            // check slider is allowed to slide.            if (!mEnableSlide) {                break;            }                        // compute the x-axis offset from last point to current point            int deltaX = (int) (mLastMotionX - x);            if (nCurScrollX + deltaX < getMinScrollX()) {                deltaX = getMinScrollX() - nCurScrollX;                mLastMotionX = mLastMotionX - deltaX;            } else if (nCurScrollX + deltaX > getMaxScrollX()) {                deltaX = getMaxScrollX() - nCurScrollX;                mLastMotionX = mLastMotionX - deltaX;            } else {                mLastMotionX = x;            }                        // Move view to the current point            if (deltaX != 0) {                scrollBy(deltaX, 0);            }                        // Save the scrolled position             mSaveScrollX = getScrollX();            break;        }        case MotionEvent.ACTION_CANCEL:         case MotionEvent.ACTION_UP: {                        // check slider is allowed to slide.            if (!mEnableSlide) {                break;            }                        final VelocityTracker velocityTracker = mVelocityTracker;            velocityTracker.computeCurrentVelocity(mVelocityUnits);                        // Set open or close state, when get ACTION_UP or ACTION_CANCEL event.            if (nCurScrollX < 0) {                int velocityX = (int) velocityTracker.getXVelocity();                if (velocityX > mMinorVelocity) {                    scrollByWithAnim(getMinScrollX() - nCurScrollX);                    setState(true);                }                else if (velocityX < -mMinorVelocity) {                    scrollByWithAnim(-nCurScrollX);                    setState(false);                } else {                    if (nCurScrollX >= getMinScrollX() / 2) {                        scrollByWithAnim(- nCurScrollX);                        setState(false);                    } else {                        scrollByWithAnim(getMinScrollX() - nCurScrollX);                        setState(true);                    }                }            } else {                if (nCurScrollX > 0) {                    scrollByWithAnim(-nCurScrollX);                }                setState(false);            }                        if (mVelocityTracker != null) {                mVelocityTracker.recycle();                mVelocityTracker = null;            }            mTouchState = TOUCH_STATE_REST;            mIsTouchEventDone = true;            break;        }                }        return true;    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {                final int action = ev.getAction();                if (mListener != null && !mListener.OnLeftSliderLayoutInterceptTouch(ev)) {            return false;        }                if ((action == MotionEvent.ACTION_MOVE)                && (mTouchState != TOUCH_STATE_REST)) {                 return true;        }        final float x = ev.getX();        final float y = ev.getY();        switch (action) {        case MotionEvent.ACTION_DOWN:                 mLastMotionX = x;                 mLastMotionY = y;                 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;                  break;        case MotionEvent.ACTION_MOVE:                 final int xDiff = (int) Math.abs(mLastMotionX - x);                 if (xDiff > mTouchSlop) {                           if (Math.abs(mLastMotionY - y) / Math.abs(mLastMotionX - x) < 1)                                   mTouchState = TOUCH_STATE_SCROLLING;                }                break;        case MotionEvent.ACTION_CANCEL:        case MotionEvent.ACTION_UP:                 mTouchState = TOUCH_STATE_REST;                 break;        }        return mTouchState != TOUCH_STATE_REST;    }        /**     * With the horizontal scroll of the animation     *      * @param nDx x-axis offset     */    void scrollByWithAnim(int nDx) {        if (nDx == 0) {            return;        }        Log.d(TAG,"scrollByWithAnim:" + "x:" + (getScrollX() + "Y:" + Math.abs(nDx)))  ;        mScroller.startScroll(getScrollX(), 0, nDx, 0,                Math.abs(nDx));        invalidate();    }    /**     * Get distance of the maximum horizontal scroll     *      * @return distance in px     */    private int getMaxScrollX() {        return 0;    }        /**     * Get distance of the minimum horizontal scroll     * @return distance in px     */    private int getMinScrollX() {        return -mSlidingWidth;    }            /**     * Open LeftSlideLayout     */    public void open() {        Log.d(TAG,"scroll by width:" + (getMinScrollX() - getScrollX()))  ;        if (mEnableSlide) {            Log.d(TAG,"scroll by width:" + (getMinScrollX() - getScrollX()))  ;            scrollByWithAnim(getMinScrollX() - getScrollX());            setState(true);        }    }    /**     * Close LeftSlideLayout     */    public void close() {        if (mEnableSlide) {            scrollByWithAnim((-1) * getScrollX());            setState(false);        }    }        /**     * Determine whether LeftSlideLayout is open     *      * @return true-open,false-close     */    public boolean isOpen() {        return mIsOpen;    }        /**     * Set state of LeftSliderLayout     *      * @param bIsOpen the new state     */    private void setState(boolean bIsOpen) {        boolean bStateChanged = false;        if (mIsOpen && !bIsOpen) {            bStateChanged = true;        } else if (!mIsOpen && bIsOpen) {            bStateChanged = true;        }                mIsOpen = bIsOpen;                if (bIsOpen) {            mSaveScrollX = getMaxScrollX();        } else {            mSaveScrollX = 0;        }                if (bStateChanged && mListener != null) {            mListener.OnLeftSliderLayoutStateChanged(bIsOpen);        }    }        /**     * enable slide action of LeftSliderLayout      *      * @param bEnable     */    public void enableSlide(boolean bEnable) {        mEnableSlide = bEnable;    }        /**     * Set listener to LeftSliderLayout     */    public void setOnLeftSliderLayoutListener(OnLeftSliderLayoutStateListener listener) {        mListener = listener;    }    /**     * LeftSliderLayout Listener     *     */    public interface OnLeftSliderLayoutStateListener {                 /**         * Called when LeftSliderLayout’s state has been changed.         *          * @param bIsOpen the new state         */        public void OnLeftSliderLayoutStateChanged(boolean bIsOpen);                /**         * Called when LeftSliderLayout has got onInterceptTouchEvent.         *          * @param ev Touch Event         * @return true - LeftSliderLayout need to manage the InterceptTouchEvent.         *         false - LeftSliderLayout don‘t need to manage the InterceptTouchEvent.         */        public boolean OnLeftSliderLayoutInterceptTouch(MotionEvent ev);   }}

LeftSliderLayout有一个Listener。它有二个函数,一个是LeftSliderLayout的打开与关闭的状态改变;另一个是InterceptTouchEvent的回调,主要解决的是在拖动内容中有要处理左右滑动的控件与LeftSliderLayout的左右滑动的事件有冲突,当它返回true时,LeftSliderLayout会处理左右滑动,当它返回false时,就不处理左右滑动的事件。

为了实现侧滑菜单框架,故实现了一个BaseActivity,其他Activity只需要继承这个Activity就行,


3、BaseActivity.java代码

package com.zhaoxufeng.leftsliderlayout.example;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.widget.*;import com.zhaoxufeng.leftsliderlayout.R;import com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout;import java.util.ArrayList;import java.util.List;/** * 基类Activity,SliderMenu的基础统一框架 * User: zhiwen.nan * Date: 13-10-7 * Time: 下午8:31 * */public class BaseActivity extends Activity implements LeftSliderLayout.OnLeftSliderLayoutStateListener, View.OnClickListener {    private  LeftSliderLayout leftSliderLayout;    private ImageView mOpenButton;    private TextView mTitleText;    private ListView mListView;    private List<ListItem> mDataList;    private  long waitTime = 2000;    private  long touchTime = 0;    private static final  String TAG = "BaseActivity"  ;    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);    }    @Override    protected void onResume() {        super.onResume();        bindView();        initialDataList();        ListViewAdapter listViewAdapter = new ListViewAdapter(BaseActivity.this,mDataList) ;        mListView.setAdapter(listViewAdapter);        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {            @Override            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {                finish();                switch (i)  {                    case 0:                        mTitleText.setText(getText(R.string.categ_local_video_list));                        Intent intent = new Intent(BaseActivity.this,LocalMediaActivity.class)  ;                        startActivity(intent);                        break;                    case 1:                        mTitleText.setText(getText(R.string.cate_leida));                        Intent radIntent = new Intent(BaseActivity.this,RadoActivity.class)  ;                        startActivity(radIntent);                        break;                    case 2:                        mTitleText.setText(getText(R.string.hot_viedo));                        Intent hotIntent = new Intent(BaseActivity.this,HotMediaListActivity.class)  ;                        startActivity(hotIntent);                        break;                    case 3:                        mTitleText.setText(getText(R.string.cate_favrouite_list));                        Intent collectIntent = new Intent(BaseActivity.this,CollectListActivity.class)  ;                        startActivity(collectIntent);                        break;                    default:                        leftSliderLayout.close();                        break;                }            }        });    }    @Override    public void onClick(View view) {    }    @Override    public void OnLeftSliderLayoutStateChanged(boolean bIsOpen) {        if (bIsOpen) {//            Toast.makeText(this, "LeftSliderLayout is open!", Toast.LENGTH_SHORT).show();            Log.d(TAG," leftsilder is open")  ;        } else {           // Toast.makeText(this, "LeftSliderLayout is close!", Toast.LENGTH_SHORT).show();            Log.d(TAG," leftsilder is close")  ;        }    }    @Override    public boolean OnLeftSliderLayoutInterceptTouch(MotionEvent ev) {        return false;    }    private  void initialDataList(){        mDataList = new ArrayList<ListItem>() ;        for (int i = 0; i<= 3; i ++)       {            ListItem listItem = new ListItem();            listItem.setImageType(i);            mDataList.add(listItem);        }    }    private  void bindView(){        leftSliderLayout = (LeftSliderLayout) findViewById(R.id.main_slider_layout);        leftSliderLayout.setOnLeftSliderLayoutListener(this);        mOpenButton = (ImageView)findViewById(R.id.openButton) ;        mTitleText = (TextView)findViewById(R.id.titleText) ;        mListView = (ListView)findViewById(R.id.listTab)  ;        mOpenButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if(leftSliderLayout.isOpen()) {                    leftSliderLayout.close();                } else {                    leftSliderLayout.open();                }            }        });    }    public  void openLeftSlider(boolean isToOpen){        if(isToOpen)    {            leftSliderLayout.open();        }else {            leftSliderLayout.close();        }    }    public  void enableSlider(boolean isEnable)    {         if(isEnable)  {              leftSliderLayout.enableSlide(true);         } else {             leftSliderLayout.enableSlide(false);         }    }    @Override    public void onBackPressed() {        if(!leftSliderLayout.isOpen())   {            leftSliderLayout.open();        } else {            long currentTime = System.currentTimeMillis();            if((currentTime-touchTime)>=waitTime) {                Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show();                touchTime = currentTime;            }else {                finish();                //todo                //退出业务逻辑 ,根据项目需求来写            }        }    }}

关于左侧菜单的业务逻辑都在BaseActivity里处理,另外返回的逻辑也在里面处理,顶部统一的导航栏打开菜单栏业务逻辑,还有左侧菜单跳转的业务逻辑

4、LocalMediaActivity.java 

package com.zhaoxufeng.leftsliderlayout.example;import android.app.Activity;import android.database.Cursor;import android.os.Bundle;import android.provider.Contacts;import android.view.MotionEvent;import android.view.View;import android.view.View.OnClickListener;import android.widget.*;import com.zhaoxufeng.leftsliderlayout.R;import com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout;import com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout.OnLeftSliderLayoutStateListener;import java.util.ArrayList;import java.util.List;/** * @author zhiwen.nan * @since 1.0 * 本地视频界面 */public class LocalMediaActivity extends BaseActivity {    private ListView mListView;    private TextView mTitleText;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.local_media_fragment);        mListView = (ListView)findViewById(R.id.localVideoList) ;        mTitleText = (TextView)findViewById(R.id.titleText)  ;        mTitleText.setText("本地视频");        Cursor cursor = getContentResolver().query(Contacts.People.CONTENT_URI, null, null, null, null);        startManagingCursor(cursor);        ListAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_expandable_list_item_1,                cursor,new String[]{Contacts.People.NAME},new int[]{android.R.id.text1});        mListView.setAdapter(listAdapter);    }       @Override    public boolean OnLeftSliderLayoutInterceptTouch(MotionEvent ev) {        return true;    }}

LocalMediaActivity是自己定义的Activty和业务逻辑界面,只需继承BaseActivity即可,其他Activity类似。

以上就是核心代码,源代码下载:

Android高手进阶篇4-实现侧滑菜单框架,一分钟集成到项目中