Android自定義側滑抽屜選單
預覽圖

介面佈局
傳統的抽屜選單一般採取DrawerLayout+Toolbar+NavigationView的佈局組合。而為了使選單欄介面設計更加自由,這兒使用自定義佈局來代替NavigationView。在設計佈局之前,先在styles.xml中將“DarkActionBar”改為“NoActionBar”,刪去標題欄,然後待會我們用Toolbar替代。
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
接著開始寫我們的主介面,採取Material Design的DrawerLayout佈局。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white"> <android.support.v7.widget.CardView android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="match_parent" app:cardCornerRadius="0dp" app:cardElevation="20dp"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:fitsSystemWindows="true" android:layout_width="match_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintTop_toBottomOf="@+id/toolbar"/> </android.support.constraint.ConstraintLayout> </android.support.v7.widget.CardView> <fragment android:id="@+id/nav_view" android:name="com.example.yc.androidsrc.MenuFragment" android:layout_gravity="start" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.DrawerLayout>
DrawerLayout佈局下的 第一個子檢視 表示主內容檢視,我將其巢狀在一個CardView裡面,主要是為了當拉開選單時,主內容檢視的邊緣能呈現出卡片式的圓角和陰影。主內容檢視主要包括Toolbar和FrameLayout,其中FrameLayout用以動態載入各個選單選項所對應的fragment。
DrawerLayout佈局下的 第二個子檢視 表示選單欄檢視,我們使用靜態載入fragment的方式來呈現介面內容,其中 android:layout_gravity="start"
表示抽屜選單是從左邊拉開,而 end
則表示右邊。
選單佈局不詳說了。不過選單選項的ListView Item佈局左邊有個設定為透明的View,當該項被選中時才顯示為黑色,充當“視覺標籤”的作用。
選單側滑時的監聽事件
我們主要實現的效果是:當滑動選單時,主檢視逐漸縮小,移至介面右邊並呈現出卡片式的圓角與陰影,選單欄檢視逐漸放大並完整呈現在介面左邊。
因此我們需要監聽DrawerLayout的滑動事件,程式碼如下:
mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(View drawerView, float slideOffset) { View mContent = mDrawerLayout.getChildAt(0); View mMenu = drawerView; float scale = 1 - slideOffset; float rightScale = 0.8f + scale * 0.2f; float leftScale = 0.5f + slideOffset * 0.5f; mMenu.setAlpha(leftScale); mMenu.setScaleX(leftScale); mMenu.setScaleY(leftScale); mContent.setPivotX(0); mContent.setPivotY(mContent.getHeight() * 1/2); mContent.setScaleX(rightScale); mContent.setScaleY(rightScale); mContent.setTranslationX(mMenu.getWidth() * slideOffset); } @Override public void onDrawerOpened(View drawerView) { cardView.setRadius(20); } @Override public void onDrawerClosed(View drawerView) { cardView.setRadius(0); } @Override public void onDrawerStateChanged(int newState) { } });
我們來分析一下:其中引數slideOffset代表此時選單欄的滑動比例,數值為0~1。而在這個過程中,我希望主內容檢視的尺寸從原本的1縮放至最後的0.8,因此我們可以得到一個很簡單的線性函式,即主內容檢視的尺寸Scale=0.8+(1-slideOffset)x0.2。同樣的,我設定在這個過程中,選單欄的尺寸由0.5擴大到1,因此它的尺寸線性函式為0.5+slideOffset*0.5。而此時主內容檢視的右移尺寸剛好對應為選單欄滑出來的寬度,因此設定為mContent.setTranslationX(mMenu.getWidth()xslideOffset)。另外關於主內容檢視要從以哪個座標點為中心進行縮放,可通過setPivot進行設定,我覺得從左邊界的中間點進行縮放,效果會比較好看些。當然,以上這些資料你都可以自由設計,不同的資料得到的介面動態效果各有千秋。
主要的程式碼如下:
MainActivity.java
public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; private FrameLayout contentFrameLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerLayout.setScrimColor(Color.TRANSPARENT); // 選單滑動時content不被陰影覆蓋 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(""); // 不顯示程式應用名 toolbar.setNavigationIcon(R.drawable.ic_menu_black_24dp); // 在toolbar最左邊新增icon toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mDrawerLayout.openDrawer(GravityCompat.START); } }); final CardView cardView = (CardView) findViewById(R.id.card_view); contentFrameLayout = (FrameLayout) findViewById(R.id.content_view); replaceFragment(new TabFragment1()); // 監聽抽屜的滑動事件 mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(View drawerView, float slideOffset) { View mContent = mDrawerLayout.getChildAt(0); View mMenu = drawerView; float scale = 1 - slideOffset; float rightScale = 0.8f + scale * 0.2f; float leftScale = 0.5f + slideOffset * 0.5f; mMenu.setAlpha(leftScale); mMenu.setScaleX(leftScale); mMenu.setScaleY(leftScale); mContent.setPivotX(0); mContent.setPivotY(mContent.getHeight() * 1/2); mContent.setScaleX(rightScale); mContent.setScaleY(rightScale); mContent.setTranslationX(mMenu.getWidth() * slideOffset); } @Override public void onDrawerOpened(View drawerView) { cardView.setRadius(20); // 拉開選單時,主內容檢視的邊緣能呈現出卡片式的圓角和陰影 } @Override public void onDrawerClosed(View drawerView) { cardView.setRadius(0); // 選單關閉,圓角消失 } @Override public void onDrawerStateChanged(int newState) { } }); } public void replaceFragment(Fragment fragment) { // 動態載入fragment FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.content_view, fragment); transaction.addToBackStack(null); transaction.commit(); } }
至於選單欄檢視,主要是設定好每個選項的點選事件。這裡我通過getActivity()強行獲得MainActivity物件,然後再直接通過上面寫好的replaceFragment()函式去動態載入對應的fragment,將所要的不同功能檢視呈現在主檢視中。
MenuFragment
public class MenuFragment extends Fragment { private ListView mListView; private List<MenuItem> menuItemList = new ArrayList<>(); private MenuItemAdapter adapter; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View navView = inflater.inflate(R.layout.activity_menu, container, false); mListView = (ListView) navView.findViewById(R.id.menu_list_view); mListView.setDivider(null); // 去掉分割線 initListView(); clickEvents(); return navView; } public void initListView() { String[] data_zh = getResources().getStringArray(R.array.menu_zh); String[] data_en = getResources().getStringArray(R.array.menu_en); for (int i = 0; i < data_zh.length; i++) { MenuItem menuItem = new MenuItem(data_zh[i], data_en[i]); menuItemList.add(menuItem); } adapter = new MenuItemAdapter(getActivity(), R.layout.menu_list_item, menuItemList); mListView.setAdapter(adapter); } public void clickEvents() { mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { adapter.changeSelected(position); // 先定義好樣式如何改變 MainActivity activity = (MainActivity) getActivity(); DrawerLayout mDrawerLayout = (DrawerLayout) activity.findViewById(R.id.drawer_layout); mDrawerLayout.closeDrawer(Gravity.START); switch (position) { case 0: activity.replaceFragment(new TabFragment1()); break; case 1: activity.replaceFragment(new TabFragment2()); break; case 2: activity.replaceFragment(new TabFragment3()); break; case 3: activity.replaceFragment(new TabFragment4()); break; case 4: activity.replaceFragment(new TabFragment5()); break; default: break; } } }); } }
沉浸式狀態列
可以看到效果圖上的頂部狀態列為沉浸式,背景色與Toolbar一致並且字型為黑色(一般為白色)。
首先,應該明白各個版本的區別:要修改狀態列,Android版本至少要在4.4以上,並且在4.4是不能讓狀態列透明的,只能達到一種半透明的陰影背景,而在5.x的版本中,是可以修改背景顏色但無法修改字型顏色的,只有在6.0以上是可以隨意修改的。但是在魅族和小米第三方ROM在4.4版本以上的手機都提供了修改的介面。
這裡就不詳細展開了,具體怎麼做,我覺得這兩篇部落格總結得比較好,放上地址: