您的位置:首页 > 移动开发 > Android开发

Android工具类:实现左右滑动页面

2014-10-04 23:23 369 查看
本文介绍Android中实现左右滑动切换页面的效果

网络上实现的方法多种多样,前段时间稍微研究过,这几天又整理了一下,拿出来分享希望对码友们会有帮助。

这里把实现方法封装成一个继承于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();
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: