自定义view实现ViewPageIndicator(viewpage指示器) so easy

自定义view实现viewpage指示器其实并不难,需要读者对java画图类了解一些即可。通过对viewPage滑动时传递出的一些参数稍加利用,根据这些参数在正确的位置画个三角形就成了指示器,就这么so easy。下面贴代码,注释都在代码中,大家自己看哦


package com.example.view;

import java.util.List;

import com.example.viewpagerindicator.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
 * viewpage指示器
 * @author 黄海
 *
 */
public class ViewPagerIndicator extends LinearLayout {
	private Paint mPaint;// 绘制三角形的画笔
	private Path mPath;// 用于绘制三角形的边
	private int mTriangleWidth;// 三角形的宽
	private int mTriangleHeight;// 三角形的高
	private static final float RADIO_TRIANGLE_WIDTH = 1 / 6F;// 用于设置三角形的宽和tab底边的比例,用于屏幕适配
	/**
	 * 三角形底边的最大宽度
	 */
	private final int DIMENSION_TRIANGLE_WIDTH_MAX = (int) (getScreenWidth() / 3 * RADIO_TRIANGLE_WIDTH);
	private int mInitTranslationX;// 第一个三角形初始化的偏移位置
	private int mTranslationX;// 移动时候的三角形偏移位置
	private int mTabVisibleCount;// 可见tab的数量
	private static final int COUNT_DEFAULT_TAB = 4;// 默认可见tab为4个
	private List<String> mTitles;// 接收传递过来的title
	private static final int COLOR_TEXT_NORMAL = Color.parseColor("#FFFFFF");
	private static final int COLOR_TEXT_HIGHLIGHT = Color
			.parseColor("#FF4CDA0F");

	public ViewPagerIndicator(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public ViewPagerIndicator(Context context, AttributeSet attrs) {
		super(context, attrs);

		TypedArray attributes = context.obtainStyledAttributes(attrs,
				R.styleable.ViewPagerIndicator);
		mTabVisibleCount = attributes.getInt(
				R.styleable.ViewPagerIndicator_visible_tab_count,
				COUNT_DEFAULT_TAB);
		if (mTabVisibleCount < 0) {
			mTabVisibleCount = COUNT_DEFAULT_TAB;
		}
		// 用完必须释放
		attributes.recycle();
		// 初始化画笔
		mPaint = new Paint();
		// 防止边缘锯齿
		mPaint.setAntiAlias(true);
		mPaint.setColor(Color.parseColor("#ffffff"));
		mPaint.setStyle(Style.FILL);
		mPaint.setPathEffect(new CornerPathEffect(2));
	}

	public ViewPagerIndicator(Context context) {
		this(context, null);
	}

	/**
	 * 绘制三角形
	 * 绘制VIew本身的内容,通过调用View.onDraw(canvas)函数实现,绘制自己的孩子通过dispatchDraw(canvas)实现
	 * 
	 * 画完背景后,draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法,
	 * dispatchDraw
	 * ()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制
	 * ,当它没有背景时直接调用的是dispatchDraw
	 * ()方法,而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了
	 * dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是
	 * dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和
	 * getIntrinsicWidth(),getIntrinsicHeight()方法,然后设为背景
	 */
	@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		/**
		 * save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
		 * restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
		 * save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。
		 */
		canvas.save();
		canvas.translate(mInitTranslationX + mTranslationX, getHeight());
		canvas.drawPath(mPath, mPaint);
		canvas.restore();
	}

	/**
	 * 设置三角形的大小
	 * 
	 * onSizeChanged()在控件大小发生变化的时候调用(例如第一次初始化控件的时候) 布局过程中,
	 * 先调onMeasure计算每个child的大小, 然后调用onLayout对child进行布局,
	 * onSizeChanged()是在布局发生变化时的回调函数,间接回去调用onMeasure, onLayout函数重新布局
	 * onSizeChanged的启动时间在onDraw之前
	 */
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		// w/3为每个tab的宽度,目前可见为3个
		mTriangleWidth = (int) (w / mTabVisibleCount * RADIO_TRIANGLE_WIDTH);
		mTriangleWidth = Math.min(mTriangleWidth, DIMENSION_TRIANGLE_WIDTH_MAX);
		// 第一个三角形的偏移位置
		mInitTranslationX = w / mTabVisibleCount / 2 - mTriangleWidth / 2;

