扩展ViewFlipper做导航页(一)

通过博客《ViewFlipper简单学习笔记 》可以知道ViewFlipper的工作原理,我们可以调用showNext()或者showPrevious()两个方法来实现页面的切换,但是这个组件并没有对事件没有做处理,不像ViewPager这个组件那样重写了onInterceptTouchEvent(主要负责事件的拦截)和onTouchEvent(主要负责事件的处理)方法,可以让用户手指滑动的时候是在页面滚动翻页的效果。(关于Android的事件的处理机制可以参考博主的另外两篇博客《 android事件拦截处理机制详解》和《从源码角度分析android事件分发处理机制 》来加深理解)。在Android事件原理的基础上所以ViewFlippler响应手指事件来翻页的第一个简单版(v.1.0)本就出炉了,关键代码如下:

//该方法位于MyViewFlipper extends ViewFlipper或者ViewAnimator(ViewAnimator为ViewFlipper的父类)
@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		int action = event.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			 showNext();
			break;
		}
      return true;
	}

该版本很简单,就是在重写onTouchEvent,该方法很简单,就是简单的响应down事件:用户点击手机屏幕的时候调用父类ViewFlipper的showNext方法,自动显示下一页;其实换种思路,如果只是响应down事件来显示来显示下一页的话,完全不用重新定义ViewFlipper或者ViewAnimator,设置ViewFlipper或者ViewAnimator的onClickListener完全可以实现同样的效果:

//当点击导航页面的时候,显示导航页的下一页
viewFlipper.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				viewFlipper.showNext();
				
			}
		});

简单的功能肯定不能适应各种情况,比如这个该版本就只能查看下一页而没办法查看上一页,所以需要继续扩展功能,代码还需要修改,在做了简单的修改之后onTouchEvent代码如下:

     private Context ctx;//记录Context对象

    public MyViewFlipper(Context context) {
        super(context);
        ctx = context;
        // TODO Auto-generated constructor stub
    }

    public MyViewFlipper(Context context, AttributeSet attrs) {
        super(context, attrs);
        ctx = context;
        // TODO Auto-generated constructor stub
    }

          private float mLastMotionX;
         //这个变量解决手指一直移动的时候,连续翻页的问题
        private boolean flag = false;
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		int action = event.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			mLastMotionX = event.getX();
			break;
		case MotionEvent.ACTION_MOVE:
			final float x = event.getX();
			final float dx = x - mLastMotionX;
			if (dx > 0) {// 手指从左到右滑动
				if (!flag && getDisplayedChild() != 0) {
					setInAnimation(AnimationUtils.loadAnimation(ctx,
							R.anim.push_right_in));
					setOutAnimation(AnimationUtils.loadAnimation(ctx,
							R.anim.push_right_out));
					showPrevious();
				}
			} else {// 手指从右到左滑动
				if (!flag && getDisplayedChild() != getChildCount() - 1) {
					setInAnimation(AnimationUtils.loadAnimation(ctx,
							R.anim.push_left_in));
					setOutAnimation(AnimationUtils.loadAnimation(ctx,
							R.anim.push_left_out));
					showNext();
				}
			}
			if (dx != 0) {
				flag = true;
			}
			break;
		case MotionEvent.ACTION_UP:
			flag = false;
			break;
		}
		return true;
	}

如此,简单的左右滑动实现左右翻页的I效果实现了,而且都添加了左右滑动的动画效果。实现思路很简单:

1)判断手指滑动的距离,当移动距离是负值的时候,就显示下一页;当移动的距离是正直的时候就显示上一页。注意因为ViewFlipper是循环显示的,也就是说当一直调用showNext()方法显示下一页当显示到最后一页的时候如果再次调用showNext()方法的话就会显示第一页了,所以在处理翻页的时候要特别判断一下。当然调用showPrevious方法显示上一页也是一样的道理。

2)这里有一个变量flag(真不知道该为此变量如何命名,就简单的写成flag)很重要,在满足滑动条件的时候设为true,有isFlipping的意思或者说是否正在进行翻页的动作的意思,如果为false,说明还没有进行翻页操作,就进行翻页。设置此变量主要是用来解决随着手指的滑动一直不停的调用showNext或者showPrevious方法造成的页面闪烁问题。需要注意的是在手指抬起的时候这个flag要重新设置成false。

3)在这里用一个ctx变量来存储Context,供AnimationUtils.loadAnimation使用,其实也可以用View自带的getContext()方法,因为在Android解析xml文件创建View对象的时候已经为这个View或者ViewGroup创建了初始化了Context对象。

当然上面的写法有个缺点,就是把翻页动画写死了,这种很明显扩展性不是很好,为此我把上述代码修改为如下方式:

    public interface TurnPageAnimation{
    	/**
    	 * 显示下一页的动画
    	 */
    	void showNextAnimation();
    	/**
    	 * 显示上一页的动画
    	 */
    	void showPreviousAnimation();
    }
    private TurnPageAnimation mTurnPageAnimation;
	private float mLastMotionX;
	private boolean flag = false;
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		int action = event.getAction();
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			mLastMotionX = event.getX();
			break;
		case MotionEvent.ACTION_MOVE:
			final float x = event.getX();
			final float dx = x - mLastMotionX;
			if (dx > 0) {// 手指从左到右滑动
				if (!flag && getDisplayedChild() != 0) {
					if(mTurnPageAnimation!=null) {
						mTurnPageAnimation.showPreviousAnimation();
					}
					showPrevious();
				}
			} else {// 手指从右到左滑动
				if (!flag && getDisplayedChild() != getChildCount() - 1) {
					if(mTurnPageAnimation!=null) {
						mTurnPageAnimation.showNextAnimation();
					}
					showNext();
				}
			}
			if (dx != 0) {
				flag = true;
			}
			break;
		case MotionEvent.ACTION_UP:
			flag = false;
			break;
		}
		return true;
	}
	
	public void setTurnPageAnimation(TurnPageAnimation mTurnPageAnimation) {
		this.mTurnPageAnimation = mTurnPageAnimation;
	}

	public void showAnimation(Animation in,Animation out) {
		if(in!=null) {
			setInAnimation(in);
		}
		if(out !=null) {
			setOutAnimation(out);
		}
	}
	

