Android工具类:实现左右滑动页面
2014-10-04 23:23
369 查看
本文介绍Android中实现左右滑动切换页面的效果
网络上实现的方法多种多样,前段时间稍微研究过,这几天又整理了一下,拿出来分享希望对码友们会有帮助。
这里把实现方法封装成一个继承于ViewGroup的类(DragableLuncher),便于复用。
如标题写的,我把它封装成了一个工具类,因此先介绍使用方式。
1.使用方式
在DragableLuncher内通过include标签添加设计好的xml文件,从上往下对应屏幕的从左往右。
实际中,程序自动将一张一张同高同宽的page从左右往右紧密排列,手机屏幕作为显示窗口每次显示一个page,DragableLuncher根据用户的滑动手势来移动这个窗口。
相信很多人都很熟悉这个原理。改成按键切换页面后,使用在一些不需要Intent传递参数的跳转,对程序运行效率应该会有帮助。也可以延伸出“侧边导航”效果的实现。
2.实现代码
代码中的注释是我阅读时候的笔记,有兴趣研究实现方法的码友们可以参考下,在此基础上试着添加更多炫酷的效果。
也可以不用管内部实现方法,在自己的工程下创建一个类(DragableLuncher),然后直接copy代码进去,依照上面的使用方式使用即可。
网络上实现的方法多种多样,前段时间稍微研究过,这几天又整理了一下,拿出来分享希望对码友们会有帮助。
这里把实现方法封装成一个继承于ViewGroup的类(DragableLuncher),便于复用。
如标题写的,我把它封装成了一个工具类,因此先介绍使用方式。
1.使用方式
在DragableLuncher内通过include标签添加设计好的xml文件,从上往下对应屏幕的从左往右。
实际中,程序自动将一张一张同高同宽的page从左右往右紧密排列,手机屏幕作为显示窗口每次显示一个page,DragableLuncher根据用户的滑动手势来移动这个窗口。
相信很多人都很熟悉这个原理。改成按键切换页面后,使用在一些不需要Intent传递参数的跳转,对程序运行效率应该会有帮助。也可以延伸出“侧边导航”效果的实现。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity" > <com.example.testdemo.DragableLuncher xmlns:android="http://schemas.android.com/apk/res/android" xmlns:guojs="http://schemas.android.com/apk/res/com.example.testdemo" android:layout_width="fill_parent" android:layout_height="fill_parent" > <!-- Our page one --> <include android:id="@+id/left" layout="@layout/left"/> <!-- Our page two --> <include android:id="@+id/center" layout="@layout/center"/> <!-- Can add as more as we want --> </com.example.testdemo.DragableLuncher> </RelativeLayout>
2.实现代码
代码中的注释是我阅读时候的笔记,有兴趣研究实现方法的码友们可以参考下,在此基础上试着添加更多炫酷的效果。
也可以不用管内部实现方法,在自己的工程下创建一个类(DragableLuncher),然后直接copy代码进去,依照上面的使用方式使用即可。
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.Scroller; public class DragableLuncher extends ViewGroup { private Scroller mScroller;// 负责得到滚动属性的对象 private VelocityTracker mVelocityTracker;// 负责触摸的功能类 private int mScrollX = 0;// 滚动的起始X坐标 private float mLastMotionX;// 滚动结束X坐标 private int mCurrentScreen = 0;// 默认显示第几屏 private static final int SNAP_VELOCITY = 1000; private final static int TOUCH_STATE_REST = 0; private final static int TOUCH_STATE_SCROLLING = 1; private int mTouchState = TOUCH_STATE_REST; private int mTouchSlop = 0;//用户滑动的距离最小值 public DragableLuncher(Context context) { super(context); mScroller = new Scroller(context); //获取触发移动事件的最短距离,系统内定? mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); this.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); } public DragableLuncher(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context); //获取触发移动事件的最短距离,系统内定? mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); this.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); mCurrentScreen = 1; } /* touch事件拦截器,返回true继续执行onTouchEvent回调函数 * 即mTouchState ?= TOUCH_STATE_REST */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //获取触发事件的类型,主要有:ACTION_DOWN、ACTION_MOVE、ACTION_UP final int action = ev.getAction(); //当动作正在滑动 且 屏幕在滚动中 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { return true; } //获取触发点的X坐标 final float x = ev.getX(); switch (action) { case MotionEvent.ACTION_DOWN: //mLastMotionX相当于初始时按下的坐标点 mLastMotionX = x; //一种特殊情况,界面按初速度滚动时,触屏 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; break; case MotionEvent.ACTION_MOVE: // 获取滑动距离 final int xDiff = (int) Math.abs(x - mLastMotionX); //X滑动距离大于mTouchSlop开始滚动,小于则放弃 boolean xMoved = xDiff > mTouchSlop; if (xMoved) { mTouchState = TOUCH_STATE_SCROLLING;//进入滑动状态 } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mTouchState = TOUCH_STATE_REST;//改成闲置状态 break; } //判断进入滚动状态方可通过拦截器,否则不通过,通过后自动调用进一步的onTouchEvent return mTouchState != TOUCH_STATE_REST; } //isOpen用以控制是否开启滚动效果,可在isOpenTouchAnima中设置 public boolean isOpen = true; // 设置是否打开触摸滑动 public boolean isOpenTouchAnima(boolean isOpen) { this.isOpen = isOpen; return isOpen; } //响应滑动时间 @Override public boolean onTouchEvent(MotionEvent event) { if (isOpen) { //确保速率探测器不为空 if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } //将event事件添加到探测器中,即绑定两者关系 mVelocityTracker.addMovement(event); final int action = event.getAction(); final float x = event.getX(); //处理各种touch事件 switch (action) { case MotionEvent.ACTION_DOWN: //当页面正在滚动时按钮,则暂停滚动效果 if (!mScroller.isFinished()) { mScroller.abortAnimation(); } //只有ACTION_DOWN条件下的坐标才是初始坐标 mLastMotionX = x; break; case MotionEvent.ACTION_MOVE: //移动距离,注:此处的移动带有方向,因此不取绝对值,负值向右,正值向左 final int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; //deltaX<0表示向右 //mScrollX表示当前View滚动,左边框的X坐标,即:mScrollX>0为向右 if (deltaX < 0) { if (mScrollX > 0) { scrollBy(Math.max(-mScrollX, deltaX), 0); } } else if (deltaX > 0) { //取得可滚动的最大距离 final int availableToScroll = getChildAt(getChildCount() - 1).getRight() - mScrollX - getWidth(); if (availableToScroll > 0) { scrollBy(Math.min(availableToScroll, deltaX), 0); } } break; case MotionEvent.ACTION_UP: //计算当前速率 final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); int velocityX = (int) velocityTracker.getXVelocity(); if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) { // 滑动到左边的界面 snapToScreen(mCurrentScreen - 1); } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) { // 滑动到右边的界面 snapToScreen(mCurrentScreen + 1); } else { //滑动到判定的界面 snapToDestination(); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mTouchState = TOUCH_STATE_REST; break; case MotionEvent.ACTION_CANCEL: mTouchState = TOUCH_STATE_REST; } // mScrollX = this.getScrollX(); } else { return false; } return true; } //滑动到判定的界面 private void snapToDestination() { final int screenWidth = getWidth(); final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth; snapToScreen(whichScreen); } /** * 带动画效果显示界面 * 跳转到指定页面,id = whichScreen */ public void snapToScreen(int whichScreen) { mCurrentScreen = whichScreen; final int newX = whichScreen * getWidth(); final int delta = newX - mScrollX; mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2); invalidate(); } /** * 不带动画效果显示界面 */ public void setToScreen(int whichScreen) { mCurrentScreen = whichScreen; final int newX = whichScreen * getWidth(); mScroller.startScroll(newX, 0, 0, 0, 10); invalidate(); } //获得当前屏幕是第几屏 public int getCurrentScreen() { return mCurrentScreen; } //当主界面布局改变时调用 /* * 在此方法内逐个设置页面在parent内显示的position * 从左往右一张一张贴过去 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); //View不是隐藏状态都进行显示 if (child.getVisibility() != View.GONE) { final int childWidth = child.getMeasuredWidth(); //设置View在parent内的显示范围 //前两个参数:左上顶点的坐标 //后两个参数:右下顶点的坐标 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); childLeft += childWidth; } } } //取得测量得到的高宽 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int width = MeasureSpec.getSize(widthMeasureSpec);//提取出宽度 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);//提取宽度的模式 if (widthMode != MeasureSpec.EXACTLY) { /*测量规范模式 * MeasureSpec.AT_MOS * ——The child can be as large as it wants up to the specified size. * MeasureSpec.EXACTLY * ——The parent has determined an exact size for the child. * MeasureSpec.UNSPECIFIED * ——The parent has not imposed any constraint on the child. * */ throw new IllegalStateException("error mode."); } final int heightMode = MeasureSpec.getMode(heightMeasureSpec);//提取高度的模式 if (heightMode != MeasureSpec.EXACTLY) { throw new IllegalStateException("error mode."); } // 子元素将被分配给同样的高和宽 final int count = getChildCount(); for (int i = 0; i < count; i++) { getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); } //滚动到指定的屏幕 scrollTo(mCurrentScreen * width, 0); } //计算滚动的坐标 @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { mScrollX = mScroller.getCurrX(); scrollTo(mScrollX, 0); postInvalidate(); } } }
相关文章推荐
- 在Android中使用ViewPager实现左右滑动页面
- android 实现页面左右滑动
- Android 手势识别 (左右滑动)实现 页面 切换
- [Android学UI之三]实现新浪微博消息页面左右滑动页面方式二(二)
- [Android学UI之三]实现新浪微博消息页面左右滑动页面方式一(一)
- Appium 测试,实现上下、左右滑动页面【Android,iOS 已验证】
- Android 左右滑动切换页面或Activity的效果实现
- android 学习之Fragment+ViewPager实现页面左右滑动标签页
- 【Android基础知识】使用ViewFlipper实现页面左右滑动
- Android 手势识别 (左右滑动)实现 页面 切换
- Android 手势识别 (左右滑动)实现 页面 切换
- Android 利用ViewPager实现底部圆点导航左右滑动效果以及Fragment页面切换
- Android UI开发第二十篇——仿launcher的左右滑动(用ViewPager实现欢迎引导页面)
- [Android学UI之一]简单实现移动应用左右滑动导航页面
- Android 左右滑动切换页面或Activity的效果实现
- android-左右滑动页面设计-仿微信滑动引导页面
- Android实现左右滑动指引效果
- Android实现左右滑动指引效果
- Android实现左右滑动指引效果
- Android实现导航菜单左右滑动效果