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)); }
另外我们实现了ViewPager.OnPageChangeListener接口,因为我们要监听ViewPager的滑页行为,从而去改变导航栏的状态
所以我们也可以看到,MyIndicator持有ViewPager的引用
但是有人会问,既然我们为ViewPager设置了监听器为MyIndicator,如果我们还想要监听ViewPager怎么办呢?
所以我们为MyIndicator提供了一个方法
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); }
首先在xml布局文件里面,很简单,直接使用
<?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); }
说明我们必须在调用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); } }
最后在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); }
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); } }
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); } }
到此为止,还不涉及动画效果,但是大家在模拟器上看,就可以看到标题栏的出现,而且标题的数目,会跟你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); } } }
接下来遍历每个标题,使选中的标题背景色变成红色,其他背景色变成蓝色
可是这样还不够,我们还有标题栏自动滑动,使标题处于正中间
于是我们又了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); } } }
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/crazy__chen/article/details/47174897