Android 自定义控件之ViewPageIndicator

  久不做android,感觉自己的技术都生疏了,虽然说,感觉自己也没什么技术:)

  另:本篇博客参考的是鸿洋大神的一篇博客,再次附上链接地址http://www.cnblogs.com/freeliver54/archive/2012/08/21/2649496.html,拜谢!

  前言:虽然说网上关于ViewPageIndicator的轮子是一个又一个,但是,会用是一回事,会写,又是另一回事,如果事事都想着搬轮子,那和咸鱼有什么区别。

  好了,闲话不多说,下面就来进入正题吧。

  做一个东西,首先重要的是思路,思路清晰了,写起来才会顺手,而不至于茫然四顾,举足无措。下面来分析一下这个自定义控件要怎么写?

  android自定义控件的实现有两种方式,一是控件的组合,二就是通过画笔来进行绘制了,显然viewPageIndicator的话,直接就用控件的组合来做就行了,而且参考到常见的一些自定义ViewPageIndicator,继承LinearLayout显然是不错的选择,Tab项,不讲求华丽的话,TextView就行了,然后考虑到tab项可能一个屏幕显示不下,因此需要规定一个页面显示几个tab,哦,也就是TextView,这个可以通过提供方法进行设置,也可以提供自定义属性。

  最后,最重要的就是就是指示器了,这里和鸿洋大神一样,也是采用三角形了,至于偏移量,大体就是一个(textview的宽度-三角形的宽度)/2+textview的宽度*n。

  大体思路就是这样了,下面看一下用法:

  1、通用setItemTitle动态设置tab

  

  <study.hugy.com.myapp.customView.ViewPagerIndicator.ViewPagerIndicator
  android:id="@+id/mIndicator"
  android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:layout_below="@+id/top_bar"
   android:minHeight="40dp"
   android:orientation="horizontal"
   app:visible_count="3"/>
  <android.support.v4.view.ViewPager
   android:id="@+id/home_view_pager"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:layout_below="@+id/mIndicator">
  </android.support.v4.view.ViewPager>
  
