通过博客《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; }
//当点击导航页面的时候,显示导航页的下一页 viewFlipper.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { viewFlipper.showNext(); } });
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; }
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 = (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)); } });
事实上网上看的一些关于导航页的例子,往往都会由翻页导航条或则几个圆点表明当前位于哪一页。比如实现下面的效果:
比如上图,当位于第一页的时候,该导航圆点的样式和别的不一样来区别此时位于哪一页。当然这个用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.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); } } } });