【Android】仿今日头条顶部滑动字体染色效果

效果图:

效果图

顶部的标题颜色,跟随手指滑动时进行歌词的染色效果。

思路分析:

首先想到的是利用两层TextView,一层是默认的颜色,一层是要染色的颜色,然后染色层宽度随着手指滑动的距离进行改变,这样由于染色层在默认层的上方,所以会覆盖默认层,就能达到歌词染色的效果了。

简单实现:

那么先试试我们的想法是否可行,写一个FramLayout来作为容器。

 <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--这是默认的文字-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="这是一段文字"
            android:textColor="#000000"
            android:textSize="30sp"/>

        <!--这是染色的文字-->
        <TextView
            android:layout_width="20dp"
            android:layout_height="wrap_content"
            android:text="这是一段文字"
            android:textColor="@color/colorPrimary"
            android:textSize="30sp"/>

    </FrameLayout>

可以看到,布局很简单,只是将第二个,也就是染色层的TextView的宽度变了,那么效果如何呢?

嗯,效果实现了部分,至少证明思路的没错的,的确能实现文字染色的效果。但问题是这个染色层自动换行了,那简单,加个"singleLine",不就好了?

看上去好像可以了,那我们再把宽度变长点看看?

奇怪了,为什么明明覆盖上去了,但是染色层的字没出现呢?其实这是TextView的一种对超出文本的默认处理,我们只需要告诉TextView,对于超出文本,不用去做任何处理就好了,也就是"ellipsize = none"就好了,同时将"maxLines = 1"替换为"singleLine = true"就好了。让我们看看效果图。

到此,我们想要的效果就实现了,下面就是将思路应用到滑动栏中了。滑动栏我们使用谷歌提供的TabLayout来实现,TabLayout的滑动动画非常舒服,而且提供了自定义Tab的功能。既然如此,那么我们就可以使用自定义Tab,然后去监听滑动状态,然后控制我们的染色层的宽度就好了。

注意:使用TabLayout需要依赖design库 implementation 'com.android.support:design:28.0.0'。

常见滑动效果实现:

我们首先实现常见的根据滑动切换Fragment的效果。主要使用TabLayout+ViewPager实现。

主布局文件activity_main.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="40dp" />

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

很简单,只有一个TabLayout和一个ViewPager。

Java代码MainActivity.java:

        TabLayout tabLayout = findViewById(R.id.tabLayout);
        ViewPager viewPager = findViewById(R.id.viewPager);

        //模拟数据源
        List<String> titleList = new ArrayList<>();
        List<TestFragment> fragmentList = new ArrayList<>();
        for (int i = 0; i < 3; i++){
            tabLayout.addTab(tabLayout.newTab());//给TabLayout生成Tab项
            titleList.add("tab_" + i);//标题,会设置给TabLayout中的Tab项
            fragmentList.add(new TestFragment());//要切换的Fragment
        }
        //给ViewPager设置适配器
        viewPager.setAdapter(new TestFragmentAdapter(getSupportFragmentManager(),titleList,fragmentList));
        //将TabLayout和ViewPager关联起来
        tabLayout.setupWithViewPager(viewPager);

代码中的TestFragment是一个非常非常非常简单的Fragment,其中只是显示一个Hello,World的TextView而已。

TestFragmentAdapter.java:

public class TestFragmentAdapter extends FragmentStatePagerAdapter {
    List<String> titleList;
    List<TestFragment> fragmentList;

    public TestFragmentAdapter(FragmentManager fm,List<String>             titleList,List<TestFragment> fragmentList) {
        super(fm);
        this.titleList = titleList;
        this.fragmentList = fragmentList;
    }

    @Override
    public Fragment getItem(int i) {
        return fragmentList.get(i);
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }

    /**
     * 给TabLayout设置标题使用的,如果不写这个,会出现TabLayout为空白的现象
     * @param position 索引值
     * @return title
     */
    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return titleList.get(position);
    }
}

适配器没啥好说的,跟平常使用的时候一样。写到这里,我们已经实现了最常见的切换效果了。

常见效果图

下面,我们就要利用TabLayout的setCustomView来实现今日头条的切换效果了。

仿今日头条滑动效果实现:

首先写我们的自定义Tab的布局文件,一个FramLayout中包含两个TextView,第一个TextView为默认的,第二个TextView为染色的。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tab_normal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test"
        android:textSize="20sp"
        />

    <TextView
        android:id="@+id/tab_act"
        android:layout_width="10dp"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:ellipsize="none"
        android:textColor="@color/colorPrimary"
        android:text="test"
        android:textSize="20sp"/>
</FrameLayout>

然后在java代码中,我们要使用我们的自定义布局,而不使用TabLayout自带的布局。

        TabLayout tabLayout = findViewById(R.id.tabLayout);
        ViewPager viewPager = findViewById(R.id.viewPager);

        //模拟数据源
        List<String> titleList = new ArrayList<>();
        List<TestFragment> fragmentList = new ArrayList<>();
        for (int i = 0; i < 3; i++){
            tabLayout.addTab(tabLayout.newTab().setCustomView(R.layout.tab));//给TabLayout生成Tab项,并使用自定义布局文件
            titleList.add("tab_" + i);//标题,会设置给TabLayout中的Tab项
            fragmentList.add(new TestFragment());//要切换的Fragment
        }

        //因为我们没调用TabLayout的setupWithViewPager()方法,所以我们需要自己设置监听器,让自定义布局生效
        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager));

        //给ViewPager设置适配器
        viewPager.setAdapter(new TestFragmentAdapter(getSupportFragmentManager(),titleList,fragmentList));
        //将TabLayout和ViewPager关联起来
//        tabLayout.setupWithViewPager(viewPager);

这个时候再看看我们的效果:

可以看到TabLayout已经使用了我们的tab.xml这个布局文件了,下面就是本文最关键的部分了,根据滑动距离去更改染色层的宽度,达到今日头条的效果。

在此之前,我们需要想想,我们如何能知道手指滑动的距离?查看上文我们设置的两个监听器,发现其中"TabLayoutOnPageChangeListener"这个监听器中,有一个叫"onPageScrolled"的方法,不错,这个就是我们所需要的东西了。那么找到了需要的,我们就在这个监听器的基础上,进行我们的染色操作就好了,我们新建一个类去继承这个监听器。

MyOnPageChangeListener.java:

public class MyOnPageChangeListener extends TabLayout.TabLayoutOnPageChangeListener {
    private Context context;
    private List<Integer> tabWidthList;//Tab宽度集合
    private List<TextView> textViewToRightList;//染色层TextView集合
    private int currentScrollState;//当前滑动状态
    private int lastScrollStare;//上一次的滑动状态
    private int lastPositionOffsetPix;//上一次手指滑动的位置
    private int times;//已间隔次数
    private final int RECORD_TIMES = 8;//两次记录滑动位置的间隔

    public MyOnPageChangeListener(TabLayout tabLayout, Context context) {
        super(tabLayout);
        this.context = context;

        tabWidthList = new ArrayList<>();
        textViewToRightList = new ArrayList<>();

        //循环获取每一个Tab的宽度,因为每一个Tab宽度可能是不一样的
        for (int i = 0; i < tabLayout.getTabCount(); i++) {
            final View view = tabLayout.getTabAt(i).getCustomView();
            //由于存在view还未添加进容器中,所以可能出现getWidth为0的情况
            //为了保证getWidth不为0,使用post(runnable)的方式获取。
            view.post(new Runnable() {
                @Override
                public void run() {
                    tabWidthList.add(view.getWidth());//每个Tab的宽度

                    TextView textViewToRight = view.findViewById(R.id.tab_act);
                    //将所有的染色层宽度设置为0
                    ViewGroup.LayoutParams layoutParams = textViewToRight.getLayoutParams();
                    layoutParams.width = 0;
                    textViewToRight.setLayoutParams(layoutParams);
                    textViewToRightList.add(textViewToRight);
                }
            });
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        super.onPageScrolled(position, positionOffset, positionOffsetPixels);

        /**
         * 滑动状态,0:静止,1:手指滑动,2:自动滑动
         */
        //我们只在手指滑动和手指滑动过后的自动滑动做染色效果
        //由于没有点击监听,所以经过测试发现,点击的时候,滑动状态是才从0→2→0这样一个过程
        if (currentScrollState != 2 || lastScrollStare != 0) {
            int currentPosition, nextPosition;

            //计算,得出是向哪个方向移动
            int orientation = positionOffsetPixels - lastPositionOffsetPix;
            if (orientation > 0) {
                //向右

                currentPosition = position;
                nextPosition = currentPosition + 1;

                //操作下一个目标向右染色
                textViewToRightList.get(nextPosition).setVisibility(View.VISIBLE);
                ViewGroup.LayoutParams layoutParams = textViewToRightList.get(nextPosition).getLayoutParams();

                //设置宽度从0开始递增
                layoutParams.width = 0;
                textViewToRightList.get(nextPosition).setLayoutParams(layoutParams);

                float step = Float.valueOf(tabWidthList.get(nextPosition)) / context.getResources().getDisplayMetrics().widthPixels;
                layoutParams.width = (int) (step * positionOffsetPixels);
                textViewToRightList.get(nextPosition).setLayoutParams(layoutParams);
            }

            //记录滑动位置,两次记录之间间隔要稍微拉开一点,否则可能出现两次滑动位置在同一个地方
            if (times >= RECORD_TIMES){
                lastPositionOffsetPix = positionOffsetPixels;
                times = 0;
            }
            times++;
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        super.onPageScrollStateChanged(state);
        //更新滑动状态
        lastScrollStare = currentScrollState;
        currentScrollState = state;
    }

    @Override
    public void onPageSelected(int position) {
        super.onPageSelected(position);
    }
}

具体的逻辑都已经在代码中注释好了,进过上面的一番操作,我们已经实现了向右滑动时,下一个Tab中的文字逐渐染色的效果。

效果图

不错,后面就可以按照同样的思路实现完整的效果了。

向右滑动时,当前Tab染色层从左向右收缩,下一个Tab染色层从左向右展开。

向左滑动时,当前Tab染色层从右向左收缩,下一个Tab染色层从右向左展开。

这里就出现一个问题了,怎么让染色层做到从左向右收缩呢?我先说说我的思路,我是利用了TextView的"gravity"和"layout_gravity"来实现的。其中,"gravity"指的是TextView中的内容的位置如何;"layout_gravity"指的是TextView在父容器中的位置如何。如果我们想实现TextView从左到右收缩的效果,那么我们就需要让TextView中的"gravity"和"layout_gravity"都设置为"end"即可。

为了不在代码中做过多的操作,所以我在自定义Tab的布局文件中又写了一个TextView,这个新的TextView也是染色层,主要负责从左到右收缩和从右到左展开的染色效果。至此,我们的自定义Tab中就有了三个TextView,他们按照从下到上的顺序分别是:默认的TextView,向右染色的TextView,向左染色的TextView。下面只需要按照我们之前的逻辑来进行操作即可,由于逻辑基本相同,所以我就不再贴新的代码了。

本文所有源码已上传到GitHub,FollowTouTiao

发布了24 篇原创文章 · 获赞 7 · 访问量 8698

猜你喜欢

转载自blog.csdn.net/d745282469/article/details/89211772