这样在初始化这个ViewFlipper的时候就可以这样设置:

viewFlipper = (MyViewFlipper)findViewById(R.id.viewFlipper);
		
		viewFlipper.setTurnPageAnimation(new TurnPageAnimation() {
			
			@Override
			public void showPreviousAnimation() {
				viewFlipper.showAnimation(AnimationUtils.loadAnimation(viewFlipper.getContext(),
						R.anim.push_right_in), AnimationUtils.loadAnimation(viewFlipper.getContext(),
								R.anim.push_right_out));
				
			}
			
			@Override
			public void showNextAnimation() {
				viewFlipper.showAnimation(AnimationUtils.loadAnimation(viewFlipper.getContext(),
						R.anim.push_left_in), AnimationUtils.loadAnimation(viewFlipper.getContext(),
								R.anim.push_left_out));
			}
		});

到此位置简单的ViewFlipper就算全程了,只是简单的写了重写了onTouchEvent事件,其实这也是个自定义View,在有些人看来自定义View很神秘很复杂,其实只要弄清需求,扩展相应的功能就是了,有的需要重新写onMeasrue有的需重写onLayout甚至onDraw不一而足,当然有的确实很复杂LZ也不知道怎么实现,正在一点点模仿和积累。( demo点击此处下载)。

事实上网上看的一些关于导航页的例子,往往都会由翻页导航条或则几个圆点表明当前位于哪一页。比如实现下面的效果:

扩展ViewFlipper做导航页(一)_第1张图片

比如上图,当位于第一页的时候,该导航圆点的样式和别的不一样来区别此时位于哪一页。当然这个用ViewPager很简单就能实现这种效果的导航页,本篇博客的目的主要是改造一下ViewFlipper,来体会一下Android自定义空间的乐趣,功能不一定很强大,贵在理解其中的原理。言归正传,下面开始简单的由ViewFlipper实现这种效果的方式(该版本暂且为V3.0,在V2.0(上面的demo)的基础上做了简单修改,包括xml配置文件也做了简单修改)


实现上面的功能,我们需要知道什么时候翻页,以及当前页的索引;所以LZ模仿了ViewPager,也为自己的ViewFlipper提供了OnPageChangeListener接口,来监听分页事件:

public interface OnPageChangeListener{
	   /**
	    * 当翻页的时候调用这个方法
	    * @param pageIndex 当前页索引
	    */
	   public void onPageSelected(int pageIndex);
	}
	private OnPageChangeListener mOnPageChanageListener;
	public void setOnPagerChanagerListener(OnPageChangeListener pageChanagerListener) {
		this.mOnPageChanageListener = pageChanagerListener;
	}
	
如上面的博文所说在手指移动的距离!=0的时候说明开始执行showNext或者showPrevious方法开始翻页,同时ViewFliper本身也提供了getDisplayedChild()方法来表明当前页是第几页,所以我们在如下代码中最翻页动作做了监听(详见上文):

if (dx != 0) {
				if(!flag) {
					if(mOnPageChanageListener!=null) {
						mOnPageChanageListener.onPageSelected(getDisplayedChild());
					}
				}
				flag = true;
			}
下面就可以再你的页面中初始化图例所示的圆点点了:

viewGroup = (ViewGroup) findViewById(R.id.viewGroup);
imageViews = new ImageView[viewFlipper.getChildCount()];
		for (int i = 0; i < imageViews.length; i++) {
			imageView = new ImageView(this);
			imageView.setLayoutParams(new LayoutParams(40, 40));
			imageView.setPadding(20, 0, 20, 0);
			imageViews[i] = imageView;
			if (i == 0) {
				// 默认选中第一张图片
				imageViews[i]
						.setBackgroundResource(R.drawable.page_indicator_focused);
			} else {
				imageViews[i].setBackgroundResource(R.drawable.page_indicator);
			}

			viewGroup.addView(imageViews[i]);
		}

初始化这些圆点点和ViewFlipper之后就可以为ViewFlipper初始化翻页监听,代码如下:

viewFlipper.setOnPagerChanagerListener(new OnPageChangeListener() {

			@Override
			public void onPageSelected(int pageIndex) {
				for (int i = 0; i < imageViews.length; i++) {
					imageViews[pageIndex]
							.setBackgroundResource(R.drawable.page_indicator_focused);

					if (pageIndex != i) {
						imageViews[i]
								.setBackgroundResource(R.drawable.page_indicator);
					}
				}
			}
		});

到此为止,上图中所示的导航效果就实现完毕了,( demo代码点此);虽然说功能不是很强大,而且很多地方还有扩展的地方,比如翻页体验来说就没有ViewPager实现的效果好,当然这和ViewPager的滚动原理是分不开的,这点以后会继续深入研究,敬请期待吧,当然如果本篇博文有什么不正确的地方,还请指正,共同学习。





你可能感兴趣的:(android)