Android DrawerLayout Plus 增強版抽屜菜單,androiddrawerlayout

來源:互聯網
上載者:User

Android DrawerLayout Plus 增強版抽屜菜單,androiddrawerlayout
版本:1.0 日期:2015/2/2  2015/2/3  2015/2/26  2015/2/27著作權:©kince
一、概述
    DrawerLayout是官方提供的側滑菜單,相比SliddingMenu,它更加輕量級。預設情況下,DrawerLayout可以設定左側或者右側滑出菜單。如下,

xml布局:

<!--<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. --><com.sys.app.uikit.drawerlayoutplus.DrawerLayoutPlus xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/drawer_layout"    android:layout_width= "match_parent"    android:layout_height= "match_parent" >    <!--         As the main content view, the view below consumes the entire         space available using match_parent in both dimensions.    -->    <FrameLayout        android:id="@+id/content_frame"        android:layout_width="match_parent"        android:layout_height="match_parent" />    <!--         android:layout_gravity="start" tells DrawerLayout to treat         this as a sliding drawer on the left side for left-to-right         languages and on the right side for right-to-left languages.         The drawer is given a fixed width in dp and extends the full height of         the container. A solid background is used for contrast         with the content view.    -->    <!-- Left drawer -->    <ListView        android:id="@+id/left_drawer"        android:layout_width="240dp"        android:layout_height="match_parent"        android:layout_gravity="left"        android:background="#111"        android:choiceMode="singleChoice"        android:divider="@android:color/transparent"        android:dividerHeight="0dp" />    <!-- Right drawer -->    <ListView        android:id="@+id/right_drawer"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_gravity="right"        android:choiceMode="singleChoice" /></com.sys.app.uikit.drawerlayoutplus.DrawerLayoutPlus>
Activity代碼:
package com.sys.app.uikit.drawerlayoutplus;import java.util.Locale;import android.app.Activity;import android.app.Fragment;import android.app.FragmentManager;import android.app.SearchManager;import android.content.Intent;import android.content.res.Configuration;import android.os.Bundle;import android.support.v4.view.GravityCompat;import android.view.LayoutInflater;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.ArrayAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.Toast;public class MainActivity extends Activity {     private DrawerLayoutPlus mDrawerLayout;     private ListView mLeftDrawerList, mRightDrawerList;     private ActionBarDrawerToggle mDrawerToggle;     private CharSequence mDrawerTitle;     private CharSequence mTitle;     private String[] mPlanetTitles;     @Override     protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          mTitle = mDrawerTitle = getTitle();          mPlanetTitles = getResources().getStringArray(R.array.planets_array);          mDrawerLayout = (DrawerLayoutPlus) findViewById(R.id.drawer_layout);          mLeftDrawerList = (ListView) findViewById(R.id.left_drawer);          mRightDrawerList = (ListView) findViewById(R.id.right_drawer);          // set a custom shadow that overlays the main content when the drawer          // opens          mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);          // set up the drawer's list view with items and click listener          mLeftDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles));          mLeftDrawerList.setOnItemClickListener(new DrawerItemClickListener());          mRightDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles));          mRightDrawerList.setOnItemClickListener(new DrawerItemClickListener());          // enable ActionBar app icon to behave as action to toggle nav drawer          getActionBar().setDisplayHomeAsUpEnabled(true);          getActionBar().setHomeButtonEnabled(true);          // ActionBarDrawerToggle ties together the the proper interactions          // between the sliding drawer and the action bar app icon          mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */          mDrawerLayout, /* DrawerLayout object */          R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */          R.string.drawer_open, /* "open drawer" description for accessibility */          R.string.drawer_close /* "close drawer" description for accessibility */          ) {               public void onDrawerClosed(View view) {                    getActionBar().setTitle(mTitle);                    invalidateOptionsMenu(); // creates call to                                                       // onPrepareOptionsMenu()               }               public void onDrawerOpened(View drawerView) {                    getActionBar().setTitle(mDrawerTitle);                    invalidateOptionsMenu(); // creates call to                                                       // onPrepareOptionsMenu()               }          };          mDrawerLayout.setDrawerListener(mDrawerToggle);          if (savedInstanceState == null) {               selectItem(0);          }     }     @Override     public boolean onCreateOptionsMenu(Menu menu) {          MenuInflater inflater = getMenuInflater();          inflater.inflate(R.menu.main, menu);          return super.onCreateOptionsMenu(menu);     }     /* Called whenever we call invalidateOptionsMenu() */     @Override     public boolean onPrepareOptionsMenu(Menu menu) {          // If the nav drawer is open, hide action items related to the content          // view          boolean drawerLeftOpen = mDrawerLayout.isDrawerOpen(mLeftDrawerList);          boolean drawerRightOpen = mDrawerLayout.isDrawerOpen(mRightDrawerList);          menu.findItem(R.id.action_websearch).setVisible(!(drawerLeftOpen && drawerRightOpen));          return super.onPrepareOptionsMenu(menu);     }     @Override     public boolean onOptionsItemSelected(MenuItem item) {          // The action bar home/up action should open or close the drawer.          // ActionBarDrawerToggle will take care of this.          if (mDrawerToggle.onOptionsItemSelected(item)) {               return true;          }          // Handle action buttons          switch (item.getItemId()) {          case R.id.action_websearch:               // create intent to perform web search for this planet               Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);               intent.putExtra(SearchManager.QUERY, getActionBar().getTitle());               // catch event that there's no activity to handle intent               if (intent.resolveActivity(getPackageManager()) != null) {                    startActivity(intent);               } else {                    Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();               }               return true;          default:               return super.onOptionsItemSelected(item);          }     }     /* The click listner for ListView in the navigation drawer */     private class DrawerItemClickListener implements ListView.OnItemClickListener {          @Override          public void onItemClick(AdapterView<?> parent, View view, int position, long id) {               selectItem(position);          }     }     private void selectItem(int position) {          // update the main content by replacing fragments          Fragment fragment = new PlanetFragment();          Bundle args = new Bundle();          args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);          fragment.setArguments(args);          FragmentManager fragmentManager = getFragmentManager();          fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();          // update selected item and title, then close the drawer          mLeftDrawerList.setItemChecked(position, true);          mRightDrawerList.setItemChecked(position, true);          setTitle(mPlanetTitles[position]);          mDrawerLayout.closeDrawer(mLeftDrawerList);          mDrawerLayout.closeDrawer(mRightDrawerList);     }     @Override     public void setTitle(CharSequence title) {          mTitle = title;          getActionBar().setTitle(mTitle);     }     /**     * When using the ActionBarDrawerToggle, you must call it during     * onPostCreate() and onConfigurationChanged()...     */     @Override     protected void onPostCreate(Bundle savedInstanceState) {          super.onPostCreate(savedInstanceState);          // Sync the toggle state after onRestoreInstanceState has occurred.          mDrawerToggle.syncState();     }     @Override     public void onConfigurationChanged(Configuration newConfig) {          super.onConfigurationChanged(newConfig);          // Pass any configuration change to the drawer toggls          mDrawerToggle.onConfigurationChanged(newConfig);     }     /**     * Fragment that appears in the "content_frame", shows a planet     */     public static class PlanetFragment extends Fragment {          public static final String ARG_PLANET_NUMBER = "planet_number";          public PlanetFragment() {               // Empty constructor required for fragment subclasses          }          @Override          public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {               View rootView = inflater.inflate(R.layout.fragment_planet, container, false);               int i = getArguments().getInt(ARG_PLANET_NUMBER);               String planet = getResources().getStringArray(R.array.planets_array)[i];               int imageId = getResources().getIdentifier(planet.toLowerCase(Locale.getDefault()), "drawable",                         getActivity().getPackageName());               ((ImageView) rootView.findViewById(R.id.image)).setImageResource(imageId);               getActivity().setTitle(planet);               return rootView;          }     }}
效果:
圖-1  如果換一個需求,是從下面快顯功能表,那是用系統的DrawerLayout是做不到的,不過可以通過修改其源碼來達到目的。
二、分析    DrawerLayout作為一個父類容器,可以包含子View,而子View又可以分為內容地區和菜單地區。內容地區佔據整個螢幕大小,菜單地區預設在螢幕內側。DrawerLayout繼承於ViewGroup,必然需要重寫onLayout()方法,那麼在設定子View位置的時候,內容地區預設直接顯示出來,菜單地區預設隱藏在螢幕內側。DrawerLayout的onLayout()代碼如下:
@Override     protected void onLayout(boolean changed, int l, int t, int r, int b) {           mInLayout = true ;           final int width = r - l;           final int childCount = getChildCount();           for (int i = 0; i < childCount; i++) {               final View child = getChildAt(i);               if (child.getVisibility() == GONE) {                    continue;              }               final LayoutParams lp = (LayoutParams) child.getLayoutParams();               if (isContentView(child)) {                   child.layout(lp. leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(),                             lp. topMargin + child.getMeasuredHeight());              } else { // Drawer, if it wasn't onMeasure would have thrown an                              // exception.                    final int childWidth = child.getMeasuredWidth();                    final int childHeight = child.getMeasuredHeight();                    int childLeft;                    final float newOffset;                    if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {                        childLeft = -childWidth + ( int) (childWidth * lp.onScreen);                        newOffset = ( float) (childWidth + childLeft) / childWidth;                   } else { // Right; onMeasure checked for us.                        childLeft = width - ( int) (childWidth * lp.onScreen);                        newOffset = ( float) (width - childLeft) / childWidth;                   }                                   final boolean changeOffset = newOffset != lp.onScreen;                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK ;                    switch (vgrav) {                    default:                    case Gravity.TOP : {                        child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight);                         break;                   }                    case Gravity.BOTTOM : {                         final int height = b - t;                        child.layout(childLeft, height - lp.bottomMargin - child.getMeasuredHeight(), childLeft + childWidth,                                  height - lp.bottomMargin);                         break;                   }                    case Gravity.CENTER_VERTICAL : {                         final int height = b - t;                         int childTop = (height - childHeight) / 2;                         // Offset for margins. If things don't fit right because of                         // bad measurement before, oh well.                         if (childTop < lp.topMargin ) {                             childTop = lp. topMargin;                        } else if (childTop + childHeight > height - lp.bottomMargin ) {                             childTop = height - lp.bottomMargin - childHeight;                        }                        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);                         break;                   }                   }                    if (changeOffset) {                        setDrawerViewOffset(child, newOffset);                   }                    final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE ;                    if (child.getVisibility() != newVisibility) {                        child.setVisibility(newVisibility);                   }              }          }           mInLayout = false ;           mFirstLayout = false ;     }
  在onLayout方法中可以通過參數擷取之前測量DrawerLayout的寬度和高度,然後擷取DrawerLayout裡面子View的個數,通過一個for迴圈來設定子View的位置。可以看到先是設定內容地區的位置,然後是菜單地區,後者通過設定offset來達到隱藏菜單的目的。所以這個地方是關鍵,如果想要在螢幕下方快顯功能表,那麼就需要修改菜單的顯示位置,讓它置於螢幕下方。  菜單View位置更改好,接著是手勢操作的部分。在DrawerLayout中,是通過ViewDragHelper這個類來實現View的拖拽。首先它聲明了左右菜單的協助類和回調,  
 private final ViewDragHelper mLeftDragger ;      private final ViewDragHelper mRightDragger ;      private final ViewDragCallback mLeftCallback ;      private final ViewDragCallback mRightCallback ;

 以及左右鎖和陰影,
 private int mLockModeLeft ;      private int mLockModeRight ;      private Drawable mShadowLeft ;      private Drawable mShadowRight ;
 在構造方法裡面初始化了協助類和回調,
  mLeftCallback = new ViewDragCallback(Gravity.LEFT );           mRightCallback = new ViewDragCallback(Gravity.RIGHT );           mLeftDragger = ViewDragHelper.create( this , TOUCH_SLOP_SENSITIVITY , mLeftCallback );           mLeftDragger .setEdgeTrackingEnabled(ViewDragHelper. EDGE_LEFT);           mLeftDragger .setMinVelocity(minVel);           mLeftCallback .setDragger(mLeftDragger );           mRightDragger = ViewDragHelper.create( this , TOUCH_SLOP_SENSITIVITY , mRightCallback );           mRightDragger .setEdgeTrackingEnabled(ViewDragHelper. EDGE_RIGHT);           mRightDragger .setMinVelocity(minVel);           mRightCallback .setDragger(mRightDragger );
 在設定陰影製作效果,進行一個左右的判斷
 public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) {           /*           * TODO Someone someday might want to set more complex drawables here.           * They're probably nuts, but we might want to consider registering           * callbacks, setting states, etc. properly.           */           final int absGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection( this));           if ((absGravity & Gravity. LEFT) == Gravity. LEFT) {               mShadowLeft = shadowDrawable;              invalidate();          }           if ((absGravity & Gravity. RIGHT) == Gravity.RIGHT ) {               mShadowRight = shadowDrawable;              invalidate();          }     }
  還有菜單鎖,
  public void setDrawerLockMode(@LockMode int lockMode) {          setDrawerLockMode(lockMode, Gravity. LEFT );          setDrawerLockMode(lockMode, Gravity. RIGHT );     }
  也是進行左右的判斷,
public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) {           final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, ViewCompat.getLayoutDirection( this));           if (absGravity == Gravity. LEFT) {               mLockModeLeft = lockMode;          } else if (absGravity == Gravity. RIGHT) {               mLockModeRight = lockMode;          }           if (lockMode != LOCK_MODE_UNLOCKED) {               // Cancel interaction in progress               final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger ;              helper.cancel();          }           switch (lockMode) {           case LOCK_MODE_LOCKED_OPEN :               final View toOpen = findDrawerWithGravity(absGravity);               if (toOpen != null) {                   openDrawer(toOpen);              }               break ;           case LOCK_MODE_LOCKED_CLOSED :               final View toClose = findDrawerWithGravity(absGravity);               if (toClose != null) {                   closeDrawer(toClose);              }               break ;           // default: do nothing          }     }
  不光是上面的方法,DrawerLayout中還有很多方法也都是這樣。都是對Gravity的判斷,判斷是左側菜單還是右側,所以如果要改成從底部快顯功能表,那麼把相應的值替換為Gravity.BOTTOM即可。當然還要注意在替換的過程中的一些邏輯問題,以免有紕漏。比如offset之前是左右,現在要改成上下,因此數值要重新計算。還有變數的命名等,之前是left或者right,現在改為bottom等等。  修改後運行效果如下:







聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.