public class HomePageFragement extends Fragment {

private ViewPagerIndicator indicator;
private ViewPager viewPager;
private static List<String> title = new ArrayList<>();
static {
title.add("目录一");
title.add("目录二");
title.add("目录三");
title.add("目录四");
title.add("目录五");
}

private PagerAdapter mAdapter = new PagerAdapter() {
@Override
public int getCount()
{
return title.size();
}

@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
TextView view = new TextView(context);
view.setText(position+"");
view.setTextColor(Color.WHITE);
container.addView(view);
return view;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
};
private Context context;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
context = getActivity().getApplicationContext();
View view=inflater.inflate(R.layout.fragment_home_page,container,false);
indicator = (ViewPagerIndicator) view.findViewById(R.id.mIndicator);
viewPager = (ViewPager) view.findViewById(R.id.home_view_pager);
indicator.setItemTitles(title);
viewPager.setAdapter(mAdapter);
indicator.setViewPager(viewPager,0);
Log.i("hugy","childCounts:"+indicator.getChildCount()); return view;
  }
}  
 

 可以看到,真正涉及到ViewPageIndicator用法的代码只有两行

  indicator.setItemTitles(title);

  indicator.setViewPager(viewPager,0);

  好了,基本的用法就是这样了,下面来看一看代码的实现。

  首先是自定义属性,在res/values目录下新建attrs.xml文件,并做如下声明

  <attr name="visible_count" format="integer"></attr>
  <declare-styleable name="ViewPagerIndicator">
  <attr name="visible_count"/>
  </declare-styleable>

  
  然后看一下需要哪些属性:
  
  private final int DEFAULT_VISIBLE_COUNT = 4; //默认一个屏幕显示的tab数量,在未设置自定义属性的时候使用
  private final float REDIO_TRIENGLE = (float) (1.0 / 6); //一个三角形指示器占用一个条目的1/6宽度
  private final int COLOR_TEXT_NORMAL = 0x77FFFFFF; //textView未被选择时字体的颜色
  private final int COLOR_TEXT_SELECTED = 0xFFFFFFFF; //textView被选择时字体的颜色
  private int DEFAULT_TRIENGLE_WIDTH = 0; //默认的三角形的宽度
  private int mVisibleItemCount = DEFAULT_VISIBLE_COUNT; //一个屏幕的tab的数量,默认等于DEFAULT_VISIBLE_COUNT
  private Context mContext;//上下文
  private Paint mPaint; //绘制指示器使用
  private int mTriengleWidth; //三角形的宽度
  private Path mPath; //绘制三角形的路径
  private int mTriengleHeight; //三角形的高度
  private int mInitTranslationX; //指示器的初始偏移量
  private float mTranslationX; //指示器的偏移量
  private ViewPager mViewPager; //关联的viewPager
  private List<String> itemList; //tab显示的内容

  
  然后是构造函数
  public ViewPagerIndicator(Context context) {   this(context,null);}
  public ViewPagerIndicator(Context context, AttributeSet attrs) {
   super(context, attrs);
   mContext = context;
   DEFAULT_TRIENGLE_WIDTH = (int) (getScreenWidth()/3*REDIO_TRIENGLE);
   TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewPagerIndicator);
   mVisibleItemCount = a.getInt(R.styleable.ViewPagerIndicator_visible_count,
   DEFAULT_VISIBLE_COUNT);
  if (mVisibleItemCount < 0 ){
   mVisibleItemCount = DEFAULT_VISIBLE_COUNT;
   }
  a.recycle();
  mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setColor(Color.parseColor("#ffffffff"));
  mPaint.setStyle(Paint.Style.FILL);
  mPaint.setPathEffect(new CornerPathEffect(3));
  }

  
  
  可以看到,在构造中我们主要做了2件事,一是获得自定义的visible_count属性,二是初始化mPaint画笔;
  然后是onFinishInflate方法和onSizeChanged方法,其中onFInishInflate方法是在控件实例化后调用,此方法主要是处理使用者          ViewPageIndicator中定义了Tab的情况,不理解的话可以看一下方法中的代码,而onSizeChanged和显然就是当控件的尺寸变化时调用,这个会   在控件实例化的时候调用;

  

  @Override
  protected void onFinishInflate() {
  super.onFinishInflate();
  int cCount = getChildCount();
   if (cCount == 0) return;
   for (int i = 0; i < cCount; i++) {
   View view = getChildAt(i);
   LinearLayout.LayoutParams params = (LayoutParams) view.getLayoutParams();
   params.width = 0;
   params.width = getScreenWidth()/mVisibleItemCount;
   view.setLayoutParams(params);
   }
   setClickEvent();
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  mTriengleWidth = (int) ((w/mVisibleItemCount)*REDIO_TRIENGLE);
   mTriengleWidth = Math.min(DEFAULT_TRIENGLE_WIDTH,mTriengleWidth);
  initTriengle();   
    mInitTranslationX = (getWidth() / mVisibleItemCount - mTriengleWidth) / 2;  
  }   
  
  //移动画布到初始位置
  @override
  protected void dispatchDraw(Canvas canvas) {
      canvas.save();
  canvas.translate(mInitTranslationX + mTranslationX, getHeight() + 1);
  canvas.drawPath(mPath, mPaint);
   canvas.restore();
  super.dispatchDraw(canvas);
  }



onFinishInflate方法的作用上面已经说过了,相信大家看一下就会明白,下面说一下onSizeChanged方法,其实也没什么说的,相信大家也一眼就看的出来,其实这个方法是就是初始化指示器用的,想要对指示器形状做文章的朋友
可以在initTriengle方法中修改,下面贴上代码

private void initTriengle() {
mPath = new Path();

mTriengleHeight = (int) (mTriengleWidth/2/Math.sqrt(2));
mPath.moveTo(0,0);
mPath.lineTo(mTriengleWidth,0);
mPath.lineTo(mTriengleWidth/2,-mTriengleHeight);
mPath.close();
}