		initTriangle();
	}

	/**
	 * 初始化三角形
	 */
	private void initTriangle() {
		// 将三角形角度设置为30度
		mTriangleHeight = (int) (mTriangleWidth / 2 * Math.tan(Math.PI / 6));
		mPath = new Path();
		mPath.moveTo(0, 0);
		mPath.lineTo(mTriangleWidth, 0);
		mPath.lineTo(mTriangleWidth / 2, -mTriangleHeight);
		// 关闭当前轮廓,完成闭合
		mPath.close();
	}

	/**
	 * 三角形跟随ViewPager移动
	 * 
	 * @param position
	 * @param positionOffset
	 */
	public void scroll(int position, float positionOffset) {
		int tabWidth = getWidth() / mTabVisibleCount;
		mTranslationX = (int) (tabWidth * (positionOffset + position));

		/**
		 * 容器移动,在tab处于移动至最后一个时
		 */
		// if (position > =(mTabVisibleCount - 2) && positionOffset > 0
		// && getChildCount() > mTabVisibleCount) {
		if (position > (mTabVisibleCount - 2) && positionOffset > 0
				&& getChildCount() > mTabVisibleCount) {

			if (mTabVisibleCount != 1) {
				// this.scrollTo((position - (mTabVisibleCount - 2)) * tabWidth
				// + (int) (tabWidth * positionOffset), 0);

				this.scrollTo((int) (tabWidth * (positionOffset + position
						- mTabVisibleCount + 1)), 0);
			} else {
				this.scrollTo(position * tabWidth
						+ (int) (tabWidth * positionOffset), 0);
			}
		}
		invalidate();
	}

	/**
	 * xml加载完成之后,回调此方法
	 * 
	 * 设置每个tab的LayoutParams
	 */
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		int childCount = getChildCount();
		if (childCount == 0) {
			return;
		}

		for (int i = 0; i < childCount; i++) {
			View view = getChildAt(i);
			LinearLayout.LayoutParams params = (LayoutParams) view
					.getLayoutParams();
			params.weight = 0;
			params.width = getScreenWidth() / mTabVisibleCount;
			view.setLayoutParams(params);
		}
		setItemClickEvent();
	}

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

	/**
	 * 动态设置tab的数量
	 * 
	 * @param count
	 */
	public void setVisibleTabCount(int count) {
		mTabVisibleCount = count;
	}

	/**
	 * 动态设置tab
	 * 
	 * @param titles
	 */
	public void setTabItemTitles(List<String> titles) {
		if (titles != null && titles.size() > 0) {
			this.removeAllViews();
			mTitles = titles;
			for (String title : mTitles) {
				this.addView(generateTextView(title));
			}
			setItemClickEvent();
		}
	}

	/**
	 * 根据title创建tab
	 * 
	 * @param title
	 * @return
	 */
	private View generateTextView(String title) {
		TextView textView = new TextView(getContext());
		LinearLayout.LayoutParams params = new LayoutParams(
				LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
		params.width = getScreenWidth() / mTabVisibleCount;
		textView.setText(title);
		textView.setGravity(Gravity.CENTER);
		textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
		textView.setTextColor(COLOR_TEXT_NORMAL);
		textView.setLayoutParams(params);
		return textView;
	}

	// 接收关联的ViewPager
	private ViewPager mViewPager;

	/**
	 * 提供一个接口供外部ViewPager使用
	 * 
	 * @author Administrator
	 * 
	 */
	public interface PageOnChangeListener {
		public void onPageScrolled(int position, float positionOffset,
				int positionOffsetPixels);

		public void onPageSelected(int position);

		public void onPageScrollStateChanged(int state);
	}

	public PageOnChangeListener mListener;

	public void setViewPagerOnPageChangeListener(PageOnChangeListener listener) {
		mListener = listener;
	}

	/**
	 * 设置关联的ViewPager
	 * 
	 * @param viewpager
	 * @param position
	 */
	public void setViewPager(ViewPager viewpager, int position) {
		mViewPager = viewpager;
		mViewPager.addOnPageChangeListener(new OnPageChangeListener() {

			@Override
			public void onPageSelected(int position) {
				if (mListener != null) {
					mListener.onPageSelected(position);
				}
				highLightTextView(position);
			}

			@Override
			public void onPageScrolled(int position, float positionOffset,
					int positionOffsetPixels) {
				// 三角形跟随ViewPager移动的距离就是:
				scroll(position, positionOffset);
				if (mListener != null) {
					mListener.onPageScrolled(position, positionOffset,
							positionOffsetPixels);
				}
			}

			@Override
			public void onPageScrollStateChanged(int state) {
				if (mListener != null) {
					mListener.onPageScrollStateChanged(state);
				}

			}
		});
		mViewPager.setCurrentItem(position);
		highLightTextView(position);
	}

	/**
	 * 高亮被点击的tab
	 * 
	 * @param position
	 */
	private void highLightTextView(int position) {
		resetTextViewColor();
		View view = getChildAt(position);
		if (view instanceof TextView) {
			((TextView) view).setTextColor(COLOR_TEXT_HIGHLIGHT);
		}
	}

	/**
	 * 重置tab文本颜色
	 */
	private void resetTextViewColor() {
		for (int i = 0; i < getChildCount(); i++) {
			View view = getChildAt(i);
			if (view instanceof TextView) {
				((TextView) view).setTextColor(COLOR_TEXT_NORMAL);
			}
		}
	}

	/**
	 * 设置Tab的点击事件
	 */
	private void setItemClickEvent() {
		int childCount = getChildCount();
		for (int i = 0; i < childCount; i++) {
			final int j = i;
			View view = getChildAt(i);
			view.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					mViewPager.setCurrentItem(j);
				}
			});
		}
	}
}

