自定义ViewPager指示器


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);
		}
	}


2、获取屏幕宽度的方法

/**
	 * 获得屏幕的宽度
	 * 
	 * @return
	 */
	public int getScreenWidth() {
		WindowManager wm = (WindowManager) getContext().getSystemService(
				Context.WINDOW_SERVICE);
		DisplayMetrics outMetrics = new DisplayMetrics();
		wm.getDefaultDisplay().getMetrics(outMetrics);
		return outMetrics.widthPixels;
	}


好了,大功告成。

点击下载源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值