Design Support Library(支撑Material Design)

时间:2021-04-05 12:53:45

本篇博客主要记录一下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的使用还是比较简单的。
我们来看看实际的效果:
Design Support Library(支撑Material Design)
可以看到,EditText的hint字段显示在左上角了。
而且右下角显示了EditText当前的输入长度及最大允许的长度。

Design Support Library(支撑Material Design)
如上图所示,当输入长度超过要求时,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;
        }
    }
}

最后的实现效果类似于:
Design Support Library(支撑Material Design)

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;
                        }
                    }
            );
        }
    }
}

具体的使用效果类似于:
Design Support Library(支撑Material Design)

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,点击视图后的效果类似于:
Design Support Library(支撑Material Design)
从图上可以看出,Snackbar会遮挡住按键。

如果使用CoordinatorLayout,点击视图后的效果类似于:
Design Support Library(支撑Material Design)
从图上可以看出,当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();
                    }
                }
            }
        });
    }
}

实际的运行效果类似于:
初始状态:
Design Support Library(支撑Material Design)

移动过程中,AppBarLayout逐渐收缩:
Design Support Library(支撑Material Design)

当AppBarLayout最终消失时,我们的代码加载了ActionBar。
Design Support Library(支撑Material Design)

例子比较简单,实际的使用就要参考具体的需求了。

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>

Design Support Library(支撑Material Design)

如上图所示,蓝色框对应的就是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);
                }
            }
        });
    }
}