DrawerLayout实现侧滑菜单

1 DrawerLayout说明
1.1继承体系:java.lang.Object <-android.view.View <- android.view.ViewGroup <- android.support.v4.widget.DrawerLayout
1.2JavaDoc中的说明:DrawerLayout是一个包含了窗口内容(window content)的顶层容器,它可以让窗口内容与窗口边缘拉出的“自绘”(“drawer”)视图交互。
Drawer的位置和布局是可控的,在子视图的参数中使用android:layout_gravity设定你希望Drawer从父视图的哪边滑出:left或right(或者start/end,在支持布局方向设置的平台版本中)(注:这里的left/right和start/end是android:layout_gravity的具体取值,推荐使用start/end)。
为了使用DrawerLayout,你需要把一个宽高值都是match_parent的主内容视图(primary content view)作为布局的第一个子组件。然后在主内容视图后添加Drawer子组件并配置合适的layout_gravity参数。Drawer通常使用match_parent的高(height)和一个固定值的宽(width)(注:固定值的宽代表了Drawer被滑出后可显示的宽度)。
接口DrawerLayout.DrawerListener可以用于监听Drawer状态和动作的变化。在Drawer动画过程中避免执行耗时的操作,否则可能造成卡顿;可以在STATE_IDLE状态下执行耗时操作。抽象类DrawerLayout.SimpleDrawerListener为每一个回调方法提供了默认的无操作的实现。
就如每一个Android设计指南所描述的那样,通常一个Drawer的滑出位置在left/start时,其内容是应用的导航信息,而如果滑出位置在right/end时,其内容是可以对当前主内容执行的操作。Action Bar提供了类似的左边导航右边动作的语法糖(注:通过类ActionBarDrawerToggle,该类实现了DrawerListener接口)。
1.3小结:通过对DrawerLayout的说明可以看出,该布局同LinaerLayout等布局一样,也是顶层布局,不同的是其内部子组件有严格的顺序,第一个子组件(view)代表了显示的主内容,可以是一个layout或者一个简单的view,第二个子组件代表了侧滑出的菜单,也可以是一个layout或者简单的view。侧滑的方向由第二个子组件的layout_gravity参数决定,取值是left/right或start/end(建议用start/end,避免和原来的布局方向取值混淆)。通过实现DrawerLayout.DrawerListener接口,可以回调各种有用的方法。
1.4 注:这里的Drawer代指侧滑菜单。
1.5 小结:经测试,侧滑菜单和主内容区可以不严格按顺序来,但是如果第一个子组件指定layout_gravity属性,而且包含listview的话,则listview不能拖动,只有当包含listview的子组件位置在所有子组件的最后时,listview可以响应拖动,而主布局(DrawerLayout)中其他未指定layout_gravity属性的子组件都是内容区。如果指定两个同layout_gravity属性的组件,APP不会报错,但是侧滑以后会无响应。
通过查看源码,侧滑菜单拖动依赖于android.support.v4.widget.ViewDragHelper,其内部变量”private static final int EDGE_SIZE = 20; // dp”定义了侧滑菜单拖动的边距,即在边缘多少dp的地方按下后可以触发拖动,如果在边缘按下BASE_SETTLE_DURATION=256毫秒后,会弹出侧滑菜单,然后可以拖动,如果快速从边缘按下并拖动,则直接出现拖动中的侧滑菜单。具体菜单的绘制过程都在该类中。
通过反射可以修改边缘感应距离:

Field f = drawerLayout.getClass().getDeclaredField("mLeftDragger");
            f.setAccessible(true);
            Class drag = f.getType();
            Field edge = drag.getDeclaredField("mEdgeSize");
            edge.setAccessible(true);
            int width = listview.getLayoutParams().width/2;
            edge.setInt(f.get(drawerLayout), width);

如果修改的距离太大,可能会发生拖不出侧滑菜单的bug,原因是距离太大,在感应区的触摸时间超过了BASE_SETTLE_DURATION,进入了响应长按弹出侧滑菜单的逻辑,也可以通过反射修改,这里就需要根据具体需要去做修改了。
侧滑菜单必须设置背景色,否则默认是透明背景。
1.6状态常量:STATE_DRAGGING:表明用户正在拖动Drawer。STATE_IDLE:表明Drawer目前空闲,被固定。STATE_SETTLING:表明Drawer马上要进入最终位置。
1.7主要方法:1)void setScrimColor(int color) :设置未被侧滑菜单遮挡的主内容区的覆盖色,默认是渐变灰,如果设置为android.R.color.transparent,则未覆盖区为透明色,即不改变未覆盖区的显示。
2)void setDrawerShadow(Drawable shadowDrawable, int gravity) :设置侧滑菜单在拖动侧边缘的显示图片,比如从start位拖出,可以在拖动边缘显示一个向右的箭头图片。

