Design Support Library(支撑Material Design)

Posted ZhangJianIsAStark

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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具有两种属性,fixedscrollable -->
            <!--当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);
                
            
        );
    

以上是关于Design Support Library(支撑Material Design)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用android design support library

Android Design Support Library概览

Android Design Support Library使用详解

Material Design 开发利器:Android Design Support Library 介绍

Android Design Support Library使用详解

Android Design Support Library- TextInputLayout的使用