Material Design入门

作者: uniapp | 来源:发表于2017-08-15 13:45 被阅读129次

    同一个产品经理出品的App, Android端的界面往往比不上iOS端. Android端界面整体色彩渲染的不协调, 一直受广大用户的诟病. Material Design的横空出世, 让Android端的使用者看到了希望--声称设计史上第一次超越了iOS端(作为一位iOS开发者,对此表示呵呵). 下面是Material Design实现的效果.

    Material Design.gif

    侧面抽屉效果的弹出, 以及详情界面顶部toolBar向上滚动时的渐变, 效果有没有很赞? 下面看一下具体实现.

    首先, 需要在app的build.gradle文件中添加Material Design的支持库design, 其余的circleimageview, recyclerview, cardview, glide库, 项目中都会用到.

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:26.+'
        compile 'com.android.support.constraint:constraint-layout:1.0.2'
        compile 'com.android.support:design:26.+'
        compile 'de.hdodenhof:circleimageview:2.1.0'
        compile 'com.android.support:recyclerview-v7:26.+'
        compile 'com.android.support:cardview-v7:26.+'
        compile 'com.github.bumptech.glide:glide:4.0.0'
    
        annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0'
        testCompile 'junit:junit:4.12'
    }
    
    添加Toolbar

    首先是将ActionBar替换成Toolbar,因为Toolbar才具有Materail Design效果. 修改路径app/src/main/res/values下的styles文件.

    <resources>
        //主题是淡色, 陪衬设置成深色;
        <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
            //主题是身色, 陪衬设置成淡色;
            <item name="colorPrimary">@color/colorPrimary</item>
            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
            <item name="colorAccent">@color/colorAccent</item>
        </style>
    
        <style name="FruitActivityTheme" parent="AppTheme"></style>
    </resources>
    

    将样式改为Light.NoActionBar后,顶部的ActionBar就会被去掉. 然后在activity_main布局中添加Toolbar.

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:title="Fruits"
        app:layout_scrollFlags="scroll|enterAlways|snap"
        ></android.support.v7.widget.Toolbar>
    

    完成了以上工作, 就可以在MainActivity中使用了.

    Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    
    ActionBar actionBar = getSupportActionBar();
    if (actionBar != null){
        actionBar.setDisplayHomeAsUpEnabled(true);
        actionBar.setHomeAsUpIndicator(R.drawable.menu);
    }
    

    setDisplayHomeAsUpEnabled方法用来控制Toolbar顶部右边是否显示返回按钮, setHomeAsUpIndicator方法可以给Toolbar设置图片.

    Toobar右边的按钮又是怎么添加的呢? res目录下新建Directory, 命名为menu, 在该文件下新建文件toolbar.

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        >
    
        <item
            android:id="@+id/backup"
            android:title="Backup"
            android:icon="@drawable/left"
            app:showAsAction="always"
            android:visible="true"
            android:enabled="true"/>
        <item
            android:id="@+id/delete"
            android:title="Delete"
            android:icon="@drawable/right"
            app:showAsAction="ifRoom"/>
        <item
            android:id="@+id/settings"
            android:title="settings"
            android:icon="@drawable/left"
            app:showAsAction="never"/>
    </menu>
    

    新建的布局包含了backup, delete, settings三个按钮. 其中showAsAction属性分别设置成always, ifRoom, never. 属性名称已经表明了界面效果.然后在MainActivity中的onCreateOptionsMenu方法中使用.

     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         super.onCreateOptionsMenu(menu);
         getMenuInflater().inflate(R.menu.toolbar, menu);
         return true;
     }
    
    侧拉抽屉效果

    该效果可以通过Material Design中的DrawerLayout实现.

    <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/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
    
        <android.support.design.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.design.widget.AppBarLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="?attr/colorPrimary"
                    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                    android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                    app:title="Fruits"
                    app:layout_scrollFlags="scroll|enterAlways|snap"
                    ></android.support.v7.widget.Toolbar>
            </android.support.design.widget.AppBarLayout>
    
            <android.support.v4.widget.SwipeRefreshLayout
                android:id="@+id/swipe_refresh"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/recycler_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    app:layout_behavior="@string/appbar_scrolling_view_behavior"></android.support.v7.widget.RecyclerView>
            </android.support.v4.widget.SwipeRefreshLayout>
            <android.support.design.widget.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|end"
                android:layout_margin="16dp"
                android:src="@drawable/newmsg"
                android:foregroundTint="#000"/>
    
        </android.support.design.widget.CoordinatorLayout>
    
        <android.support.design.widget.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#abc"
            app:menu="@menu/nav_menu"
            app:headerLayout="@layout/nav_header"
            android:layout_gravity="start"
            >
    
        </android.support.design.widget.NavigationView>
    
    </android.support.v4.widget.DrawerLayout>
    

    DrawerLayout底层封装了一系列手势识别的方法. 使用时内部接收两个控件, 第一个CoordinatorLayout是主页面, 第二个NavigationView是侧滑时出现的页面.

    CoordinatorLayout类似FrameLayout, 其内部的控件都会以父布局的左上角为参照. 不同的是CoordinatorLayout符合Material Design的设计理念, 可以自动识别CoordinatorLayout类型的控件并对它们进行有效调整, 提供更好的用户体验. 上面的例子中, 因为AppBarLayout和FloatingActionButton都是CoordinatorLayout的子类, 可以保证Toolbar不被SwipeRefreshLayout遮挡, FloatingActionButton也不会随着RecyclerView的上下滚动而出现偏移. NavigationView是Material Design抽屉效果中推荐的侧拉界面, 用于显示详情信息. 上面将名称为nav_menu和nav_header的布局文件分别放在了布局中的menu和layout文件夹下, 具体实现如下.

    <?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_call"
                android:icon="@drawable/left"
                android:title="call"/>
            <item
                android:id="@+id/nav_friends"
                android:icon="@drawable/right"
                android:title="friends"/>
            <item
                android:id="@+id/nav_location"
                android:icon="@drawable/left"
                android:title="location"/>
            <item
                android:id="@+id/nav_mail"
                android:icon="@drawable/right"
                android:title="mail"/>
        </group>
    </menu>
    
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent" android:layout_height="180dp"
        android:padding="10dp"
        android:background="?attr/colorPrimary">
    
        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/icon_image"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:src="@drawable/aa"
            android:layout_centerInParent="true"/>
        <TextView
            android:id="@+id/mail"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:text="Dog's mail: dog10@163.com"
            android:textColor="#fff"
            android:textSize="14sp"/>
    
        <TextView
            android:id="@+id/username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@id/mail"
            android:text="Dog Yellow"
            android:textColor="#fff"
            android:textSize="14sp"/>
    </RelativeLayout>
    

    完成以上布局, 就可以在MainActivity中处理相关逻辑了.

    public class MainActivity extends AppCompatActivity {
    
    
        private DrawerLayout drawerLayout;
    
        private SwipeRefreshLayout swipeRefreshLayout;
    
        private Fruit[] fruits = {
                new Fruit("Apple", R.drawable.apple),
                new Fruit("banana", R.drawable.banana),
                new Fruit("berray", R.drawable.berray),
                new Fruit("tomato", R.drawable.tomato),
        };
    
        private List<Fruit> fruitList = new ArrayList<>();
    
        private FruitAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_main);
    
            swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_refresh);
            swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
            swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    refreshFruits();
                }
            });
    
            Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
            NavigationView navigationView = (NavigationView)findViewById(R.id.nav_view);
    
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null){
    
                actionBar.setDisplayHomeAsUpEnabled(true);
                actionBar.setHomeAsUpIndicator(R.drawable.menu);
    
            }
            navigationView.setCheckedItem(R.id.nav_call);
            navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                    drawerLayout.closeDrawers();
                    return true;
                }
            });
    
            FloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab);
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
    //                Toast.makeText(MainActivity.this, "fabs clicked!", Toast.LENGTH_SHORT).show();
                    Snackbar.make(view, "Data deleted", Snackbar.LENGTH_SHORT).setAction("Undo", new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            Toast.makeText(MainActivity.this, "Data restore", Toast.LENGTH_SHORT).show();
                        }
                    }).show();
                }
            });
    
            initFruits();
    
            RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
            GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
            recyclerView.setLayoutManager(layoutManager);
            adapter = new FruitAdapter(fruitList);
            recyclerView.setAdapter(adapter);
        }
    
        private void refreshFruits(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        Thread.sleep(2000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
    
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            initFruits();
                            adapter.notifyDataSetChanged();
                            swipeRefreshLayout.setRefreshing(false);
                        }
                    });
                }
            }).start();
        }
    
        private void initFruits(){
            fruitList.clear();
            for (int i = 0; i < 50; i++){
                Random random = new Random();
                int index = random.nextInt(fruits.length);
                fruitList.add(fruits[index]);
            }
        }
    
        //menu的item最多显示2个,属性设置为never时,被折叠不显示.展开的大小是固定的.
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            super.onCreateOptionsMenu(menu);
            getMenuInflater().inflate(R.menu.toolbar, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()){
                case android.R.id.home:
                    drawerLayout.openDrawer(GravityCompat.START);
                    break;
                case R.id.backup:
                    Toast.makeText(this, "Back up", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.delete:
                    Toast.makeText(this, "Delete", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.settings:
                    Toast.makeText(this, "Setting", Toast.LENGTH_SHORT).show();
                    break;
            }
            return true;
        }
    }
    

    其中Fruit实体类和FruitAdapter是RecyclerView正常显示所需的java文件. 不明白的可以参照:ListView和RecyclerView

    public class Fruit {
        private String name;
    
        private int imageId;
    
        public Fruit(String apple, int imageId) {
            this.name = apple;
            this.imageId = imageId;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getImageId() {
            return imageId;
        }
    
        public void setImageId(int imageId) {
            this.imageId = imageId;
        }
    }
    
    public class FruitAdapter extends RecyclerView.Adapter <FruitAdapter.ViewHolder>{
    
        static class ViewHolder extends RecyclerView.ViewHolder{
    
            CardView cardView;
            ImageView fruitImage;
            TextView fruitName;
    
            public ViewHolder(View view){
                super(view);
    
                cardView = (CardView)view;
                fruitImage = (ImageView)view.findViewById(R.id.fruit_image);
                fruitName = (TextView)view.findViewById(R.id.fruit_name);
            }
        }
    
        private List<Fruit> mFruits;
        private Context mContext;
    
        public FruitAdapter(List<Fruit> fruits){
            mFruits = fruits;
        }
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (mContext == null){
                mContext = parent.getContext();
            }
    
            View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false);
    
            final  ViewHolder holder = new ViewHolder(view);
            holder.cardView.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View view) {
    
                    int position = holder.getAdapterPosition();
                    Fruit fruit = mFruits.get(position);
                    Intent intent = new Intent(mContext, FruitActivity.class);
                    intent.putExtra(FruitActivity.FRUIT_NAME, fruit.getName());
                    intent.putExtra(FruitActivity.FRUIT_IMAGE_ID, fruit.getImageId());
                    mContext.startActivity(intent);
                }
            });
            return holder;
        }
    
        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            Fruit fruit = mFruits.get(position);
            holder.fruitName.setText(fruit.getName());
            Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);
        }
    
        @Override
        public int getItemCount() {
            return mFruits.size();
        }
    }
    

    RecycleView中的单元控件使用了Material Design中的CardView, 包含ImageView和TextView两个控件.

    <?xml version="1.0" encoding="utf-8"?>
    
    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
    
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_margin="5dp"
        app:cardCornerRadius="4dp">
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <ImageView
                android:id="@+id/fruit_image"
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:scaleType="centerCrop"/>
    
            <TextView
                android:id="@+id/fruit_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_margin="5dp"
                android:textSize="12sp"
                />
        </LinearLayout>
    
    </android.support.v7.widget.CardView>
    

    设置CardView中的Image时使用了Glide, 可以结合设备屏幕分辨率对图片的清晰度自动调整, 保证相对较好的渲染效果. 使用前需要导入相应的库, 具体设置可以参照文章开头.

    Fruit详情页面

    在FruitAdapter中给ViewHolder添加点击方法, 点击CardView的item后, 跳转到FruitActivity页面, 其布局文件如下:

    <?xml version="1.0" encoding="utf-8"?>
    
    <android.support.design.widget.CoordinatorLayout
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:fitsSystemWindows="true">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/appBar"
            android:layout_width="match_parent"
            android:layout_height="250dp"
            android:fitsSystemWindows="true">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed"
                android:fitsSystemWindows="true"
                >
                
                <ImageView
                    android:id="@+id/fruit_image_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    app:layout_collapseMode="parallax"
                    android:fitsSystemWindows="true"/>
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    ></android.support.v7.widget.Toolbar>
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <android.support.v7.widget.CardView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="15dp"
                    android:layout_marginLeft="15dp"
                    android:layout_marginRight="15dp"
                    android:layout_marginTop="35dp"
                    app:cardCornerRadius="4dp"></android.support.v7.widget.CardView>
                
                    <TextView
                        android:id="@+id/fruit_content_text"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_margin="10dp"/>
    
            </LinearLayout>
        </android.support.v4.widget.NestedScrollView>
    
        <android.support.design.widget.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:src="@drawable/left"
            app:layout_anchor="@id/appBar"
            app:layout_anchorGravity="bottom|end"/>
    </android.support.design.widget.CoordinatorLayout>
    

    该页面包含3部分: 上面的AppBarLayout, 下面的NestedScrollView, 以及悬浮在页面的FloatingActionButton. 最后的FloatingActionButton的锚点依托在AppBarLayout的右下部.

    使用CollapsingToolbarLayout可以实现随着ScrollView滚动的效果. 设置为layout_scrollFlags属性为'scroll|exitUntilCollapsed'后, CollapsingToolbarLayout就能根据ScrollView向上滚动的距离来决定内部控件的显示或者隐藏.为了满足Material Design设计, 需要将布局放在AppBarLayout当中.

    Material Design当中允许修改顶部状态栏. 首先将符合CoordinatorLayout的父布局和子布局的fitsSystemWindows属性全部设置成true. 然后修改AppTheme为透明色. 由于Material Design是在Android5.0推出, 所以需要在res文件夹下新建目录values-v21对Android的不同版本进行适配. 在values-v21目录下新建styles.xml文件, 内容如下.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <style name="FruitActivityTheme" parent="AppTheme">
            <item name="android:statusBarColor">@android:color/transparent</item>
        </style>
    </resources>
    

    NestedScrollView是符合Material Design标准的ScrollView, 使用方式也比较类似. 有了布局就可以在FruitActivity添加处理逻辑.

    public class FruitActivity extends AppCompatActivity {
    
    
        public static final String FRUIT_NAME = "fruit_name";
    
        public static final String FRUIT_IMAGE_ID = "fruit_image_id";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_fruit);
    
            Intent intent = getIntent();
            String fruitName = intent.getStringExtra(FRUIT_NAME);
            int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID, 0);
            Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
            CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout)findViewById(R.id.collapsing_toolbar);
            ImageView fruitImageView = (ImageView)findViewById(R.id.fruit_image_view);
            TextView fruitContextText = (TextView)findViewById(R.id.fruit_content_text);
            setSupportActionBar(toolbar);
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null){
                actionBar.setDisplayHomeAsUpEnabled(true);
            }
            collapsingToolbarLayout.setTitle(fruitName);
            Glide.with(this).load(fruitImageId).into(fruitImageView);
            String fruitContent = generateFruitContent(fruitName);
            fruitContextText.setText(fruitContent);
        }
    
        private String generateFruitContent(String fruitName){
            StringBuilder fruitContent = new StringBuilder();
            for (int i = 0; i < 500; i++){
                fruitContent.append(fruitName);
            }
            return fruitContent.toString();
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch(item.getItemId()){
                case android.R.id.home:
                    finish();
                    return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
    
    总结

    Material Design是Android为了增强UI设计效果和提高用户体验而推荐的标准, 背后是统一的设计理念, 为标准繁多的Android界面设计吹来了一股清风. 上面的例子中利用Material Design中推出的组件替换了App的ActionBar为Toolbar,利用DrawerLayout实现了抽屉效果,并且在Fruit的详情页面,利用CollapsingToolbarLayout实现了Toolbar随NestedScrollView滚动而变化. 相信经过以上实践, 能够对Material Design的理解能够更加深入. 也希望在App的开发中能够践行Material Design的设计理念.

    GitHub源码

    喜欢和关注都是对我的支持和鼓励~

    相关文章

      网友评论

        本文标题:Material Design入门

        本文链接:https://www.haomeiwen.com/subject/xxderxtx.html