ViewPager有所了解,并且对Fragment有所了解。之后,我也会出一些关于ViewPager、Fragment这类基础文章。ViewPager、Fragment是在android-support-v4.jar这个附加包里面的。ViewPager主要的作用就是能实现手势滑动的简单导航,通过滑动,能实现翻页的效果。而ViewPager指示器就是一个导航条,大家可以这样理解。回到主题,今天我给大家带来的是自定义的ViewPager指示器。首先先上图:
默认效果:
手势移动时的效果:
就是这样的,当然在这里指示器的颜色、标题的数量都是可以自定义的。
首先我们先看一下我们的Activity:
public class LinePagerIndicatorActivity extends FragmentActivity {
/**
* 标题数据,可以有多个(直接往后面加数据就是了)
*/
private List<String> mDatas = Arrays.asList("ZANE1", "ZANE2", "ZANE3", "ZANE4");
private ViewPagerLineIndicator indicator;
private ViewPager viewPager;
private FragmentPagerAdapter mAdapter;
private List<Fragment> mFragments = new ArrayList<Fragment>();
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_line_pager_indicator);
findView();
initData();
setView();
}
private void findView() {
indicator = (ViewPagerLineIndicator) findViewById(R.id.viewPagerLineIndicator);
viewPager = (ViewPager) findViewById(R.id.viewPager);
}
private void initData() {
// 创建 fragment
for (String data : mDatas) {
TabFragment tabFragment = TabFragment.newInstance(data);
mFragments.add(tabFragment);
}
mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public int getCount() {
return mFragments.size();
}
@Override
public Fragment getItem(int arg0) {
return mFragments.get(arg0);
}
};
}
private void setView() {
// 设置Tab上的标题
indicator.setTabItemTitles(mDatas);
viewPager.setAdapter(mAdapter);
// 设置关联的ViewPager
indicator.setViewPager(viewPager, 0);
}
}
这里面就是一个fragment数组,然后就是我们的指示器,使用viewpager关联起来。FragmentPagerAdapter是 ViewPager与Fragment关联的适配器,实现手势滑动时页面翻页。
这里主要配置我们的自定义指示器的地方是setView()这个方法。可见我们的指示器就用了两个方法:setTabItemTitles(List<String> datas)->将标题引进来。setViewPager(ViewPager viewPager, int position)绑定ViewPager。
怎么样?使用起来很简单吧。
来看看我们Activity的layout布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:zane="http://schemas.android.com/apk/res/com.zane.customviewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.zane.customviewpager.view.ViewPagerLineIndicator
android:id="@+id/viewPagerLineIndicator"
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@drawable/title_bar_bg_one_row"
zane:item_color="#ff00ff"
zane:item_count="4" >
</com.zane.customviewpager.view.ViewPagerLineIndicator>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
</android.support.v4.view.ViewPager>
</LinearLayout>
也十分简单,就一个指示器,一个viewpager。
大家会注意到我们这里使用了自定义属性:
xmlns:zane="http://schemas.android.com/apk/res/com.zane.customviewpager" 命名空间,指定我们的包名。
zane:item_color="#ff00ff"、zane:item_count="4"这是我们定义的两个属性。是提供给用户来设置属性,这样实现了解耦。用户无需改代码,只需要在xml里面修改。这里我们提供了指示器颜色和标题数量的自定义。
fragment不是主要的,就不多说,不会的同学可以去搜下关于fragment的文章。一搜就有一大堆,看几篇就差不多了。我还是给出代码:
public class TabFragment extends Fragment {
public static final String BUNDLE_TITLE = "title";
private String mTitle = "DefaultValue";
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Bundle bundle = getArguments();
if(bundle != null)
mTitle = bundle.getString(BUNDLE_TITLE);
TextView textView = new TextView(getActivity());
textView.setText(mTitle);
textView.setGravity(Gravity.CENTER);
return textView;
}
public static TabFragment newInstance(String title){
Bundle bundle = new Bundle();
bundle.putString(BUNDLE_TITLE, title);
TabFragment tabFragment = new TabFragment();
tabFragment.setArguments(bundle);
return tabFragment;
}
}
这里就是将Activity传来的标题设置到TextView上,而我们的Fragment的布局就是一个TextView。
接下来我们来制作指示器吧!~
首先,我将属性自定义的属性列出来(ps:就是在layout里面的属性)
我们先建一个resources文件叫attr.xml在res->values里面。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="item_count" format="integer"></attr>
<attr name="item_color" format="color"></attr>
<!-- ViewPagerLineIndicator 指示器的样式 -->
<declare-styleable name="ViewPagerLineIndicator">
<attr name="item_color"/>
<attr name="item_count" />
</declare-styleable>
</resources>
这里定义了指示器的颜色(item_color)和标题的数量(item_count),如果有其他需求,可以继续添加其他属性。
系统采用键值对的形式,<attr name="item_count" format="integer"></attr>,这里name表示的就是属性的名称,format就是属性的格式咯。因为是标题的数量,所以就是integer。
<declare-styleable name="ViewPagerLineIndicator"> 是声明。 就像 view 的 id 一样,声明之后,R文件里就会生成ViewPagerLineIndicator。
好了,接下来就到激动人心的自定义控件的部分了。
新建一个ViewPagerLineIndicator类,继承LinearLayout。首先,我们分析一下这个指示器会有哪些属性呢?看下效果图:
可见组成部分就是标题和底部指示器。
经过分析有以下属性:
1、标题(从图可以看出是一个数组)
2、有了标题数组,我们想要画出来一定要知道每一个item的宽度。而宽度就要通过 屏幕的宽度/item个数。所以要定义item的数量,同时也需要个默认数量(主要是作用于用户没有设置item_count属性)
3、指示器的颜色、标题的颜色(跟标题数量一样,也要给出一个默认的颜色,以免爆空指针)。
4、画笔是肯定需要的。同时画标题的矩形也是必要的。
5、由于要计算指示器的偏移量,所以需要手指滑动的偏移量属性和指示器的宽度。
6、在上图可以看出,被选中的标题颜色与其他标题颜色不同。所以需要两个颜色的属性(正常标题颜色和标题选中时的颜色)
7、与之绑定的ViewPager,由于要实现ViewPager翻页时我们的指示器也跟随滚动。所以这个也是必要的。
属性就这样差不多了。大家见下图的定义:
/**
* 画笔
*/
private Paint mPaint;
/**
* 默认的Tab数量
*/
private static final int COUNT_DEFAULT_TAB = 4;
/**
* tab数量
*/
private int mTabVisibleCount = COUNT_DEFAULT_TAB;
/**
* 线条默认颜色
*/
private static final int DEFAULT_COLOR = 0xFFFFFF00;
/**
* 指示器线条的颜色
*/
private int mLineColor = DEFAULT_COLOR;
/**
* tab上的内容
*/
private List<String> mTabTitles;
/**
* 与指示器绑定的 viewpager
*/
private ViewPager mViewPager;
/**
* 指示器(矩形)
*/
private Rect mRect;
/**
* 指示器的宽度
*/
private int mLineWidth;
/**
* 标题正常时的颜色
*/
private static final int COLOR_TEXT_NORMAL = 0x77FFFFFF;
/**
* 标题选中时的颜色
*/
private static final int COLOR_TEXT_HIGHLIGHTCOLOR = 0xFFFFFFFF;
/**
* 手指滑动时的偏移量
*/
private float mTranslationX;
下面是构造方法。
public ViewPagerLineIndicator(Context context) {
this(context, null);
}
public ViewPagerLineIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
// 获得自定义属性
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewPagerLineIndicator);
mLineColor = a.getInt(R.styleable.ViewPagerLineIndicator_item_color,
DEFAULT_COLOR);
mTabVisibleCount = a.getInt(R.styleable.ViewPagerLineIndicator_item_count, COUNT_DEFAULT_TAB);
if(mTabVisibleCount<=0)
mTabVisibleCount = COUNT_DEFAULT_TAB;
a.recycle();
// 初始化画笔,用来画矩形指示器
mPaint = new Paint();
mPaint.setColor(mLineColor);
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.FILL);
}
在构造方法里主要是实现初始化的操作,这里就是获得之前我们创建的attr.xml文件中定义的属性,获得用户在布局文件中输入的属性。然后就是初始化画笔。
下面是定义标题内容:
/**
* 设置tab的标题内容 可选,可以自己在布局文件中写死
*
* @param datas
*/
public void setTabItemTitles(List<String> datas) {
// 如果传入的list有值,则移除布局文件中设置的 view
if (datas != null && datas.size() > 0) {
this.removeAllViews();
this.mTabTitles = datas;
for (String title : mTabTitles) {
// 添加 view
addView(generateTextView(title));
}
// 设置item的click事件
setItemClickEvent();
}
}
/**
* 根据标题生成我们的TextView
*
* @param text
* @return
*/
private TextView generateTextView(String text) {
TextView tv = new TextView(getContext());
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
lp.width = getScreenWidth() / mTabVisibleCount;
tv.setGravity(Gravity.CENTER);
tv.setTextColor(COLOR_TEXT_NORMAL);
tv.setText(text);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
tv.setLayoutParams(lp);
return tv;
}
该方法通过标题数组在LinearLayout里面添加对应的textview
接下来是初始化item。
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 设置每一个 tab 的宽、高度,以及点击事件
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
LayoutParams params = (LayoutParams) view.getLayoutParams();
params.weight = 0;
params.width = mLineWidth;
view.setLayoutParams(params);
}
// 设置点击事件
setItemClickEvent();
}
private void setItemClickEvent() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
final int position = i;
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setCurrentItem(position);
}
});
}
}
给每一个item设置宽度以及点击事件,因为当我们点击item的时候要实现viewpager的同步。所以就有:mViewPager.setCurrentItem(position)。
下面是我们画出指示器:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 当大小改变时调用
super.onSizeChanged(w, h, oldw, oldh);
mLineWidth = getWidth() / mTabVisibleCount;
initLine();
}
/**
* 初始化矩形指示器
*/
private void initLine() {
mRect = new Rect(0, 0, mLineWidth, 10);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
// 偏移到 tab 的底部,因为指示器是在底部的
canvas.translate(mTranslationX, getHeight() - 10);
// 画出矩形指示器
canvas.drawRect(mRect, mPaint);
canvas.restore();
}
onSizeChange(int w, int h, int oldw, int oldh),该方法在view大小改变的时候自动调用。在此,我们获得每一个item的宽度。即:屏幕的宽度/tab的个数。
initLine()->用来初始化矩形,dispatchDraw(Canvas canvas)画出矩形指示器。
然后就是绑定viewpager,viewpager翻页,跟随滚动:
/**
* 绑定 viewpager
*/
public void setViewPager(ViewPager viewPager, int position) {
mViewPager = viewPager;
highLightTextView(position);
mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
System.out.println("onPageSelected:" + position);
resetTextViewColor();<span style="white-space:pre"></span>// 重置默认颜色
highLightTextView(position);<span style="white-space:pre"></span>// 设置当前选中的item为高亮颜色
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
System.out.println("onPageScrolled:" + position);
// Pager 滚动时, tab 跟随滚动
scroll(position, positionOffset);
}
@Override
public void onPageScrollStateChanged(int state) {
System.out.println("onPageScrollStateChanged:" + state);
}
});
}
private void scroll(int position, float offset) { // 计算偏移量mTranslationX = getWidth() / mTabVisibleCount * (position + offset);// 重画,刷新界面invalidate();}
上面实现的就是当viewpager翻页时,指示器也跟随滚动,同时被选中的标题颜色为高亮。
下面是辅助的方法:
1、设置字体颜色的方法:
/**
* 设置字体颜色
*/
private void resetTextViewColor() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view instanceof TextView) {
((TextView) view).setTextColor(COLOR_TEXT_NORMAL);
}
}
}
/**
* 设置高亮文本
*
* @param position
*/
private void highLightTextView(int position) {
View view = getChildAt(position);
if (view instanceof TextView) {
((TextView) view).setTextColor(COLOR_TEXT_HIGHLIGHTCOLOR);
}
}
/**
* 获得屏幕的宽度
*
* @return
*/
public int getScreenWidth() {
WindowManager wm = (WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
好了,大功告成。