指示器的一个自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="visible_tab_count" format="integer" />

    <declare-styleable name="ViewPagerIndicator">
        <attr name="visible_tab_count" />
    </declare-styleable>

</resources>


然后例子的activity

package com.example.viewpagerindicator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.example.view.ViewPagerIndicator;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;

public class MainActivity extends FragmentActivity {

	private ViewPager mViewpager;
	private ViewPagerIndicator mViewPagerIndicator;
	private List<String> mTitles = Arrays.asList("短信1", "收藏2", "推荐3", "短信4",
			"收藏5", "推荐6", "短信7", "收藏8", "推荐9");
	private List<VpSimpleFragment> mContents = new ArrayList<VpSimpleFragment>();// 装载ViewPager数据的List
	/**
	 * FragmentPagerAdapter,见名知意,这个适配器就是用来实现Fragment在ViewPager里面进行滑动切换的,因此,
	 * 如果我们想实现Fragment的左右滑动,可以选择ViewPager和FragmentPagerAdapter实现。
	 * FragmentPagerAdapter拥有自己的缓存策略
	 * ,当和ViewPager配合使用的时候,会缓存当前Fragment以及左边一个、右边一个,一共三个Fragment对象。
	 * 假如有三个Fragment
	 * ,那么在ViewPager初始化之后,3个fragment都会加载完成,中间的Fragment在整个生命周期里面只会加载一次
	 * ,当最左边的Fragment处于显示状态
	 * ,最右边的Fragment由于超出缓存范围,会被销毁,当再次滑到中间的Fragment的时候,最右边的Fragment会被再次初始化。
	 */
	private FragmentPagerAdapter mAdapter;// ViewPager适配器

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main);

		initViews();
		initDatas();

		// 动态设置tab
		mViewPagerIndicator.setVisibleTabCount(3);
		mViewPagerIndicator.setTabItemTitles(mTitles);

		mViewpager.setAdapter(mAdapter);
		mViewPagerIndicator.setViewPager(mViewpager, 0);
	}

	/**
	 * 初始化视图
	 */
	private void initDatas() {
		mViewpager = (ViewPager) findViewById(R.id.viewpager);
		mViewPagerIndicator = (ViewPagerIndicator) findViewById(R.id.indicator);
	}

	/**
	 * 初始化数据
	 */
	private void initViews() {
		for (String title : mTitles) {
			VpSimpleFragment fragment = VpSimpleFragment.newInstance(title);
			mContents.add(fragment);
		}
		mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {

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

			@Override
			public Fragment getItem(int position) {
				return mContents.get(position);
			}
		};
	}
}
xml文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.example.viewpagerindicator"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffffff"
    android:orientation="vertical" >

    <com.example.view.ViewPagerIndicator
        android:id="@+id/indicator"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="@drawable/shape_backgroud"
        android:orientation="horizontal"
        app:visible_tab_count="4" >
    </com.example.view.ViewPagerIndicator>

    <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>


整体难度中等,大家如果有类似的需求可以直接copy ViewPageIndicator的代码拿去使用,不用谢。

源码


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值