自定义ViewPager
来源:互联网 发布:stm32和51编程一样吗 编辑:程序博客网 时间:2024/06/05 10:24
由于公司要求我做一个luancher项目并分为3页。考虑到以后要应对需求的多样性,那么就不考虑去用SDK原生的了,也就只能自己写一个出来。那么 今天将跟大家分享 如何自定义一个简陋的ViewPager。功能上还有一些不足,以后再慢慢优化,希望大家多多指教。下面开始为大家带来自定义ViewPager的实现。
好了,首先我们自定义一个MyViewPager继承于ViewGroup,由于luancher各个界面中要放很多一些子控件,所以这边肯定要继承于ViewGroup了,下面来说说所要实现的一些相关的方法。还是先上代码,然后逐一击破吧。
package com.example.myviewpager;
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
public class MyViewPager extends ViewGroup {
private Context mContext;
/**
* 滑动工具类
*/
private Scroller myScroller;
/**
* 手势识别的工具类
*/
private GestureDetector detector;
/**
* 是否为快速滑动事件
*/
private boolean isFling;
/**
* 当前的ID值 显示在屏幕上的子View的下标
*/
private int currId = 0;
/**
* down事件时的x坐标
*/
private int firstX = 0;
/**
* 界面发生改变时的回调接口
*/
private MyPageChangedListener pageChangedListener;
public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); this.mContext = context; initData();}private void initData() { myScroller = new Scroller(mContext); detector = new GestureDetector(mContext, new OnGestureListener() { @Override /** * 手指离开触摸屏的那一刹那时触发 */ public boolean onSingleTapUp(MotionEvent e) { return false; } @Override /** * 如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动时触发 */ public void onShowPress(MotionEvent e) { } @Override /** * 手指在触摸屏上滑动时触发 */ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { /** * 移动当前view内容 移动一段距离 disX X方向移的距离 为正是,view向左移动,为负时,图片向右移动 disY * Y方向移动的距离 */ scrollBy((int) distanceX, 0); return false; } @Override /** * 手指长按触摸屏,并且没有松开时触发 */ public void onLongPress(MotionEvent e) { } @Override /** * 手指在触摸屏上迅速移动,并松开的动作,即快速滑动时触发 */ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { isFling = true; if (velocityX > 0 && currId > 0) { // 快速向右滑动 currId--; } else if (velocityX < 0 && currId < getChildCount() - 1) { // 快速向左滑动 currId++; } moveToDest(currId); return false; } @Override /** * 用户按下屏幕时触发 */ public boolean onDown(MotionEvent e) { return false; } });}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // int size = MeasureSpec.getSize(widthMeasureSpec); // int mode = MeasureSpec.getMode(widthMeasureSpec); // 设置子view的测量大小 for (int i = 0; i < getChildCount(); i++) { View v = getChildAt(i); v.measure(widthMeasureSpec, heightMeasureSpec); }}@Override/** * 对子view进行布局,确定子view的具体位置,changed 若为true ,说明布局发生了变化 * * l\t\r\b\ 则是指当前viewgroup 在其父view中的位置 * * 另外父view 会根据子view的需求,和自身的情况,来综合确定子view的位置 */protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 0; i < getChildCount(); i++) { // 取得下标为I的子view View view = getChildAt(i); // 指定子view的位置 , 左,上,右,下,是指在viewGround坐标系中的位置 // view.getWidth() 得到view的真实的大小。 // view.getMeasuredWidth() 得到view的测量的大小 view.layout(0 + i * getWidth(), 0, getWidth() + i * getWidth(), getHeight()); }}@Override/** * view挂载到窗体时触发 */protected void onAttachedToWindow() { super.onAttachedToWindow();}@Overridepublic boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); return true;}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { detector.onTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: firstX = (int) ev.getX(); break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: if (!isFling) { int nextId = 0; if (ev.getX() - firstX > getWidth() / 2) { nextId = currId - 1; } else if (firstX - ev.getX() > getWidth() / 2) { nextId = currId + 1; } else { nextId = currId; } moveToDest(nextId); } isFling = false; break; } return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: // break; return true; case MotionEvent.ACTION_UP: break; } return super.onInterceptTouchEvent(ev);}/** * 移动到指定的界面 * * @param <br/> * nextId 界面的下标 */public void moveToDest(int nextId) { /* * 对 nextId 进行判断 ,确保 是在合理的范围 即 nextId >=0 && next <=getChildCount()-1 */ currId = (nextId > 0) ? nextId : 0; currId = (currId <= getChildCount() - 1) ? currId : (getChildCount() - 1); // 瞬间移动 // scrollTo(currId*getWidth(), 0); // 设置回调 if (pageChangedListener != null) { pageChangedListener.moveToDest(currId); } // 最终的位置 - 现在的位置 = 要移动的距离 int distance = currId * getWidth() - getScrollX(); // 设置滑动这段距离所运行的时间,开始缓慢滑动效果 myScroller.startScroll(getScrollX(), 0, distance, 0, Math.abs(distance)); /* * 刷新当前view onDraw()方法 的执行 */ invalidate();}@Override/** * invalidate() 会导致 computeScroll()这个方法的执行 */public void computeScroll() { if (myScroller.computeScrollOffset()) { // 获取滑动过后的当前X坐标 int newX = (int) myScroller.getCurrX(); scrollTo(newX, 0); invalidate(); }}public void setOnPageChangedListener(MyPageChangedListener pageChangedListener) { this.pageChangedListener = pageChangedListener;}/** * 页面改时时的监听接口 */public interface MyPageChangedListener { void moveToDest(int currid);}
}
这个类就这么多东西了,没其他的。
那么首先是重写构造函数,带两个参数的构造函数,这样即可在XML中应用,如果是实现只带一个参数的构造则要代码new出来而不能直接在XML中应用,在构造方法中先获取上下文context,然后初始化一些数据,这边用到了2个工具类:一个是Scroller滑动辅助工具类,另外一个则是GestureDetector手势识别的工具类,GestureDetector这个估计很多人都用过,具体该方法在什么时候回调在代码中已注释,Scroller则是在后面滑动的时候要用到的。
然后就是onMeasure方法对子view进行大小的测量
还有一点作为父view控件,需要对子view进行布局layout,设置子view的位置,否则将子view无法显示出来
上面的都是最基本的了。
好了,下面进入重点:
那么体验SDK Viewpager时是可以左右滑动,并且快速滑动时可以进入到下一界面。根据这一点,我这边重写了几个方法
dispatchTouchEventon(),TouchEvent(),onInterceptTouchEvent(),computeScroll()。就这4个重要的函数了!另外还有一个moveToPageIndex()是移动当前屏幕到第几页的。
首先用户手指按下(down),移动(move),抬起(up)这些事件我们都要捕获到拿来用,同时也要分发给各个子view,既然这样,那我这边是这样来做的。
在TouchEvent中返回true,表示后续事件统统拿到(这边如果对于Android事件分发机制不是很熟悉的同学请去查阅相关资料)。
dispatchTouchEventon中(用户操作时,最先执行的就是这个方法,消息分发)我们将事件分发到GestureDetector这个类,一部分事件在GestureDetector中去处理,同时记得要return super.dispatchTouchEvent(ev); 将事件交由父类分发到各个子view,这里不能return true(如果return true表示后续不在分发接下来的事件)否则子View将无法获取分发的事件。
然后对于滑动屏幕这个事件就交由GestureDetector去处理,那么dispatchTouchEventon我们要做的就是处理up事件,当用户手指离开屏幕时, 如果不是快速滑动的情况下,我们要将屏幕移动到第几页面中,而且还要判断页面的ID,不能超过页面总数,也不能小于0。
moveToPageIndex()则是要与computeScroll()结合起来讲解,首先moveToPageIndex中我们用到了Scroller工具类,目的就要实现一种带有动画效果的滑动,如果我们在moveToPageIndex中直接用scrollTo函数就会产生不好的用户体验,界面切换的时候,总是瞬时的,我们要让它有一定的切换时间。具体API代码中也有注释。
最后一个就是onInterceptTouchEvent,见名知意,该方法是用来拦截消息的,一旦拦截,那么后续分发的事件都拦截,我这边主要使用在这边,如下图:
就是在移动屏幕到其它页面时,如果手指是在该button上的话会同事触发该button的onclick事件,那么怎么才能阻止这种情况的发生?还记得我在第一篇博文中提到过完整的一次onclick是由down跟up组成的吧,那么问题就好办了,我们用手指在屏幕上滑动时我们就拦截move事件,move事件拦截后,则后续的事件(up)将不会再分发给各个子view控件,这样就不会同事触发button的onclick事件了。
还有就是注册一个回调接口监听界面的切换。
最后就是在xml布局文件中应用
以及在activity中注册界面切换的监听
好了!到这里基本上可以说是大功告成。
源码下载
- viewpager自定义
- 自定义Viewpager
- 自定义ViewPager
- 自定义viewpager
- 自定义ViewPager
- 自定义viewpager
- 自定义ViewPager
- 使用ViewPager.PageTransformer自定义ViewPager
- ViewPager自定义切换动画
- 自定义ViewPager切换动画
- 自定义ViewPager指示器
- 自定义viewpager切换动画
- ViewPager自定义指示条
- 自定义控件-ViewPager篇
- 自定义ViewPager的高度
- Android--自定义tab+viewPager
- 自定义viewpager指示器
- 自定义TabHost+Fragment+ViewPager
- Android--HelloWorld
- Android数据库SQLite操作详解及LitePal用法详解(三)
- ubuntu搭建ftp服务器
- 时间转换
- 漏洞探秘
- 自定义ViewPager
- Java常用类源码分析_Date类
- UVALive 3942(使用数据结构trie树加速dp)
- 【慕课笔记】5-3 字符流的过滤器
- kidd风的IOS日志之IOS-API概述
- Ruby On Rails使用Bootstrap框架
- Android服务器配置编译指南
- 欢迎使用CSDN-markdown编辑器
- C语言实现通讯录