private void setClickEvent() {
for (int i = 0; i < getChildCount(); i++) {
final int finalI = i;
getChildAt(i).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setCurrentItem(finalI);
invalidate();
}
});
}
}

这样,内部的方法基本上就写完了,下面来看看对外暴露的方法

首先是setItemTitle(List<String> data)

  public void setItemTitles(List<String> data){
this.removeAllViews();
for (String str:data){
TextView view = new TextView(mContext);
view.setText(str);
view.setTextColor(COLOR_TEXT_NORMAL);
  // view.setWidth(getScreenWidth()/mVisibleItemCount);
view.setTextAlignment(TEXT_ALIGNMENT_CENTER);
this.addView(view);
}
Log.i("hugy","childCounts:"+getChildCount());
setClickEvent();
onFinishInflate();
}
  关于这个方法,要说的就是末尾的inFinishInflate方法了,这个方法的作用在于给包裹TextView的
ViewPagerIndicator重新分配大小,举个例子吧,如果你有5个tab项,你的每一个tab的宽度为1/3的
ViewPagerIndicator的宽度
,那么,如果不手动执行onFinishInflate方法的话,你能看到的条目就只有
三条,后面两条是看不见的,因为你的ViewPageIndicator的宽度固定为了屏幕的宽度,这个宽度内
只能容纳三个,多的就显示不了了,而调用了
onFinishInflate之后,重新给ViewPageIndicator分
配了宽度,因此剩余的条目也就可见了,因此手动调用此方法是不可缺少的;


  然后就是对外暴露的第二个方法setViewPager(ViewPager viewPager,int pos)了

public void setViewPager(ViewPager viewPager,int pos){
this.mViewPager = viewPager;
this.mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
scroll(position,positionOffset);
if (pageChangerListener != null){
pageChangerListener.onPageScrolled(position,positionOffset,positionOffsetPixels);
}
}

@Override
public void onPageSelected(int position) {
resetTextViewColor();
highLightTextView(position);
scroll(position,0);
if (pageChangerListener != null){
pageChangerListener.onPageSelected(position);
}
}

@Override
public void onPageScrollStateChanged(int state) {
if (pageChangerListener != null){
pageChangerListener.onPageScrollStateChanged(state);
}
}
});
mViewPager.setCurrentItem(pos);
highLightTextView(pos);
}

  highLightTextView(pos)和resetTextView()就不多说了,都是改变textView中的字体的颜色,关键的是
scroll(pos,offset)方法


下面是代码

private void scroll(int position, float positionOffset) {
mTranslationX = getWidth() / mVisibleItemCount * (position + positionOffset);
int tabWidth = getScreenWidth() / mVisibleItemCount;
if (positionOffset > 0 && position >= (mVisibleItemCount - 2)
&& getChildCount() > mVisibleItemCount)
{
if (mVisibleItemCount != 1)
{
this.scrollTo((position - (mVisibleItemCount - 2)) * tabWidth
+ (int) (tabWidth * positionOffset), 0);
} else
// 为count为1时 的特殊处理
{
this.scrollTo(
position * tabWidth + (int) (tabWidth * positionOffset), 0);
}
}

invalidate();
}
  主要就是一些逻辑的东西,这里就不多说了,说说最后一个问题,那就是,viewPager的setOnPageChangeListener 
已经被我们占用了,但是,使用还是想要监听怎么办呢,好吧,又要用到接口回调了


private OnPageChangerListener pageChangerListener;

public void setOnpageChangeListener(OnPageChangerListener pageChangerListener){
this.pageChangerListener = pageChangerListener;
}

public interface OnPageChangerListener{
void onPageSelected(int position);
void onPageScrolled(int position,float positionOffset,int positionOffsetPixels);
void onPageScrollStateChanged(int state);
}

  标准的接口回调实现,申明的方法和ViewPager的onPageChangeListener是一样的,

然后再viewPager原本的onPageChangeListener的相应方法中进行调用就好了。
ok,做到这里,一个自定义的ViewPagerIndicator就实现了。



posted on 2017-05-15 20:34  秦家十月  阅读(519)  评论(0)    收藏  举报

导航