Androidandroid系统控件Material Design

Android Design Support Library系列

2017-05-09  本文已影响2732人  滴滴滴9527

Google官方在14年 I/O大会上推出了全新的设计语言——Material Design,
Material Design让Android界面在体验上更加鲜艳和简洁,正在逐步成为APP设计的趋势.
而与Material Design相关的是一系列实现Material Design效果的控件库Android Design Support Library,从今天开始将一一介绍他们的用法.
这一系列控件库在使用时都需要在gradle中添加依赖:

compile 'com.android.support:design:25.3.1'

一、TabLayout介绍

官方地址:TabLayout
需要翻墙

TabLayout1 TabLayout2

像上面这种选项卡切换的效果,几乎所有应用中都有涉及到,这种效果实现方式很多,TabHost实现、RadioGroup+RadioButton实现、ViewPager+PagerTabStrip或者种种第三方库(Viewpageindicator...)来实现、HorizontalScrollView+LinearLayout动态实现......
而在Android Design Support Library中就有这样一个控件:TabLayout,继承自HorizontalScrollView,通过TabLayout我们可以很轻松的实现这种效果.

二、TabLayout简单使用

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </android.support.design.widget.TabLayout>
    
</LinearLayout>

Activity中:

public class MainActivity extends AppCompatActivity {

    private TabLayout mTablayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTablayout = (TabLayout) findViewById(R.id.tablayout);
        mTablayout.addTab(mTablayout.newTab().setText("A"));
        mTablayout.addTab(mTablayout.newTab().setText("B"));
        mTablayout.addTab(mTablayout.newTab().setText("C"));
    }
}

效果图:



简单的选项卡效果就实现了,当然,你也可以在布局文件中写死,像下面这样:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="A" />

        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="B" />

        <android.support.design.widget.TabItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="C" />
    </android.support.design.widget.TabLayout>

</LinearLayout>

效果和上面是一样的,不过一般没人这么使用.

三、TabLayout的常用属性

app:tabIndicatorColor=""   指示器颜色
app:tabIndicatorHeight=""  指示器高度,设置为0就是没有指示器
app:tabTextColor=""    Tab文本默认颜色
app:tabSelectedTextColor=""     Tab文本被选中后的颜色
app:tabTextAppearance=""      为Tab文本设置样式,一般是需要为Tab加图标时使用
app:tabMode=""  只有两个值:fixed、scrollable
    fixed用于标题栏少的情况,每个Tab可以平分屏幕宽度
    scrollable用于标题栏多出屏幕的情况,如果标题栏少的时候用很难看,占不满屏幕
app:tabBackground=""    TabLayout背景,和android:background=""效果一样
app:tabGravity=""    对齐方式:  居中显示center、fill填满
app:tabGravity="center"
app:tabGravity="fill"

还有一些很少使用的:

从左边开始偏移距离,tabMode值必须为scrollable才会生效
app:tabContentStart="150dp"
app:tabContentStart
选项卡宽度限制
app:tabMaxWidth=""   最大宽度
app:tabMinWidth=""   最小宽度
Tab内边距
app:tabPaddingStart=""
app:tabPaddingBottom=""
app:tabPaddingEnd=""
app:tabPaddingTop=""

app:tabPadding=""

三、TabLayout方法

Tablayout.newTab()         创建标签
Tablayout.addTab()         添加标签
Tablayout.removeTab()      删除标签
Tablayout.removeTabAt()    通过索引删除标签
Tablayout.removeAllTabs()  删除全部标签
因为TabLayout继承自HorizontalScrollView,所以可以直接添加View
addView ()
添加监听器,取消监听器,清楚所有的监听器
Tablayout.addOnTabSelectedListener()
Tablayout.removeOnTabSelectedListener()
Tablayout.clearOnTabSelectedListeners()
Tablayout.getSelectedTabPosition()    获取当前选中的Tab位置
Tablayout.getTabAt()      根据索引获取Tab
Tablayout.getTabCount()    获取Tab总数
对应tabGravity属性
Tablayout.getTabGravity()
Tablayout.setTabGravity()
对应tabMode属性
Tablayout.getTabMode()
Tablayout.setTabMode()
对应tabTextColor属性
Tablayout.setTabTextColors()
Tablayout.getTabTextColors()
和ViewPager关联,随着ViewPager滑动而滑动
TabLayout和ViewPager配合使用是最常见的运用方式
两者关联后:
1)TabLayout之前创建的Tab并不能正常显示,但可以在关联后通过 getTabAt() 得到标签之后进行修改
但其实没有必要,我们一般由2)和3)决定Tab的数量和内容
2)TabLayout的Tab数量由ViewPager分页数量决定
3)TabLayout的Tab内容由ViewPager的Adapter中 getPagerTitle() 方法返回的内容决定

