本篇博客主要记录一下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);
}
}
});
}
}