现在,越来越多的项目明确要求有 Material Design 意识。一套好的模板可以帮助我们减少开发难度和时间,有更多的时间去解决客户提出的难点。学会这一套示例代码,对你很有帮助!
本示例项目完全遵循 Material Design 思想。方便开发者,也可以让初学者很容易就上手!
示例 Gif :
- 项目遵循:不使用第三方库,所有导入的库都是安卓原生的!
- 项目使用:一个 Activity + 多个 Fragment 的模式。底部导航栏使用 BottomNavigationView ,而非 LinearLayout + TextView 或 Tab 切换等。
- 项目实现:主题切换。
导入 Material Design 所需的原生库:
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:preference-v14:25.3.1'
从布局文件说起吧,Material Design 样式,而非简单的 LinearLayout 或 RelativeLayout
主页面布局代码采用 DrawerLayout + CoordinatorLayout + AppBarLayout + Toolbar + FrameLayout
我将这部分分成几个小部分进行书写,目的是每一块都清晰,也不会导致因为控件元素过多导致布局代码很长,不宜读,不宜管理。
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity"
tools:openDrawer="start">
<include
layout="@layout/activity_main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/layout_background"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:itemIconTint="@drawable/nav_item_color"
app:itemTextColor="@drawable/nav_item_color"
app:menu="@menu/menu_main_drawer" />
</android.support.v4.widget.DrawerLayout>
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/activity_main_content_layout" />
</android.support.design.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/layout_background"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main_content" />
细心的童鞋会发现,我在主页面布局里面设置了 NavigationView ,即:侧边栏。
我这里实现的思路是拼接式,即:侧边栏头部 + 侧边栏内容(Menu实现)
侧边栏头部:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@color/colorPrimary"
android:gravity="bottom"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark" />
侧边栏内容:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group
android:id="@+id/main_group"
android:checkableBehavior="single">
<item
android:id="@+id/nav_home"
android:icon="@mipmap/ic_launcher_round"
android:title="Home" />
<item
android:id="@+id/nav_page_two"
android:icon="@mipmap/ic_launcher_round"
android:title="Page Two" />
<item
android:id="@+id/nav_page_three"
android:icon="@mipmap/ic_launcher_round"
android:title="Page Three" />
</group>
<group
android:id="@+id/second_group"
android:checkableBehavior="none">
<item
android:id="@+id/nav_switch_theme"
android:icon="@mipmap/ic_launcher_round"
android:title="Switch theme" />
<item
android:id="@+id/nav_settings"
android:icon="@mipmap/ic_launcher_round"
android:title="Settings" />
<item
android:id="@+id/nav_about"
android:icon="@mipmap/ic_launcher_round"
android:title="About" />
</group>
</menu>
布局代码就讲解到这里,如果还有不明白的地方,请在评论栏留言,谢谢。
主类(核心代码):
MainActivity 继承 AppCompatActivity , 实现接口 OnNavigationItemSelectedListener 。
- 在 onCreate() 里面
① 首先要初始化控件:
private void initViews() {
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this,
mDrawerLayout,
mToolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
);
mDrawerLayout.addDrawerListener(toggle);
toggle.syncState();
mNavigationView = (NavigationView) findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
}
② 初始化 Fragment , 这里我们创建三个简单的 Fragment 。
if (savedInstanceState != null) {
mHomeFragment =
(HomeFragment) getSupportFragmentManager().
getFragment(savedInstanceState, "HomeFragment");
mPageTwoFragment =
(PageTwoFragment) getSupportFragmentManager().
getFragment(savedInstanceState, "PageTwoFragment");
mPageThreeFragment =
(PageThreeFragment) getSupportFragmentManager().
getFragment(savedInstanceState, "PageThreeFragment");
selectedNavItem = savedInstanceState.getInt(CURRENT_NAV_ITEM);
} else {
mHomeFragment =
(HomeFragment) getSupportFragmentManager().
findFragmentById(R.id.content_main);
if (mHomeFragment == null) {
mHomeFragment = HomeFragment.newInstance();
}
mPageTwoFragment =
(PageTwoFragment) getSupportFragmentManager().
findFragmentById(R.id.content_main);
if (mPageTwoFragment == null) {
mPageTwoFragment = PageTwoFragment.newInstance();
}
mPageThreeFragment =
(PageThreeFragment) getSupportFragmentManager().
findFragmentById(R.id.content_main);
if (mPageThreeFragment == null) {
mPageThreeFragment = PageThreeFragment.newInstance();
}
}
③ 显示需要显示的 Fragment 。
if (!mHomeFragment.isAdded()) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.content_main, mHomeFragment, "HomeFragment")
.commit();
}
if (!mPageTwoFragment.isAdded()) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.content_main, mPageTwoFragment, "PageTwoFragment")
.commit();
}
if (!mPageThreeFragment.isAdded()) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.content_main, mPageThreeFragment, "PageThreeFragment")
.commit();
}
④ 显示默认的 Fragment 。
if (selectedNavItem == 0) {
showHomeFragment();
} else if (selectedNavItem == 1) {
showPageTwoFragment();
} else if (selectedNavItem == 2) {
showPageThreeFragment();
}
- 控件都初始化完毕之后,我们来设置侧边栏
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {}
上面的代码是从接口 OnNavigationItemSelectedListener 里拿到的,但是我看到网上有很多人问这段代码怎么来的,回答的五花八门,我觉得统统是误导!
在方法里面进行侧边栏的设置,我们的侧边栏有 Home 、 Page Two 、 Page Three 、 Switch Theme 、 Settings 、About 六个部分。
所以代码实现:
int id = item.getItemId();
if (id == R.id.nav_home) {
showHomeFragment();
} else if (id == R.id.nav_page_two) {
showPageTwoFragment();
} else if (id == R.id.nav_page_three) {
showPageThreeFragment();
} else if (id == R.id.nav_switch_theme) {
mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
}
@Override
public void onDrawerOpened(View drawerView) {
}
@Override
public void onDrawerClosed(View drawerView) {
}
@Override
public void onDrawerStateChanged(int newState) {
}
});
} else if (id == R.id.nav_settings) {
Intent intent = new Intent(MainActivity.this, PrefsActivity.class);
intent.putExtra(PrefsActivity.EXTRA_FLAG, PrefsActivity.FLAG_SETTINGS);
startActivity(intent);
} else if (id == R.id.nav_about) {
Intent intent = new Intent(MainActivity.this, PrefsActivity.class);
intent.putExtra(PrefsActivity.EXTRA_FLAG, PrefsActivity.FLAG_ABOUT);
startActivity(intent);
}
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerLayout.closeDrawer(GravityCompat.START);
return true;
- 显示所要显示的 Fragment
以一个 Fragment 的显示为例,其他的大同小异:
private void showHomeFragment() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.show(mHomeFragment);
fragmentTransaction.hide(mPageTwoFragment);
fragmentTransaction.hide(mPageThreeFragment);
fragmentTransaction.commit();
mToolbar.setTitle(R.string.app_name);
mNavigationView.setCheckedItem(R.id.nav_home);
}
主题切换
自定义 Applciation 继承 Application
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
if (PreferenceManager.getDefaultSharedPreferences(
getApplicationContext()).getBoolean(Utils.KEY_NIGHT_MODE, false)) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
}
并在 onDrawerClosed() 里面实现切换主题并存储到 SharedPreferences 操作。
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
if ((getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES) {
sp.edit().putBoolean(Utils.KEY_NIGHT_MODE, false).apply();
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
} else {
sp.edit().putBoolean(Utils.KEY_NIGHT_MODE, true).apply();
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}
getWindow().setWindowAnimations(R.style.WindowAnimationFadeInOut);
recreate();
当然好的 Material Design 离不开 style 的设置:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowContentTransitions">true</item>
<item name="android:windowAllowEnterTransitionOverlap">false</item>
<item name="android:windowAllowReturnTransitionOverlap">false</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="preferenceTheme">@style/MaterialPreferenceTheme</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
<style name="MaterialPreferenceTheme" parent="PreferenceThemeOverlay.v14.Material">
<item name="android:fadeScrollbars">true</item>
<item name="android:scrollbars">vertical</item>
<item name="android:scrollbarFadeDuration">1</item>
<item name="android:background">@color/layout_background</item>
<item name="android:textColorPrimary">@color/colorPrimaryText</item>
<item name="android:textColorSecondary">@color/colorSecondaryText</item>
<item name="android:colorAccent">@color/colorAccent</item>
</style>
<style name="WindowAnimationFadeInOut">
<item name="android:windowEnterAnimation">@anim/fade_in</item>
<item name="android:windowExitAnimation">@anim/fade_out</item>
</style>
</resources>
整体的思想都在代码中体现出来了。如果想进一步学习,研究每一处怎么使用,欢迎下载示例代码学习。示例代码已上传至 GitHub ,欢迎 Star 、Fork !如有不明确的地方,或是我写的你认为有更好的解决办法的地方,欢迎在评论区留言,或者到 GitHub 上留言。开源的世界,共同进步!
网友评论