Tablayout.setupWithViewPager()
TabLayout中的内部类:Tab,表示TabLayout中的每一个标签
Tab的方法
boolean is Selected()   Tab是否被选中
void setSelected()   设置Tab为被选中状态
setText ()    设置Tab文本内容
getText ()    获取Tab文本内容
getIcon ()  获取Tab的图标
setIcon ()  为Tab添加图标
setCustomView()    设置用户自定义的Tab,参数为资源id或者View对象
getPosition() 获取当前位置

四、TabLayout搭配ViewPager使用

1、MainActivity布局文件的两种方式
<?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: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/tablayout"
            app:tabMode="scrollable"
            app:tabBackground="@android:color/holo_blue_dark"
            app:tabIndicatorColor="@android:color/white"
            app:tabTextColor="@android:color/black"
            app:tabSelectedTextColor="@android:color/white"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </android.support.v4.view.ViewPager>

</LinearLayout>
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        app:tabMode="scrollable"
        app:tabBackground="@android:color/holo_blue_dark"
        app:tabIndicatorColor="@android:color/white"
        app:tabTextColor="@android:color/black"
        app:tabSelectedTextColor="@android:color/white"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>


</LinearLayout>
2、MainActivity.java
public class MainActivity extends AppCompatActivity {

    private TabLayout mTablayout;
    private ViewPager mViewPager;
    private List<String> mDatas;
    private List<Fragment> fragments;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();
        initFragments();
        mTablayout = (TabLayout) findViewById(R.id.tablayout);
        mViewPager = (ViewPager) findViewById(R.id.viewpager);
       mViewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), mDatas, fragments));
//        mTablayout.setupWithViewPager(mViewPager);
//注意:我的布局文件中ViewPager和TabLayout 是嵌套的,所以不需要这一步
//          如果没有嵌套,则需要在ViewPager设置Adapter之后加上这一步
//另外,只能是ViewPager嵌套Tablayout,反了会报错的,因为Tablayout中只能嵌套TabItem
    }

    private void initData() {
        mDatas = new ArrayList<>();
        for (int i = 'A'; i < 'I'; i++) {
            mDatas.add("" + (char) i);
        }
    }

    private void initFragments() {
        fragments = new ArrayList<>();
        for (int i = 0; i < mDatas.size(); i++) {
            MyFragment fragment = MyFragment.newInstance(mDatas.get(i));
            fragments.add(fragment);
        }
    }
}
3、MyPagerAdapter.java
public class MyPagerAdapter extends FragmentStatePagerAdapter {
    private List<String> mDatas;
    private List<Fragment> mFragments;

    public MyPagerAdapter(FragmentManager fm, List<String> mDatas, List<Fragment> fragments) {
        super(fm);
        this.mDatas = mDatas;
        this.mFragments = fragments;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mDatas.size()==mFragments.size()?mFragments.size():0;
    }

    /**
     * 重写此方法,返回TabLayout的内容
     */
    @Override
    public CharSequence getPageTitle(int position) {
        return mDatas.get(position);
    }
}
4、MyFragment.java
public class MyFragment extends Fragment {

    public static MyFragment newInstance(String data) {
        Bundle args = new Bundle();
        args.putString("key", data);
        MyFragment fragment = new MyFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment, container, false);
        TextView tv = (TextView) view.findViewById(R.id.tv);
        tv.setText("Fragment------" + getArguments().getString("key"));
        return view;
    }
}
5、Fragment布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        tools:text="别看了,我就是一个TextView" />
</RelativeLayout>
5、效果图

五、TabLayout之自定义Tab布局

我们的Tab上有时候不仅仅只有文字,还会有图标,那么这时候我们的getPageTitle()方法就不应该仅仅返回一个文本了.

方式1:通过SpannableString为Tab添加图标

