Android 使用ViewGroup实现ViewPager的效果
2016-05-21 14:52
309 查看
ViewPager控件可以让我们做出很多漂亮的界面,例如导航, 页面菜单等. 那么我们自己能否去实现ViewPager的效果呢? 本文将介绍如何使用ViewGroup + scrollTo + scroller实现ViewPager控件, 并且会简单地实现一个自己的scroller, 来了解学习系统提供的scroller类滑屏功能的实现思想.
首先看一下实现的效果:
定义自己的ViewPager类–MyViewPager继承ViewGroup
(1) 在MyViewPager中, 重写OnLayout方法, 在OnLayout方法中去确定子view在ViewPager中的位置:
通过getChildAt获得所有的子view, 调用子view的layout方法设置每个子view的位置, layout接收4个参数(就是左上角坐标与右下角坐标), 来确定view的大小, 如上图可以分析出每个view在MyViewPager中的位置, 例如第2个view的位置是:
[getWidth(), 0, 2 * getWidth(), getHeight()] , getWidth 与 getHeight为MyViewPager的宽高. 我们可以看到变化的只有横坐标.
(2) 重写onTouchEvent方法, 此方法处理屏幕触摸事件, 在这里使用用户手势识别工具类GestureDetector来处理action.move事件, 在OnGestureListener的onScroll中处理move事件, onScroll方法的参数distanceX, 就是工具类帮我们计算好的手指在屏幕上x轴方向移动的距离, 然后就可以很方便的使用此参数, 调用scrollBy(x, y)方法移动视图, 让视图偏移(x, y). 这样就实现了MyViewPager随手指移动而移动.
2.使用MyViewPager. 并给MyViewPager设置子view
布局文件:
给MyViewPager设置6张图片. 现在效果图如下:
3.现在图片可以跟随手指滑动而滑动, 但是还不能自动地切换, 图片只能停在你移动到的地方, 如果想实现切换的效果, 还需要我们自己去处理touch事件, 在onTouchEvent中继续添加代码:
定义两个成员变量, firstX, currPos来记录点击屏幕时的点与当前显示在屏幕上的子view. 在touch_up事件中处理: 当在屏幕上滑动的距离大于屏幕的一半切换视图, 否则留在当前视图. 在MoveToDest方法中调用scrollTo来实现.
现在的效果为:
4.在ViewPager中, 我们去切换视图时, 并不是瞬间完成, 而是有个过程.
我们实现MyScroller类来实现滑动过程,新建MyScroller类:
在startScroll方法中记录下当前滑动点坐标与目标点坐标, 并记录下当前时间. 然后在computeScrollOffset中开始去更新当前的滑动的坐标.
接下来使用MyScroller来实现滑动过程,在MyViewPager类中定义MyScroller类成员变量并去使用它来实现滑动过程:
然后修改moveToDest方法,不去使用ScrollTo来实现移动,使用MyScroller来实现:
现在的效果你会看到视图切换时就会有个过程而不是很快就完成了:
现在视图的切换是匀速的,如果想达到ViewPager那种加速效果,将MyScroller改成系统提供的Scroller类即可,只需要将private MyScroller myScroller; 改为private Scroller myScroller; 其他都不需要动就可以实现加速效果, 因为我们的MyScroller类的接口和系统Scroller接口一样。
5.现在MyViewPager中的View全为ImageView, 那么向MyViewPager中添加一个布局(包括一个button和listView)来看一下是否同样的支持滑动效果.
(1)布局文件list_view.xml:
(2)我们将布局文件加载到MyViewPager的第四个view
这个时候你运行会发现这个布局不会显示出来, 因为这个时候我们没有在MyViewPager中去调用布局控件的OnMeasure方法, 让其去测量子View的大小, 导致布局view没有显示出来,所以我们需要在MyViewPager中去重载OnMeasure方法,去调用每个子view的measure,绘制子view的大小。
现在的效果为:
现在我们发现,当滑动到布局文件中,在listview上可以上下滑,但是此时左右滑动失效了,不能左右滑动。
原因因为Touch事件的传递导致, 我们知道touch事件是从父view到子view一层一层传递,当我们左右滑动时,此事件由MyViewPager一层层传递到listview,但是listview并不支持(消费)此事件,所以导致左右滑动不起作用了, 现在我们重写onInterceptTouchEvent,来去判断touch事件,如果是左右滑动事件那么就去中断此事件,不让其再往下传递,我们去处理消费它。
现在效果如下:
此时还缺少一点就是开放一个接口让外部来使用, 比如导航界面,点击某一个点,会直接跳到那个点指定的页面。
在MyViewPager中添加接口:
然后在moveToDest方法中去判断此监听器是否为空,不为空就调用其接口。
至此, 使用ViewGroup实现ViewPager效果完成, 如有问题可以留言。
源码下载地址:
http://download.csdn.net/detail/lbcab/9536961
首先看一下实现的效果:
定义自己的ViewPager类–MyViewPager继承ViewGroup
public class MyViewPager extends ViewGroup { private Context context; //手势识别工具类 private GestureDetector detector; public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; initView(); } private void initView() { detector = new GestureDetector(context, new GestureDetector.OnGestureListener() { @Override public boolean onDown(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } //处理移动事件 @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //将当前视图内容偏移(x , y)个单位,可视区域也跟着偏移(x,y)个单位 //也就是说让视图跟着鼠标移动, distanceX为鼠标在屏幕上移动的距离 scrollBy((int) distanceX, 0); return false; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } }); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //设置每个子view的位置 for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); view.layout(i * getWidth(), 0, (i + 1) * getWidth(), getHeight()); } } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); detector.onTouchEvent(event); return true; } }
(1) 在MyViewPager中, 重写OnLayout方法, 在OnLayout方法中去确定子view在ViewPager中的位置:
通过getChildAt获得所有的子view, 调用子view的layout方法设置每个子view的位置, layout接收4个参数(就是左上角坐标与右下角坐标), 来确定view的大小, 如上图可以分析出每个view在MyViewPager中的位置, 例如第2个view的位置是:
[getWidth(), 0, 2 * getWidth(), getHeight()] , getWidth 与 getHeight为MyViewPager的宽高. 我们可以看到变化的只有横坐标.
(2) 重写onTouchEvent方法, 此方法处理屏幕触摸事件, 在这里使用用户手势识别工具类GestureDetector来处理action.move事件, 在OnGestureListener的onScroll中处理move事件, onScroll方法的参数distanceX, 就是工具类帮我们计算好的手指在屏幕上x轴方向移动的距离, 然后就可以很方便的使用此参数, 调用scrollBy(x, y)方法移动视图, 让视图偏移(x, y). 这样就实现了MyViewPager随手指移动而移动.
2.使用MyViewPager. 并给MyViewPager设置子view
public class MainActivity extends AppCompatActivity { private MyViewPager myViewPager; private int[] imgIds = new int[] { R.mipmap.a, R.mipmap.b, R.mipmap.c, R.mipmap.d, R.mipmap.e, R.mipmap.f }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myViewPager = (MyViewPager) findViewById(R.id.my_viewpager); for (int i = 0; i < imgIds.length; i++) { ImageView imageView = new ImageView(this); imageView.setImageResource(imgIds[i]); myViewPager.addView(imageView); } } }
布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.myviewpager.MainActivity"> <com.myviewpager.MyViewPager android:id="@+id/my_viewpager" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
给MyViewPager设置6张图片. 现在效果图如下:
3.现在图片可以跟随手指滑动而滑动, 但是还不能自动地切换, 图片只能停在你移动到的地方, 如果想实现切换的效果, 还需要我们自己去处理touch事件, 在onTouchEvent中继续添加代码:
//标记当前显示在屏幕上的图片 private int currPos = 0; //记录按下时的横坐标 private int firstX = 0; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); detector.onTouchEvent(event); //添加下面的代码 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: firstX = getScrollX(); //获得按下去时的横坐标 break; case MotionEvent.ACTION_UP: //判断显示哪个子view, 如果滑动大于父控件的一半切换子view int tmpPos = currPos; if ((getScrollX() - firstX) > getWidth() / 2) { //向左滑动 tmpPos++; } else if ((firstX - getScrollX()) > getWidth() / 2) { //向右滑动 tmpPos--; } MoveToDest(tmpPos); //切换到指定的图片 break; default: break; } return true; } public void MoveToDest(int tmpPos) { //确定currPos的值, 保证currPos的范围在[0, getChildCount() - 1] currPos = tmpPos > 0 ? tmpPos : 0; currPos = currPos < getChildCount() - 1 ? currPos : (getChildCount() - 1); //将视图内容偏移至(x , y)坐标处,可视区域位于(x , y)坐标处 scrollTo(currPos * getWidth(), 0); }
定义两个成员变量, firstX, currPos来记录点击屏幕时的点与当前显示在屏幕上的子view. 在touch_up事件中处理: 当在屏幕上滑动的距离大于屏幕的一半切换视图, 否则留在当前视图. 在MoveToDest方法中调用scrollTo来实现.
现在的效果为:
4.在ViewPager中, 我们去切换视图时, 并不是瞬间完成, 而是有个过程.
我们实现MyScroller类来实现滑动过程,新建MyScroller类:
public class MyScroller { private Context context; private int disX; private int startY; private int startX; private int disY; private long startTime; //开始动画时间 private boolean isFinish; //标志是否结束动画 //默认运行时间,500ms private int duration = 500; //当前绘制所在的X位置 private long currX; //当前绘制所在的Y位置 private long currY; public MyScroller(Context context) { this.context = context; } public long getCurrY() { return currY; } public void setCurrY(long currY) { this.currY = currY; } public long getCurrX() { return currX; } public void setCurrX(long currX) { this.currX = currX; } /** * 开始移动 * @param startX 开始时的x坐标 * @param startY 开始时的y坐标 * @param disX x方向要移动的距离 * @param disY y方向要移动的距离 */ public void startScroll(int startX, int startY, int disX, int disY) { this.startX = startX; this.startY = startY; this.disX = disX; this.disY = disY; this.startTime = SystemClock.uptimeMillis(); this.isFinish = false; } /** * 计算当前的运行状况 * @return * true 还在运行 * false 运行结束 */ public boolean computeScrollOffset() { if (isFinish) { return false; } //获得绘制时的时间 long passTime = SystemClock.uptimeMillis() - startTime; //如果时间还在允许的范围内 if (passTime <= duration) { currX = startX + disX * passTime / duration; currY = startY + disY * passTime / duration; } else { //绘制运行结束 currX = startX + disX; currY = startY + disY; isFinish = true; } return true; } }
在startScroll方法中记录下当前滑动点坐标与目标点坐标, 并记录下当前时间. 然后在computeScrollOffset中开始去更新当前的滑动的坐标.
接下来使用MyScroller来实现滑动过程,在MyViewPager类中定义MyScroller类成员变量并去使用它来实现滑动过程:
private MyScroller myScroller; //在initView中初始化 private void initView() { ............... myScroller = new MyScroller(context); ............... }
然后修改moveToDest方法,不去使用ScrollTo来实现移动,使用MyScroller来实现:
public void moveToDest(int nextId) { .................. //不使用scrollTo来实现移动 // scrollTo(currId * getWidth(), 0); //获得移动的距离,移动距离等于最终位置-当前位置 int distance = currId * getWidth() - getScrollX(); myScroller.startScroll(getScrollX(), 0, distance, 0); //会导致computeScroll方法执行 invalidate(); } @Override public void computeScroll() { //计算当前绘制状况,进行绘制 if (myScroller.computeScrollOffset()) { int nowX = (int) myScroller.getCurrX(); scrollTo(nowX, 0); invalidate(); } }
现在的效果你会看到视图切换时就会有个过程而不是很快就完成了:
现在视图的切换是匀速的,如果想达到ViewPager那种加速效果,将MyScroller改成系统提供的Scroller类即可,只需要将private MyScroller myScroller; 改为private Scroller myScroller; 其他都不需要动就可以实现加速效果, 因为我们的MyScroller类的接口和系统Scroller接口一样。
5.现在MyViewPager中的View全为ImageView, 那么向MyViewPager中添加一个布局(包括一个button和listView)来看一下是否同样的支持滑动效果.
(1)布局文件list_view.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/list_layout" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="70dp" android:layout_margin="10dp" android:text="button"/> <ListView android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp"/> </LinearLayout>
(2)我们将布局文件加载到MyViewPager的第四个view
private LinearLayout listLayout; private ListView listview; private String[] datas = { "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry", "Cherry", "Mango" }; @Override protected void onCreate(Bundle savedInstanceState) { ............ //添加下面代码 listLayout = (LinearLayout) LayoutInflater.from(getApplicationContext()) .inflate(R.layout.list_view, null); listview = (ListView) listLayout.findViewById(R.id.listview); ArrayAdapter<String> adapter = new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_list_item_1, datas); listview.setAdapter(adapter); for (int i = 0; i < ids.length; i++) { if (i == 3) { //将布局文件添加到第四个位置 msv.addView(listLayout); } else { ImageView imageView = new ImageView(this); imageView.setBackgroundResource(imgIds[i]); msv.addView(imageView); } } }
这个时候你运行会发现这个布局不会显示出来, 因为这个时候我们没有在MyViewPager中去调用布局控件的OnMeasure方法, 让其去测量子View的大小, 导致布局view没有显示出来,所以我们需要在MyViewPager中去重载OnMeasure方法,去调用每个子view的measure,绘制子view的大小。
//绘制每个子控件的尺寸 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); view.measure(widthMeasureSpec, heightMeasureSpec); } }
现在的效果为:
现在我们发现,当滑动到布局文件中,在listview上可以上下滑,但是此时左右滑动失效了,不能左右滑动。
原因因为Touch事件的传递导致, 我们知道touch事件是从父view到子view一层一层传递,当我们左右滑动时,此事件由MyViewPager一层层传递到listview,但是listview并不支持(消费)此事件,所以导致左右滑动不起作用了, 现在我们重写onInterceptTouchEvent,来去判断touch事件,如果是左右滑动事件那么就去中断此事件,不让其再往下传递,我们去处理消费它。
/** * 返回true, 中断事件,执行自己的onTouchEvent方法 * 返回false, 默认处理,不中断事件, 也不会执行自己的onTouchEvent方法 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean result = false; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //解决点击图片时跳动的bug detector.onTouchEvent(ev); firstX = (int) ev.getX(); firstY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: int disx = (int) Math.abs(ev.getX() - firstX); //不管是左右移,只判断是否左右移动 int disy = (int) Math.abs(ev.getY() - firstY); //竖直方向移动距离 //判断是否为水平方向移动,disx > 10 防止手指按住屏幕抖动 if(disx > disy && disx > 10) { result = true; } else { result = false; } break; case MotionEvent.ACTION_UP: break; default: break; } return result; }
现在效果如下:
此时还缺少一点就是开放一个接口让外部来使用, 比如导航界面,点击某一个点,会直接跳到那个点指定的页面。
在MyViewPager中添加接口:
//监听器对象 private MyPagerChangedListener listener; public MyPagerChangedListener getListener() { return listener; } public void setListener(MyPagerChangedListener listener) { this.listener = listener; } //页面改变时的监听接口 public interface MyPagerChangedListener{ void moveToDest(int currId); }
然后在moveToDest方法中去判断此监听器是否为空,不为空就调用其接口。
//移动到指定的子控件上 public void moveToDest(int nextId) { .................. //触发listener事件 if (listener != null) { listener.moveToDest(currId); } .............. }
至此, 使用ViewGroup实现ViewPager效果完成, 如有问题可以留言。
源码下载地址:
http://download.csdn.net/detail/lbcab/9536961
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories