自定义控件---------ViewPager的一个小案例
来源:互联网 发布:java log4j用法 编辑:程序博客网 时间:2024/05/22 11:36
今天做了一个关于VIewPager的小demo 感觉有些难以理解所有在这里分享出来,大家需要的就带走吧
首先说下具体的功能吧
1、几张图片滑动切换 调用系统的api按系统默认的250毫秒滑动切换到下一张,自己写一个匀速切换到下一张(两种)
2、添加导航按钮,滑动到下一张的时候,导航的按钮自动切换到相应的位置上
3、在几张图片当中添加页面(这里添加一个自定义的测试页面)里面用ScrollView来模拟ListView实现效果,下面用listview来解释
大体的功能就是上面的几个吧,但是详细的细节却很重要哦,这个小demo搞了我一天了。。。。。有些地方还不是很理解,这里只代表是我个人的理解,也欢迎各位多提意见,thank!
下面先来看一张最终的效果图:
好了,现在先来说下具体怎么实现的:
这是一个自定的ViewPager:
1、写一个类继承ViewGroup,并实现其两个参数的构造方法
public class MyScorllView extends ViewGroup {public MyScorllView(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {} }
2、引入到布局文件中
<com.ittest.MyScorllView android:id="@+id/msv" android:layout_width="fill_parent" android:layout_height="wrap_content" />
3、到对应的Activity类中实例化id,并引入图片资源数组,添加到MyScorllView
public class MainActivity extends Activity {private MyScorllView msv;// 图片资源ID 数组private int[] ids = new int[] { R.drawable.a1, R.drawable.a2,R.drawable.a3, R.drawable.a4, R.drawable.a5, R.drawable.a6 }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); msv = (MyScorllView) findViewById(R.id.msv); //添加6张图片 for (int i = 0; i < ids.length; i++) {ImageView image = new ImageView(this);//设置背景资源才能完美的填充到布局中,而src则不能image.setBackgroundResource(ids[i]);//添加给MyScorllViewmsv.addView(image);} } }
4、指定子view的位置(onLayout()方法)
通过第三步,到这里MyScorllView中已获得了图片的资源@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {//用for循环动态获取位置for (int i = 0; i < getChildCount(); i++) {View view = getChildAt(i);//给当前view设置布局(左、上、右、下)view.layout(0+(i*getWidth()), 0, getWidth()+(i*getWidth()), getHeight());}}
5、给图片添加滑动事件
detector = new GestureDetector(ctx, new GestureDetector.OnGestureListener() {@Overridepublic boolean onSingleTapUp(MotionEvent e) {// TODO Auto-generated method stubreturn false;}@Overridepublic void onShowPress(MotionEvent e) {// TODO Auto-generated method stub}/** * 正在滑动时回调该方法 */@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,float distanceY) {/** * scrollBy(int i ,int j)方法让当前view的内容发生移动 * distanceX x方向移动的距离 * distanceY y方向移动的距离 */scrollBy((int) distanceX, 0);//只关心x方向上的移动/** * scrollTo(x,y) 让当前的view的内容移动到某点上 * x 目标点的x坐标 * y 目标点的y坐标 */return false;}@Overridepublic void onLongPress(MotionEvent e) {// TODO Auto-generated method stub}/** * 发生快速滑动时回调此方法 */@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) {isFling = true;System.out.println("velocityX:"+velocityX);if(velocityX > 0 && currIndex > 0){currIndex--;}else if(velocityX < 0 && currIndex < getChildCount()-1){currIndex++;}//让view移动到相应的位置上去moveToDest(currIndex);return false;}@Overridepublic boolean onDown(MotionEvent e) {// TODO Auto-generated method stubreturn false;}});#@Overridepublic boolean onTouchEvent(MotionEvent event) {//用detector来解析正常的滑动点击事件detector.onTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:firstX = lastX = (int) event.getX();isFling = false;break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP://没有快速滑动的时候才执行以下操作if(!isFling){//当前x的坐标upXint upX = (int) event.getX();//tempIndex临时下标int tempIndex = currIndex;if(upX - firstX > getWidth()/2){//滑动到上一页面tempIndex--;}else if(firstX - upX> getWidth()/2){//滑动到下一页面tempIndex++;}//让view的内容移动到子view上面去moveToDest(tempIndex);}break;}return true;}
6、让view的内容移动到子view上面去
public void moveToDest(int tempIndex) {//防止左右滑动超出边界if(tempIndex < 0){tempIndex = 0;}if(tempIndex > getChildCount()-1){tempIndex = getChildCount()-1;}//将临时的下标给当前子view的下标currIndex = tempIndex;/** * 触发改变页面的监听 */if(pageChangeListener != null){pageChangeListener.moveToDest(currIndex);}//scrollTo(currIndex*getWidth(), 0);//移动的距离 = 终点坐标 - 当前坐标int distanceX = currIndex*getWidth() - getScrollX();//开始执行动画scroller.startScroll(getScrollX(), getScrollY(), distanceX, 0,Math.abs(distanceX));/** * 刷新会导致computeScroll()的执行 */invalidate();}
@Overridepublic void computeScroll() {if(scroller.computeScrollOffset()){int curX = scroller.getCurrX();//获得当前滑动的位置//滑动到当前位置上scrollTo(curX, 0);//再刷新invalidate();}}
自定义MyScroller类(攻城狮)
/** * 用于计算速度和位移 * @author Administrator * */public class MyScroller {private int startX;private int startY;private int distanX;private int distanY;//开始执行动画的时间private long startTime;//判断动画是否执行完成private boolean isFished;public MyScroller(Context ctx){}/** * 开始滑动 * @param startX 基准点的x的坐标 * @param startY 基准点的y的坐标 * @param distanX x方向上要移动的距离 * @param distanY y方向上要移动的距离 */public void startScroll(int startX,int startY,int distanX,int distanY){this.startX = startX;this.startY = startY;this.distanX = distanX;this.distanY = distanY;//从手机开机到现在的毫秒数 但不包括手机休眠的时间this.startTime = SystemClock.uptimeMillis();//判断动画是否执行完成this.isFished = false;}/** * 系统默认运行动画执行的的总时间 3秒 */private int totalTime = 3000;private int currX;private int currY;public int getCurrX() {return currX;}public void setCurrX(int currX) {this.currX = currX;}public int getCurrY() {return currY;}public void setCurrY(int currY) {this.currY = currY;}/** * 计算当前的位移量 * @return true 还在执行动画 false 动画已经结束 */public boolean computeScrollOffset(){//判断下动画是否已经结束----如果动画已经结束了就开始计算if(isFished){return false;}//获得已经运行的时间(当前时间-开始时间)long passTime = SystemClock.uptimeMillis()-startTime;//给它设置一个默认执行动画的总时间 来判断动画是否还在执行中 进行相应的计算if(passTime < totalTime){//说明动画还在执行当中//这段时间的位置 = 这段时间的差 * 速度//速度 = 总位移 / 总时间currX = (int)(startX + passTime*distanX/totalTime);currY = (int)(startY + passTime*distanY/totalTime);}else{//动画已经执行完成//计算当前x、y的位置currX = startX + distanX;currY = startY + distanY;//将isFished设置为true 表示动画已经执行完成了isFished = true;}return true;}}
7、在布局文件中RadioGroup,并在代码中动态添加RadioButton
<RadioGroup android:id="@+id/my_radio_group" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" ></RadioGroup>
//根据msv中的view的数量 动态添加RadioButton for (int i = 0; i < msv.getChildCount(); i++) { RadioButton button = new RadioButton(this); //如果是第一个的位置就默认选中 if(i == 0){ button.setChecked(true); }else{ button.setChecked(false); } //将button的下标值设置为他的id的值 button.setId(i); //将button添加到radioGroup中 radioGroup.addView(button); }
8、在MyScrollView中添加页面改变的监听
/** * 定义接口,当页面发生改变时 触发该事件 */interface IPageChangeListener{ void moveToDest(int destIndex);}private IPageChangeListener pageChangeListener;public IPageChangeListener getPageChangeListener() { return pageChangeListener;}public void setPageChangeListener(IPageChangeListener pageChangeListener) { this.pageChangeListener = pageChangeListener;} //给msv设置改变监听 msv.setPageChangeListener(new MyScorllView.IPageChangeListener() { @Override public void moveToDest(int destIndex) { //根据切换的页面下标 找到对应的值选中 //((RadioButton)radioGroup.getChildAt(destIndex)).setChecked(true); radioGroup.check(destIndex); } });
9、给RadioButton添加改变时监听
//给RadioButton添加改变时监听 radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { //checkedId是选中的button的ID值 //在这里id是和下标一样 的数字 msv.moveToDest(checkedId); } });
10、添加测试页面test.xml(添加一个ScrollView滚动测试)这里用ScrollVIew模拟ListView
这里test.xml就不列出了 大家只需模拟在这个测试页面上有滑动点击事件即可想ScrollView和ListView都有滑动点击事件
/** * 添加测试页面 */ View view = getLayoutInflater().inflate(R.layout.test, null); msv.addView(view,2); //根据msv中的view的数量 动态添加RadioButton for (int i = 0; i < msv.getChildCount(); i++) { RadioButton button = new RadioButton(this); //如果是第一个的位置就默认选中 if(i == 0){ button.setChecked(true); }else{ button.setChecked(false); } //将button的下标值设置为他的id的值 button.setId(i); //将button添加到radioGroup中 radioGroup.addView(button); }
在测量view的大小的时候,也需要测量子view的大小
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //当ViewGroup在测量大小的时候 也应该为每个子view测量大小 否则就可能会出现问题 for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); view.measure(widthMeasureSpec, heightMeasureSpec); }}
11、给自定义ViewGroup添加事件分发
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev);}
12、自定义ViewGroup的事件中断机制
/** * 该方法默认返回false 即不中断事件的传递 * 如果返回true 即中断事件的传递 就需要我们自己来处理事件的传递 即执行onTouchEvent()方法 */@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { boolean result = false; //如果水平滑动的距离大于竖直滑动的距离就中断竖直的子view滑动事件 否则就不中断 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downInterX = (int) ev.getX(); downInterY = (int) ev.getY(); //解决detector不能收到down的事件 在这里将down的事件传递给它 //如果不将down时间传给它,那么onTouchEvent事件将不能正常执行 detector.onTouchEvent(ev); firstX = lastX = (int) ev.getX(); //将是否正在快速滑动设置为false isFling = false; break; case MotionEvent.ACTION_MOVE: int distanceX = (int) Math.abs(ev.getX()-downInterX); int distanceY = (int) Math.abs(ev.getY()-downInterY); //为了防止手机抖动distanceX设置大于10 if(distanceX > distanceY && distanceX >10){ //水平移动大于竖直移动 result = true; }else{ result = false; } break; case MotionEvent.ACTION_UP: break; } return result;};
好了 ,到这里,这个小demo就已经结束了,代码中都有详细的注释,大家看注释把,如果还是不理解的欢迎大家留言给我。。。。。
附上个人的一点难点理解:(这点是关于在测试页面点击事件触发的理解)
当点击view中的listview时,view先收到点击滑动的事件,再一层一层的往里面传递,最终传递到ListView上,而在此过程中外面层的事件,可随时消耗掉事件,这样外面层消耗了事件,里层就收不到滑动的事件,这样就被中断事件了。 当水平方向上的距离大于竖直方向上的距离时,竖直方向的滑动点击事件将被中断,也就是listview的事件被中断,这时就执行外层的滑动点击事件,反之,当滑动点击事件传递到里层的listview时,listview消耗了这个事件,这时外层的view滑动点击事件将被中断。 另外需要注意的就是,当滑动点击事件执行时,需要将down事件传递系统的手势滑动解析的工具GestureDetector,这样就可以防止滑动点击时跳动的问题。
最后给大家附上一张事件中断传递机制的图解,能更好的帮助大家理解后面的测试页面点击的部分。
Touch事件传递机制流程图
附上测试页面:
最后欢迎大家,关注我的博客,不定期分享一些有用的android方面的东西!
- 自定义控件---------ViewPager的一个小案例
- 复杂自定义控件---自定义ViewPager的实现
- 自定义ViewGroup(这里以自定义一个类似ViewPager的控件为例)
- 一个小的自定义的触摸控件,类似于联系人列表
- symbian 自定义控件遇到的一个小问题
- Android自定义的轮播图控件,基于ViewPager
- 兼容viewpager中嵌套的viewpager自定义控件
- 自定义控件实战-打造一个简约而不简单的ViewPager指示器
- Android 自定义控件之ViewGroup实例(实现一个简易的Viewpager)
- 自定义控件-ViewPager篇
- 自定义控件:Viewpager
- 自定义Tab + ViewPager控件
- 一个ViewPager的页面切换指示控件
- viewpager+fragment实现的一个小Demo
- 一个简单地ViewPager自定义的轮播图
- UIWebView控件的简易浏览器小案例
- 关于安卓自定义控件的案例
- Android自定义控件:将ViewPager封装自己的TabPager控件
- C++300面试题目大全
- 为用户控件动态增加事件
- 【HDU】4901 The Romantic Hero 01背包
- OpenCV学习之例程详解(03):搜索文件夹下所有指定类型图片并逐张显示
- Cocos2d-x 3.1.1 之 MessageBox、LabelTTF、菜单、
- 自定义控件---------ViewPager的一个小案例
- 关于内核空间地址映射问题
- 九度OJ-题目1010:A + B
- POJ 1789 Truck History(Prim)
- linux驱动____LED子系统笔记
- C++笔试题目大全
- POJ 1287 Networking(最小生成树)
- HDU 4902 Nice boat
- C# 图片和byte[]的互相转换