SpannableString基本上与String差不多,也是用来存储字符串,但它们俩的特殊就在于SpannableString有一个SetSpan()方法,能给这些存储的String添加各种格式或者称样式(Span),将原来的String以不同的样式显示出来,比如在原来String上加下划线、加背景色、改变字体颜色、用图片把指定的文字给替换掉,等等.

MainActivity中的修改
private int[] imageRes = {
            R.mipmap.a,R.mipmap.b, R.mipmap.c,R.mipmap.d,
            R.mipmap.e, R.mipmap.f,R.mipmap.g,R.mipmap.h
};

 mViewPager.setAdapter(new MyPagerAdapter(MainActivity.this,getSupportFragmentManager(), mDatas, fragments,imageRes));

添加了一个装有图片资源的数组,然后通过构造传给MyPagerAdapter,当然,其实标题mDatas和图标资源imageRes你直接声明在MyPagerAdapter中也是可以的.

MyPagerAdapter中的修改
    private int[] imageRes;
    private Context mContext;

    public MyPagerAdapter(Context context, FragmentManager fm, List<String> mDatas, List<Fragment> fragments, int[] imageRes) {
        super(fm);
        this.mContext = context;
        this.mDatas = mDatas;
        this.mFragments = fragments;
        this.imageRes = imageRes;
    }


    /**
     * 通过SpannableString 为Tab添加各种样式,这里就添加了一个图标
     */
    @Override
    public CharSequence getPageTitle(int position) {
        SpannableString sb = new SpannableString("   "+mDatas.get(position));
        Drawable image = mContext.getResources().getDrawable(imageRes[position]);
       image.setBounds(0,0,image.getIntrinsicWidth(), image.getIntrinsicHeight());
        ImageSpan imageSpan = new ImageSpan(image, ImageSpan.ALIGN_BOTTOM);
        sb.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return sb;
    }

运行之后你会发现效果没变,这是因为TabLayout创建的tab默认样式textAllCaps属性为true,这阻止了ImageSpan被渲染出来,可以通过下面的样式文件定义来改变:

在res/values/styles中定义我们自己的样式,把textAllCaps属性设为false
    <style name="MyTabTextAppearance" parent="TextAppearance.Design.Tab">
        <item name="textAllCaps">false</item>
       <!--<item name="android:textSize">20sp</item>-->
    </style>

TabLayout中Tab的文字大小无法在布局文件中更改,也是在这里更改的
在布局文件中为TabLayout引用我们自己的样式
  app:tabTextAppearance="@style/MyTabTextAppearance"
效果图
方式2:通过Tab的setCustomView()方法自定义Tab布局

上面已经介绍了,Tab有一个方法,setCustomView(),参数为一个资源id或者一个View对象,我们通过这个方法来自定义Tab的布局.

1)MainActivity布局文件
<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabBackground="@android:color/holo_blue_dark"
        app:tabIndicatorColor="@android:color/white"
        app:tabMode="scrollable"
        app:tabSelectedTextColor="@android:color/white"
        app:tabTextAppearance="@style/MyTabTextAppearance"
        app:tabTextColor="@android:color/black" />

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>

</LinearLayout>
2)MainActivity.java
public class MainActivity extends AppCompatActivity {

    private TabLayout mTablayout;
    private ViewPager mViewPager;
    private List<String> mDatas;
    private List<Fragment> fragments;
    private MyPagerAdapter mAdapter;
    private int[] imageRes = {
            R.mipmap.a,R.mipmap.b, R.mipmap.c,R.mipmap.d,
            R.mipmap.e, R.mipmap.f,R.mipmap.g,R.mipmap.h
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();
        initFragments();
        mTablayout = (TabLayout) findViewById(R.id.tablayout);
        mViewPager = (ViewPager) findViewById(R.id.viewpager);
        mAdapter = new MyPagerAdapter(MainActivity.this,getSupportFragmentManager(), mDatas, fragments,imageRes);
        mViewPager.setAdapter(mAdapter);
        mTablayout.setupWithViewPager(mViewPager);

        /**
         * 为每一个Tab设置自定义布局
         */
        for (int i = 0; i <mTablayout.getTabCount() ; i++) {
            TabLayout.Tab tab = mTablayout.getTabAt(i);
            tab.setCustomView(mAdapter.getTabView(i));
        }
    }

    private void initData() {
        mDatas = new ArrayList<>();
        for (int i = 'A'; i < 'I'; i++) {
            mDatas.add("" + (char) i);
        }
    }

