• 教你轻松自己定义ViewPagerIndicator


    ViewPagerIndicator集成分页指示器。事实上就是标题栏和ViewPager的联动效果,大家先看一下效果图直观了解:(图侵删)


    这篇文章将会教大家怎么简单高速地制作自己的ViewPagerIndicator,同一时候说明制作思路,让大家能够轻易的扩展和定制自己想要的效果。

    因为文章的主要目的在于介绍总体思路,所以实现的界面效果可能不是非常好看,只是大家看过这篇文章以后,一定能够自己改动出好看的效果的。


    话不多说。先来说明总体思路。

    对照上图。对于整个控件而言,显然以下显示内容的,是一个ViewPager,通过ViewPager我们轻易的得到翻页的效果,那么难点在于上面的标题导航栏,总的来说我们要实现三点:

    1,使用ViewPager翻页的时候,导航栏对应的标题会有变化(比如上图的蓝色的下划线,或者背景颜色的变化)。来提示用户,如今是哪个标题下的内容

    2,点击导航栏标题,ViewPager会翻到相应页,另外当我们点击某个title时。我们希望整个视图能够移动到以这个title为中心。

    3,当导航栏的标题过多,超出屏幕宽度。我们能够滑动导航栏找到后边的其它标题


    要实现上面三个效果,我先来说第三个的实现

    我使用HorizontalScrollView来实现,HorizontalScrollView能够水平拖动。如果HorizontalScrollView里面包括着一系列的TextView。这个样式不就是我想要的标题栏的效果吗?

    因为HorizontalScrollView继承自FrageLayout,所以里面仅仅能包括一个子控件,通常是LinearLayout,然后再让LinearLayout去包括TextView就能够了

    另外还要讲HorizontalScrollView的HorizontalScrollBarEnabled设置为false,用于隐藏它原本的水平方向的滚动栏

    OK,看起来我们第三个问题攻克了


    如今来思考第一个问题,要title尾随ViewPager变化,我们非常自然想到要去监听ViewPager的翻页事件。使用ViewPager.OnPageChangeListener,因为title的数目跟ViewPager中Fragement的数目一样多。翻到那个,我们将对于index的title(也就是Textview)的背景变色就能够了

    因为ViewPager.OnPageChangeListener的onPageSelected(int position)中的參数position会为我们提供这个index

    OK。第一个问题貌似也没有那么难。


    如今来考虑第二个问题。这里涉及两个滑动。

    一个是ViewPager的滑动,正如我们上面所说,TextView和ViewPager中Fragement一一相应

    为了响应点击,显然我要每一个TextView设置一个OnClickListener

    可是TextView怎么知道自己的index呢?TextView本身是没有这个属性的,我们能够继承TextView,然后加入一个index属性不就完了吗?

    有了index,我们在onclick方法里面,调用ViewPager的setCurrentItem(item)方法。就能够让ViewPager滑动到正确的位置


    上面所说动画效果是ViewPager自带的,可是第二个滑动。就是HorizontalScrollView本身的滑动,HorizontalScrollView我们能够手动滑动,可是怎么样才干让它自己主动滑到我们须要的位置呢?

    HorizontalScrollView提供了一个smoothScrollTo(int x, int y)方法。使用这种方法,我们能够将HorizontalScrollView滑动到任何位置。

    问题使我们怎么确定这个位置,因为每一个TextView里面的文字数目可能不同,意味着TextView的宽度各不同样,这样要怎么计算位置呢?

    我们能够使用getLeft()方法获得目标TextView距离左边的长度,这样就不用管之前的TextView的宽度了,由于getLeft()相当于获得了它们的和,可是移动到getLeft()就超过了,我们希望它移动到中间位置。那么getLeft()还有减去(HorizontalScrollView.getWidth()-TextView.getWidth())/2

    至于为什么这样算。大家不明确的话,能够看图:我不再做过多解释



    计算出smoothScrollTo()的位置以后,调用这个函数就好了,这样就实现了标题栏和ViewPager的联动效果。


    原理解讲到这里,以下我们来直接看代码。

    先来看构造函数和相关属性

    public class MyIndicator extends HorizontalScrollView implements ViewPager.OnPageChangeListener{
        private ViewPager mViewPager;
        private MyLinearLayout myLinearLayout;
        ViewPager.OnPageChangeListener mListener;
    
        public MyIndicator(Context context) {
            super(context);
            init(context);
        }
    
        public MyIndicator(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public MyIndicator(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(context);
        }
    
    
        private void init(Context mcontext){
            setHorizontalScrollBarEnabled(false);//隐藏自带的滚动栏
            //加入linearLayout
            myLinearLayout = new MyLinearLayout(mcontext);
            myLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
            addView(myLinearLayout, new ViewGroup.LayoutParams(WRAP_CONTENT, MATCH_PARENT));
        }

    从上面的代码我们能够看到。我继承HorizontalScrollView来自己定义了一个控件,这个控件就是我们上面说的导航栏。而且隐藏了它的滚动栏

    另外我们实现了ViewPager.OnPageChangeListener接口,由于我们要监听ViewPager的滑页行为。从而去改变导航栏的状态

    所以我们也能够看到,MyIndicator持有ViewPager的引用

    可是有人会问,既然我们为ViewPager设置了监听器为MyIndicator。假设我们还想要监听ViewPager怎么办呢?

    所以我们为MyIndicator提供了一个方法

    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener){
            mListener = listener;
        }

    这样就能够为ViewPager设置监听器了,而这个listener的调用,须要我们在MyIndicator实现的ViewPager.OnPageChangeListener接口的方法的最后主动调用

    也就是这样写:

    @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if(mListener!=null) mListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
        }
    
        @Override
        public void onPageSelected(int position) {
            setCurrentItem(position);
            if(mListener!=null) mListener.onPageSelected(position);
        }
    
        @Override
        public void onPageScrollStateChanged(int state) {
            if(mListener!=null) mListener.onPageScrollStateChanged(state);
        }


    看完了初始化的工作,我们能够看看怎么使用这个MyIndicator

    首先在xml布局文件中面,非常easy。直接使用

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        
        <com.example.kaiyicky.myapplication.MyIndicator
            android:id="@+id/indicator"
            android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            />
        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            />
    
    </LinearLayout>
    然后在Activity里面这样

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);        
    
            ViewPager pager = (ViewPager)findViewById(R.id.pager);       
           
            MyIndicator indicator = (MyIndicator)findViewById(R.id.indicator);
            indicator.setViewPager(pager);
        }
    通过一个setViewPager()方法使MyIndicator持有ViewPager的引用就能够了


    OK,接下来继续看MyIndicator怎么写。看setViewPager()方法

    public void setViewPager(ViewPager viewPager){
            setViewPager(viewPager,0);
        }
    
        public void setViewPager(ViewPager viewPager,int initPos){
            if (mViewPager == viewPager) {
                return;
            }
            if (mViewPager != null) {
                mViewPager.setOnPageChangeListener(null);
            }
            final PagerAdapter adapter = viewPager.getAdapter();
            if (adapter == null) {
                throw new IllegalStateException("ViewPager does not have adapter instance.");
            }
            mViewPager = viewPager;
            viewPager.setOnPageChangeListener(this);
            notifyDataSetChanged();
            setCurrentItem(initPos);
        }

    在上面的代码中我们能够看到。我们检查了ViewPager的Adapter是否为空。假设是要抛出异常

    说明我们必须在调用setViewPager()之前为ViewPager设置Adapter

    为什么呢?由于导航栏的标题数目跟ViewPager的页面数目是一样的,而FragmentPagerAdapter里面的getCount()方法返回了这个数目,假设没有设置Adapter

    MyIndicator就不知道怎么绘制导航栏了。由于连标题数目都不清楚

    对于Adapter。我们能够写一个简单的,比如

    class GoogleMusicAdapter extends FragmentPagerAdapter {
            public GoogleMusicAdapter(FragmentManager fm) {
                super(fm);
            }
    
            @Override
            public Fragment getItem(int position) {
                return TestFragment.newInstance(CONTENT[position % CONTENT.length]);
            }
    
            @Override
            public CharSequence getPageTitle(int position) {
                return CONTENT[position % CONTENT.length].toUpperCase();
            }
    
            @Override
            public int getCount() {
                return CONTENT.length;
            }
        }
    当中TestFragment是这种

    public final class TestFragment extends Fragment {
        private static final String KEY_CONTENT = "TestFragment:Content";
    
        public static TestFragment newInstance(String content) {
            TestFragment fragment = new TestFragment();
    
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < 20; i++) {
                builder.append(content).append(" ");
            }
            builder.deleteCharAt(builder.length() - 1);
            fragment.mContent = builder.toString();
    
            return fragment;
        }
    
        private String mContent = "?

    ?

    ?

    "; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if ((savedInstanceState != null) && savedInstanceState.containsKey(KEY_CONTENT)) { mContent = savedInstanceState.getString(KEY_CONTENT); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView text = new TextView(getActivity()); text.setGravity(Gravity.CENTER); text.setText(mContent); text.setTextSize(20 * getResources().getDisplayMetrics().density); text.setPadding(20, 20, 20, 20); LinearLayout layout = new LinearLayout(getActivity()); layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); layout.setGravity(Gravity.CENTER); layout.addView(text); return layout; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(KEY_CONTENT, mContent); } }


    事实上就是一个 创建Fragment的工具类

    最后在Activity改动一下

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            FragmentPagerAdapter adapter = new GoogleMusicAdapter(getSupportFragmentManager());
    
            ViewPager pager = (ViewPager)findViewById(R.id.pager);
            pager.setAdapter(adapter);
            
            MyIndicator indicator = (MyIndicator)findViewById(R.id.indicator);
            indicator.setViewPager(pager);
        }



    回过头来看setViewPager()方法。然后就是调用了两个函数,首先是notifyDataSetChanged()

    private void notifyDataSetChanged(){
            myLinearLayout.removeAllViews();
            PagerAdapter mAdapter = mViewPager.getAdapter();
            int count = mAdapter.getCount();
            for(int i=0;i<count;i++){
                addTab(i,mAdapter.getPageTitle(i));
            }
            requestLayout();
        }
    
        private void addTab(int index,CharSequence text) {
            TabView tabView = new TabView(getContext());
            tabView.index = index;
            tabView.setFocusable(true);
            tabView.setOnClickListener(mTabClickListener);
            tabView.setText(text);
            tabView.setTextSize(30);
            tabView.setPadding(20,0,20,0);
            myLinearLayout.addView(tabView);
        }

    这个函数事实上就是起到加入标题的作用

    我们通过Adapter获得了数目,然后逐个调用addTab()将标题栏加入进LinearLayout

    有人会问myLinearLayout是什么,眼下在MyIndicator里面事实上就是一个LinearLayout,我独立出来是为了大家以后方便扩展。代码例如以下

    public class MyLinearLayout extends LinearLayout {
        public MyLinearLayout(Context context) {
            super(context);
            setWillNotDraw(false);
        }
    }

    然后来看addTab()做了什么,顾名思义,就是加入tab,前面原理分析的时候,我们已经说过tab事实上是TextView。可是要标记index。所以我们要继承TextView自己定义一个控件

    private class TabView extends TextView {
            public int index;
            public TabView(Context context,int index){
                this(context);
                this.index = index;
            }
            public TabView(Context context) {
                super(context);
            }
        }

    能够看到,事实上仅仅是为textView添加了Index属性


    到此为止,还不涉及动画效果,可是大家在模拟器上看,就能够看到标题栏的出现。并且标题的数目。会跟你ViewPager中Fragment数目一样


    以下来谈论动画效果的实现

    上面我们记得,setViewPager()方法里面,另一个setCurrentItem()方法。另外onPageSelected()里面也有调用这种方法

    事实上这种方法就是来实现换页的动态效果的。onPageSelected()里面调用,能够在viewPager滑动的时候换页

    public void setCurrentItem(int item) {
            if (mViewPager == null) {
                throw new IllegalStateException("ViewPager has not been bound.");
            }
            int mSelectedTabIndex = item;
            mViewPager.setCurrentItem(item);
    
            final int tabCount = myLinearLayout.getChildCount();
            for (int i = 0; i < tabCount; i++) {//遍历标题,改变选中的背景
                final View child = myLinearLayout.getChildAt(i);
                final boolean isSelected = (i == item);
                child.setSelected(isSelected);
                if (isSelected) {
                    child.setBackgroundColor(Color.RED);
                    animateToTab(item);//动画效果
                }else{
                    child.setBackgroundColor(Color.TRANSPARENT);
                }
            }
        }

    事实上这种方法也非常easy。首先实现ViewPager的滑动。仅仅有调用ViewPager的setCurrentItem()方法就好了

    接下来遍历每一个标题。使选中的标题背景色变成红色,其它背景色变成蓝色

    但是这样还不够。我们还有标题栏自己主动滑动,使标题处于正中间

    于是我们又了aniateToTab()方法

    例如以下

    private Runnable mTabSelector;
        private void animateToTab(final int position) {
            final View tabView = myLinearLayout.getChildAt(position);/获取目标标题栏对象
            if (mTabSelector != null) {
                removeCallbacks(mTabSelector);
            }
            mTabSelector = new Runnable() {
                public void run() {
                    final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;//计算要滑动到的位置
                    smoothScrollTo(scrollPos, 0);
                    mTabSelector = null;
                }
            };
            post(mTabSelector);//在主线程运行动画
        }
    和一開始就说明得原理一样,我们计算出来要smoothScrollTo的终于位置,然后调用这种方法就好了

    仅仅有写在runnable里面,是为了保证在主线程调用


    OK,到此为止,我们就实现了滑动ViewPager,标题栏也会滑动的效果了,不信大家如今能够測试一下自己的代码


    接下来就是点击标题。也会自己主动滑动,为了让TextView能点击,我为每一个TextView都设置了OnClickListener

     private final OnClickListener mTabClickListener = new OnClickListener() {
            public void onClick(View view) {
                TabView tabView = (TabView)view;
                final int oldSelected = mViewPager.getCurrentItem();
                final int newSelected = tabView.index;
                setCurrentItem(newSelected);
            }
        };
    监听器里面更简单。就是获得目标标题栏的index。然后调用setCurrentItem()就能够了

    这样就实现了点击滑动的效果,点击标题栏,Viewpager也会跟着翻页哦

    
    

    整个控件就说完了。假设大家事先明确了我的思路,看起代码来应该非常流畅

    最后贴出MyIndicator的完整代码。大家能够任意改造,实现自己须要的效果啊!

    public class MyIndicator extends HorizontalScrollView implements ViewPager.OnPageChangeListener{
        private ViewPager mViewPager;
        private MyLinearLayout myLinearLayout;
        ViewPager.OnPageChangeListener mListener;
    
        private final OnClickListener mTabClickListener = new OnClickListener() {
            public void onClick(View view) {
                TabView tabView = (TabView)view;
                final int oldSelected = mViewPager.getCurrentItem();
                final int newSelected = tabView.index;
                setCurrentItem(newSelected);
            }
        };
    
        public MyIndicator(Context context) {
            super(context);
            init(context);
        }
    
        public MyIndicator(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }
    
        public MyIndicator(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(context);
        }
    
    
        private void init(Context mcontext){
            setHorizontalScrollBarEnabled(false);//隐藏自带的滚动栏
            //加入linearLayout
            myLinearLayout = new MyLinearLayout(mcontext);
            myLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
            addView(myLinearLayout, new ViewGroup.LayoutParams(WRAP_CONTENT, MATCH_PARENT));
        }
    
        public void setViewPager(ViewPager viewPager){
            setViewPager(viewPager,0);
        }
    
        public void setViewPager(ViewPager viewPager,int initPos){
            if (mViewPager == viewPager) {
                return;
            }
            if (mViewPager != null) {
                mViewPager.setOnPageChangeListener(null);
            }
            final PagerAdapter adapter = viewPager.getAdapter();
            if (adapter == null) {
                throw new IllegalStateException("ViewPager does not have adapter instance.");
            }
            mViewPager = viewPager;
            viewPager.setOnPageChangeListener(this);
            notifyDataSetChanged();
            setCurrentItem(initPos);
        }
    
        private void notifyDataSetChanged(){
            myLinearLayout.removeAllViews();
            PagerAdapter mAdapter = mViewPager.getAdapter();
            int count = mAdapter.getCount();
            for(int i=0;i<count;i++){
                addTab(i,mAdapter.getPageTitle(i));
            }
            requestLayout();
        }
    
        private void addTab(int index,CharSequence text) {
            TabView tabView = new TabView(getContext());
            tabView.index = index;
            tabView.setFocusable(true);
            tabView.setOnClickListener(mTabClickListener);
            tabView.setText(text);
            tabView.setTextSize(30);
            tabView.setPadding(20,0,20,0);
            myLinearLayout.addView(tabView);
        }
    
        public void setCurrentItem(int item) {
            if (mViewPager == null) {
                throw new IllegalStateException("ViewPager has not been bound.");
            }
            int mSelectedTabIndex = item;
            mViewPager.setCurrentItem(item);
    
            final int tabCount = myLinearLayout.getChildCount();
            for (int i = 0; i < tabCount; i++) {//遍历标题,改变选中的背景
                final View child = myLinearLayout.getChildAt(i);
                final boolean isSelected = (i == item);
                child.setSelected(isSelected);
                if (isSelected) {
                    child.setBackgroundColor(Color.RED);
                    animateToTab(item);//动画效果
                }else{
                    child.setBackgroundColor(Color.TRANSPARENT);
                }
            }
        }
    
        private Runnable mTabSelector;
        private void animateToTab(final int position) {
            final View tabView = myLinearLayout.getChildAt(position);
            if (mTabSelector != null) {
                removeCallbacks(mTabSelector);
            }
            mTabSelector = new Runnable() {
                public void run() {
                    final int scrollPos = tabView.getLeft() - (getWidth() - tabView.getWidth()) / 2;
                    smoothScrollTo(scrollPos, 0);
                    mTabSelector = null;
                }
            };
            post(mTabSelector);
        }
    
        public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener){
            mListener = listener;
        }
    
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if(mListener!=null) mListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
        }
    
        @Override
        public void onPageSelected(int position) {
            setCurrentItem(position);
            if(mListener!=null) mListener.onPageSelected(position);
        }
    
        @Override
        public void onPageScrollStateChanged(int state) {
            if(mListener!=null) mListener.onPageScrollStateChanged(state);
        }
    
        private class TabView extends TextView {
            public int index;
            public TabView(Context context,int index){
                this(context);
                this.index = index;
            }
            public TabView(Context context) {
                super(context);
            }
        }
    }
    



  • 相关阅读:
    【读书笔记】Cracking the Code Interview(第五版中文版)
    【LeetCode】贪心 greedy(共38题)
    【LeetCode】树(共94题)
    C++学习笔记 <multimap>
    C++学习笔记 <map>
    Flatten Binary Tree to Linked List <leetcode>
    Sum Root to Leaf Numbers <leetcode>
    Combinations <leetcode>
    Populating Next Right Pointers in Each Node II <leetcode>
    Pascal's Triangle II <leetcode>
  • 原文地址:https://www.cnblogs.com/slgkaifa/p/7344660.html
Copyright © 2020-2023  润新知