2 DrawerLayout.DrawerListener接口说明
2.1 说明:该接口用于监视Drawer
2.2 主要回调方法:1)abstract void OnDrawerClosed(View drawerView)
说明:当Drawer完全关闭时回调
参数:drawerView 被关闭的Drawer视图
2)abstract void OnDrawerOpened(View drawerView)
说明:当Drawer完全打开时回调,此时Drawer处在可以交互的位置(注:即可以点击Drawer内的子组件了)
参数:同上
3)abstract void OnDrawerSlide(View drawerView, float slideOffset)
说明:当Drawer的位置改变时回调
参数:同上。slideOffset 当前Drawer的新偏移量占其宽的比值,取值0-1
4)abstract void OnDrawerStateChanged(int newState)
说明:当Drawer状态改变时回调
参数:新状态,取值STATE_IDLE,STATE_DRAGGING,STATE_SETTLING
2.3 主要的实现类:ActionBarDrawerToggle, DrawerLayout.SimpleDrawerListener
3 ActionBarDrawerToggle说明
注:SimpleDrawerListener是抽象类,方便开发者实现接口而不用实现所有接口
3.1 JavaDoc中的说明:该类实现了一种方法可以把DrawerLayout和框架ActionBar关联在一起,推荐用该方法实现导航视图设计。
为了使用本类,可以在你的Activity中创建一个实例,然后在适当的时候调用onConfigurationChanged和onOptionsItemSelected。
在onRestoreInstanceState被调用后,在你的Activity的onPostCreate方法中调用本类的syncState()方法来同步关联的DrawerLayout的状态。
本类可以完全当做一个DrawerListener。
3.2 主要方法:public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, int drawerImageRes, int openDrawerContentDescRes, int closeDrawerContentDescRes)
说明:给定的activity会和指定的DrawerLayout关联。Drawer指示器图标(drawer indicator drawable)在Drawer打开时会触发动画,指示了Drawer在打开状态时离屏显示,在关闭状态时在屏显示。字符串资源用来描述Drawer的open/close动作。
参数:activity:Drawer托管的activity。drawerLayout:给定的Activity的ActionBar关联的DrawerLayout。drawerImageRes:一个Drawable资源用来做Drawer指示器。
void syncState()
说明:同步Drawer指示器和关联DrawerLayout的状态。该方法必须在Activity的onPostCreate方法中调用,可以在DrawerLayout实例的状态恢复后同步二者状态,而在其他时间,本类不会收到状态通知(比如,如果你在一段时间内停止分发Drawer的事件)。
4 SDK中的示例

public class DrawerLayoutActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ListView mDrawer;//侧滑菜单
    private TextView mContent;//主内容区

    private ActionBarHelper mActionBar;//Activity的ActionBar封装

    private ActionBarDrawerToggle mDrawerToggle;//连接DrawerLayout和ActionBar的DrawerListener

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.drawer_layout);

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawer = (ListView) findViewById(R.id.start_drawer);
        mContent = (TextView) findViewById(R.id.content_text);

        mDrawerLayout.setDrawerListener(new DemoDrawerListener());
        //设置侧滑菜单边缘的阴影
        mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);

        mDrawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.drawer_title));
        //这里的侧滑菜单是一个ListView,所以设置他的adapter
        mDrawer.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
                Shakespeare.TITLES));
        mDrawer.setOnItemClickListener(new DrawerItemClickListener());

        mActionBar = createActionBarHelper();
        mActionBar.init();

        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        /*
         * The action bar home/up action should open or close the drawer.
         * mDrawerToggle will take care of this.
         */
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    /**
     * This list item click listener implements very simple view switching by changing
     * the primary content text. The drawer is closed when a selection is made.
     */
    private class DrawerItemClickListener implements ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            mContent.setText(Shakespeare.DIALOGUE[position]);
            mActionBar.setTitle(Shakespeare.TITLES[position]);
            mDrawerLayout.closeDrawer(mDrawer);
        }
    }

    /**
     * A drawer listener can be used to respond to drawer events such as becoming
     * fully opened or closed. You should always prefer to perform expensive operations
     * such as drastic relayout when no animation is currently in progress, either before
     * or after the drawer animates.
     *
     * When using ActionBarDrawerToggle, all DrawerLayout listener methods should be forwarded
     * if the ActionBarDrawerToggle is not used as the DrawerLayout listener directly.
     */
    private class DemoDrawerListener implements DrawerLayout.DrawerListener {
        @Override
        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.onDrawerOpened(drawerView);
            mActionBar.onDrawerOpened();
        }

        @Override
        public void onDrawerClosed(View drawerView) {
            mDrawerToggle.onDrawerClosed(drawerView);
            mActionBar.onDrawerClosed();
        }

        @Override
        public void onDrawerSlide(View drawerView, float slideOffset) {
            mDrawerToggle.onDrawerSlide(drawerView, slideOffset);
        }

        @Override
        public void onDrawerStateChanged(int newState) {
            mDrawerToggle.onDrawerStateChanged(newState);
        }
    }

    /**
     * Create a compatible helper that will manipulate the action bar if available.
     */
    private ActionBarHelper createActionBarHelper() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return new ActionBarHelperICS();
        } else {
            return new ActionBarHelper();
        }
    }

    /**
     * Stub action bar helper; this does nothing.
     */
    private class ActionBarHelper {
        public void init() {}
        public void onDrawerClosed() {}
        public void onDrawerOpened() {}
        public void setTitle(CharSequence title) {}
    }

    /**
     * Action bar helper for use on ICS and newer devices.
     */
    private class ActionBarHelperICS extends ActionBarHelper {
        private final ActionBar mActionBar;
        private CharSequence mDrawerTitle;
        private CharSequence mTitle;

        ActionBarHelperICS() {
            mActionBar = getActionBar();
        }

        @Override
        public void init() {
            mActionBar.setDisplayHomeAsUpEnabled(true);
            mActionBar.setHomeButtonEnabled(true);
            mTitle = mDrawerTitle = getTitle();
        }

        /**
         * When the drawer is closed we restore the action bar state reflecting
         * the specific contents in view.
         */
        @Override
        public void onDrawerClosed() {
            super.onDrawerClosed();
            mActionBar.setTitle(mTitle);
        }

        /**
         * When the drawer is open we set the action bar to a generic title.
         * The action bar should only contain data relevant at the top level of
         * the nav hierarchy represented by the drawer, as the rest of your content
         * will be dimmed down and non-interactive.
         */
        @Override
        public void onDrawerOpened() {
            super.onDrawerOpened();
            mActionBar.setTitle(mDrawerTitle);
        }

        @Override
        public void setTitle(CharSequence title) {
            mTitle = title;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值