    private void initFragments() {
        fragments = new ArrayList<>();
        for (int i = 0; i < mDatas.size(); i++) {
            MyFragment fragment = MyFragment.newInstance(mDatas.get(i));
            fragments.add(fragment);
        }
    }
}
3)MyPagerAdapter.java
public class MyPagerAdapter extends FragmentStatePagerAdapter {
    private List<String> mDatas;
    private List<Fragment> mFragments;
    private int[] imageRes;
    private Context mContext;

    public MyPagerAdapter(Context context, FragmentManager fm, List<String> mDatas, List<Fragment> fragments, int[] imageRes) {
        super(fm);
        this.mContext = context;
        this.mDatas = mDatas;
        this.mFragments = fragments;
        this.imageRes = imageRes;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mDatas.size()==mFragments.size()?mFragments.size():0;
    }

    /**
     * 要使用我们自定义的布局,这里返回null
     */
    @Override
    public CharSequence getPageTitle(int position) {
        return null;
    }


    /**
     * 定义一个方法,来返回Tab的内容
     */
    public View getTabView(int position){
       View view = LayoutInflater.from(mContext).inflate(R.layout.tab_layout, null);
        TextView tv= (TextView) view.findViewById(R.id.tv);
        ImageView iv = (ImageView) view.findViewById(R.id.iv);
        tv.setText(mDatas.get(position));
        iv.setImageResource(imageRes[position]);
        return view;
    }
}
4)每一个Tab的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        tools:src="@mipmap/a" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="5dp"
        tools:text="别看了,我就是一个TextView" />
</LinearLayout>

其它的文件没有发生改变

5)效果图

ok, 效果还好,但是文字颜色的选择器效果没了,这种情况我们可以在每一个Tab的布局文件为TextView的textColor设置一个选择器就行了

android:textColor="@drawable/selector_tab_textcolor"
在drawable目录下新建一个选择器selector_tab_textcolor.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/white" android:state_selected="true"/>
    <item android:color="@color/black" />
</selector>

但是图片要实现选择器的效果就不好办了,Tab数量太多,针对每一个tab的图片都需要一个selector....

另外我们也注意到:setCustomView()为Tab设置自定义布局的参数不仅仅可以是View对象,也可以是资源Id.像下面这样:

        int[] tabs = {R.layout.tab_one,
                    R.layout.tab_two,
                    R.layout.tab_three,
                    R.layout.tab_four};//有多少tab,就添加多少tab的布局文件

        for (int i = 0; i < mTablayout.getTabCount(); i++) {
            mTabLayout.getTabAt(i).setCustomView(tabs[i]);
        }

这种做法适合选项卡比较少而且固定的情况.

六、关于TabLayout指示器长度的更改

TabLayout的指示器(Indicator)可以更改颜色、高度,但是你会发现没有更改长度的属性或者方法,这时我们查看源码,发现被private 了,所以只能通过反射来做了.

private final SlidingTabStrip mTabStrip;
    public  void setIndicator(TabLayout tabs, int leftDip, int rightDip) {
        Class<?> tabLayout = tabs.getClass();
        Field tabStrip = null;
        try {
            tabStrip = tabLayout.getDeclaredField("mTabStrip");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        tabStrip.setAccessible(true);
        LinearLayout ll_tab = null;
        try {
            ll_tab = (LinearLayout) tabStrip.get(tabs);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        
        int left = (int) (Resources.getSystem().getDisplayMetrics().density * leftDip);
        int right = (int) (Resources.getSystem().getDisplayMetrics().density * rightDip);

        for (int i = 0; i < ll_tab.getChildCount(); i++) {
            View child = ll_tab.getChildAt(i);
            child.setPadding(0, 0, 0, 0);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
            params.leftMargin = left;
            params.rightMargin = right;
            child.setLayoutParams(params);
            child.invalidate();
        }
    }

注意:tabMode必须是fixed
1)调用setIndicator(mTabLayout, 20, 20);方法即可,这两个20根据实际需求来定.
2)关于背景不正常问题,不要使用TabLayout自带的app:tabBackground="";
使用android:background=" "就可以了

ok,TabLayout的使用就写到这里,如果你的项目中有类似效果要实现,不妨试试TabLayout.

上一篇下一篇

猜你喜欢

热点阅读