Design Support Library(支撐Material Design)
本篇部落格主要記錄一下Design Support Library中控制元件的基本使用方式。
Design Support Library是一個相容函式庫,使得開發者可以
在Android 2.1及以上的裝置中實現Material Design的效果。
在使用Design Support Library之前,
需要在工程的build.gradle中新增類似如下依賴:
dependencies {
.........
implementation 'com.android.support:design:27.1.0'
}
接下來,我們就來記錄下Design Support Library中控制元件的用法。
1 Snackbar
Snackbar是帶有動畫效果的快速提示欄,顯示在螢幕的底部,主要用來替代Toast。
與Toast不同的是,Snackbar顯示時,使用者可以點選Snackbar執行相應的操作。
與Toast相似的是,如果使用者沒有任何操作,Snackbar到達指定時間之後就會自動消失。
Snackbar的使用方式類似於:
//使用Snackbar時,必須要指定依附的view
//Snackbar會根據傳入的view,找到parent view
//即使不傳入layout對應的id,最終還是能夠顯示在螢幕底部
View rootView = findViewById(R.id.rootView);
//第二個引數為Snackbar文字欄位
//這裡可以指定時長為LENGTH_INDEFINITE,於是只要不被點選,Snackbar就不會消失
Snackbar.make(rootView, "We just do a test", Snackbar.LENGTH_INDEFINITE)
//"Click"為按鍵對應的文字欄位
//此處,當點選按鍵時就會顯示一個Toast
.setAction("Click", new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this ,
"You click the snack bar", Toast.LENGTH_SHORT).show();
}
}).show();
2 TextInputLayout
TextInputLayout的主要作用是作為EditText的容器,從而為EditText生成浮動的標籤。
此外,它還可以對EditText的輸入進行檢查和提示。
我們可以看看具體的示例:
......................
//TextInputLayout作為EditText的父容器即可
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
//這裡我們讓TextInputLayout監控EditText的輸入長度
app:counterEnabled="true"
app:counterMaxLength="11">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
//決定軟體盤確定鍵對應的圖示
android:imeOptions="actionSearch"
android:inputType="number"
//這裡定義了提示字元
android:hint="Just a test"
android:labelFor="@id/edit_text"/>
</android.support.design.widget.TextInputLayout>
......................
容易看出TextInputLayout的使用還是比較簡單的。
我們來看看實際的效果:
可以看到,EditText的hint欄位顯示在左上角了。
而且右下角顯示了EditText當前的輸入長度及最大允許的長度。
如上圖所示,當輸入長度超過要求時,TextInputLayout還可以變換顏色進行提示。
3 TabLayout
TabLayout一般與ViewPager一起使用。
TabLayout的介面比較多,我們不一一列舉了,
此處僅給出一個使用示例。
主介面XML類似於:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TabLayout
android:id="@+id/tableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!--tabMode具有兩種屬性,fixed和scrollable -->
<!--當Tab數量較少,不足以佈滿整個螢幕時,使用fixed;否則使用scrollable -->
app:tabMode="scrollable"/>
</android.support.v4.view.ViewPager>
</LinearLayout>
主Activity的程式碼如下:
package work.test;
...........
/**
* @author zhangjian
*/
public class MainActivity extends AppCompatActivity {
private static final int MAX_TAB_SIZE = 10;
private List<String> mDataList;
private List<Fragment> mFragmentList;
private int[] mImageRes = {R.mipmap.ic_launcher, R.mipmap.ic_launcher_round};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initFragment();
TabLayout tabLayout = findViewById(R.id.tableLayout);
//初始化ViewPager
ViewPager viewPager = findViewById(R.id.viewPager);
//將Adapter和資料關聯起來
LocalPagerAdapter adapter = new LocalPagerAdapter(this,
getSupportFragmentManager(), mDataList, mFragmentList, mImageRes);
viewPager.setAdapter(adapter);
//關聯TabLayout和ViewPager
tabLayout.setupWithViewPager(viewPager);
for (int i = 0; i < tabLayout.getTabCount(); ++i) {
//獲取每個Tab
TabLayout.Tab tab = tabLayout.getTabAt(i);
if (tab != null) {
//此處,每個Tab使用自定義的view
//需要呼叫setCustomView介面
tab.setCustomView(adapter.getTabView(i));
}
}
}
private void initData() {
mDataList = new ArrayList<>();
for (int i = 0; i < MAX_TAB_SIZE; ++i) {
mDataList.add("Tab: ".concat(String.valueOf(i)));
}
}
private void initFragment() {
mFragmentList = new ArrayList<>();
for (int i = 0; i < mDataList.size(); ++i) {
mFragmentList.add(DataFragment.newInstance(mDataList.get(i)));
}
}
//繼承FragmentStatePagerAdapter
private class LocalPagerAdapter extends FragmentStatePagerAdapter {
private Context mContext;
private List<String> mAdapterData;
private List<Fragment> mAdapterFragment;
private int[] mImageId;
LocalPagerAdapter(Context context, FragmentManager fm, List<String> data,
List<Fragment> fragmentList, int[] imageId) {
super(fm);
mContext = context;
mAdapterData = data;
mAdapterFragment = fragmentList;
mImageId = imageId;
}
@Override
public Fragment getItem(int position) {
return mAdapterFragment.get(position);
}
@Override
public int getCount() {
return (mAdapterData.size() == mAdapterFragment.size()) ? mAdapterData.size() : 0;
}
//若Tab使用自定義的view,那麼getPageTitle返回null
//否則就需要實現該介面,返回需要顯示的字符集
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return null;
}
//這裡就是構造每個Tab對應的View
View getTabView(int position) {
View view = LayoutInflater.from(mContext).inflate(R.layout.tab_layout, null);
TextView textView = view.findViewById(R.id.tab_title);
textView.setText(mDataList.get(position));
ImageView imageView = view.findViewById(R.id.tab_img);
imageView.setImageResource(mImageId[position % (mImageId.length)]);
return view;
}
}
}
最後的實現效果類似於:
4 NavigationView
NavigationView主要用於實現導航抽屜,該View與DrawerLayout配合使用。
我們結合具體的例子,看看NavigationView的使用方法。
主介面的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/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<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:fitsSystemWindows="true"
<!--NavigationView主要由兩部分組成-->
<!--主要包括:頭部檢視和選單檢視-->
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu"/>
</android.support.v4.widget.DrawerLayout>
NavigationView的頭部檢視可以是任意形式的普通檢視,類似於:
<?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="192dp"
android:background="?attr/colorPrimaryDark"
android:paddingStart="16dp"
android:orientation="vertical"
android:theme="@style/ThemeOverlay.AppCompat.Dark"
android:gravity="center|start">
<ImageView
android:id="@+id/testView"
android:layout_width="64dp"
android:layout_height="64dp"
android:scaleType="centerCrop"
android:src="@drawable/animation"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Text View"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>
</LinearLayout>
NavigationView的選單檢視類似於:
<!--容易看出與普通的menu檢視完全一致-->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single" >
<item
android:id="@+id/nav_home"
android:icon="@android:drawable/btn_star"
android:title="Home" />
<item
android:id="@+id/nav_msg"
android:icon="@android:drawable/ic_btn_speak_now"
android:title="Message" />
<item
android:id="@+id/nav_friend"
android:icon="@android:drawable/btn_radio"
android:title="Friend" />
</group>
<item android:title="Sub items">
<menu>
<item
android:icon="@mipmap/ic_launcher"
android:title="Sub item 1" />
<item
android:icon="@mipmap/ic_launcher_round"
android:title="Sub item 2" />
</menu>
</item>
</menu>
NavigationView在程式碼中的使用方式類似於:
/**
* @author zhangjian
*/
public class MainActivity extends AppCompatActivity {
DrawerLayout mDrawerLayout;
//定義一個常規的menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.actionbar_menu, menu);
return super.onCreateOptionsMenu(menu);
}
//點選menu的按鍵後,將DrawerLayout繪製到主介面
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
//未繪製時,才繪製
if (!mDrawerLayout.isDrawerVisible(GravityCompat.START)) {
mDrawerLayout.openDrawer(GravityCompat.START);
}
break;
default:
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDrawerLayout = findViewById(R.id.drawerLayout);
//定義NavigationView的按鍵時,與普通menu一致
NavigationView navigationView = findViewById(R.id.nav_view);
if (navigationView != null) {
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.nav_home:
item.setCheckable(true);
break;
case R.id.nav_friend:
//..........
break;
case R.id.nav_msg:
//..........
break;
default:
}
//點選後可以隱藏
mDrawerLayout.closeDrawers();
return true;
}
}
);
}
}
}
具體的使用效果類似於:
5 FloatingActionButton
FloatingActionButton的使用方式與普通button類似。
對應的XML類似於:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="15dp"
<!--可以指定繪製時和點選後的陰影-->
app:elevation="6dp"
app:pressedTranslationZ="20dp" />
</FrameLayout>
具體的使用方式與button一致:
/**
* @author zhangjian
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FloatingActionButton button = findViewById(R.id.fab);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//............
}
});
}
}
6 CoordinatorLayout
CoordinatorLayout的用途是使不同的檢視元件直接相互作用,協調動畫效果。
我們結合具體的例子看看它的用法。
XML的定義類似於:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
app:elevation="6dp"
app:pressedTranslationZ="20dp"
android:layout_gravity="end|bottom"/>
</android.support.design.widget.CoordinatorLayout>
實際使用時的程式碼如下:
/**
* @author zhangjian
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FloatingActionButton button = findViewById(R.id.fab);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Test", Snackbar.LENGTH_INDEFINITE)
.setAction("Remove", new View.OnClickListener() {
@Override
public void onClick(View view) {
//...........
}
}).show();
}
});
}
}
對於前文提及的XML檔案,如果使用普通的ViewGroup時,例如FrameLayout,點選檢視後的效果類似於:
從圖上可以看出,Snackbar會遮擋住按鍵。
如果使用CoordinatorLayout,點選檢視後的效果類似於:
從圖上可以看出,當Snackbar出現時,按鍵會自動移動。
點選Snackbar使之消失時,按鍵會回到原來的位置。
7 CollapsingToolbarLayout
CollapsingToolbarLayout主要用於實現:螢幕內容滑動時,收縮檢視的作用。
CollapsingToolbarLayout主要與CoordinatorLayout、AppBarLayout協同工作。
這裡我們也從一個例子入手,看看基本的使用方法。
XML檔案如下:
<?xml version="1.0" encoding="utf-8"?>
<!--最外層佈局CoordinatorLayout, 協調AppBarLayout與NestedScrollView-->
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--AppBarLayout包裹CollapsingToolbarLayout-->
<android.support.design.widget.AppBarLayout
android:id="@+id/barLayout"
android:layout_width="match_parent"
android:layout_height="400dp">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/p_1"
<!--這裡的scroll表示CollapsingToolbarLayout會隨著螢幕內容上滑收縮 -->
<!--自己使用時感覺,exitUntilCollapsed主要針對Toolbar -->
<!--有該標誌時,隨著滑動收縮,Toolbar最後可以停留在螢幕上,否則將隨CollapsingToolbarLayout消失-->
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/ic_launcher"
<!--有兩種模式,當為parallax時,該檢視在CollapsingToolbarLayout收縮的同時滑動 -->
<!--當為pin時,CollapsingToolbarLayout收縮到該檢視對應的位置時,才會滑動 -->
app:layout_collapseMode="pin"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!--宣告該behavior時,滑動其中的內容,才能觸發AppBarLayout滑動-->
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:id="@+id/scrollText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
在實際的程式碼中,我們可以監聽AppBarLayout的滑動,例如:
/**
* @author zhangjian
*/
public class MainActivity extends AppCompatActivity {
ActionBar mActionBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mActionBar = getSupportActionBar();
if (mActionBar != null) {
mActionBar.hide();
}
AppBarLayout appBarLayout = findViewById(R.id.barLayout);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
//verticalOffset表示AppBarLayout移動的偏移量
//表示未移動
if (verticalOffset == 0) {
if (mActionBar != null) {
mActionBar.hide();
}
//達到最大移動範圍
} else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
if (mActionBar != null) {
mActionBar.show();
}
}
}
});
}
}
實際的執行效果類似於:
初始狀態:
移動過程中,AppBarLayout逐漸收縮:
當AppBarLayout最終消失時,我們的程式碼載入了ActionBar。
例子比較簡單,實際的使用就要參考具體的需求了。
8 BottomSheetBehavior
BottomSheetBehavior主要用於實現底部彈出介面的功能。
它需要配合CoordinatorLayout使用。
我們來看一個具體的例子:
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/test"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
<!--這個表示介面高出螢幕底端的距離,為0則表示整個隱藏 -->
app:behavior_peekHeight="10dp"
<!--指定這個behavior,linear就變成BottomSheet -->
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/large_text" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
如上圖所示,藍色框對應的就是LinearLayout對應的位置。
我們既可以手動上滑BottomSheet,也可以直接用程式碼修改對應的狀態或監聽變化。
示例程式碼類似於:
/**
* @author zhangjian
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View bottomSheet = findViewById(R.id.bottomSheet);
final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
//可以註冊回撥監聽變化
behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
Log.v("ZJTest", "state changed to: " + newState);
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
Log.v("ZJTest", "slideOffset: " + slideOffset);
}
});
//也可以直接修改狀態
Button button = findViewById(R.id.test);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int state = behavior.getState();
if (state == BottomSheetBehavior.STATE_COLLAPSED) {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else if (state == BottomSheetBehavior.STATE_EXPANDED) {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
});
}
}