左右滑动的控件我们使用的也是非常多了,但是基本上都是使用的viewpager 等 android基础的控件,那么我们有么有考虑过查看他的源码进行定制呢?当然,如果你自我感觉非常好的话可以自己定制一个,osc的ScrollLayout就是自己定义的View 和Viewpager的区别还是不小的

代码不是很多不到300行,但是却实现了左右滑动页面的效果,还是值得学习的.效果如下:

我们看到ScrollLayout直接继承了ViewGroup然后自定义了一系列功能,那么接下来就分析一下:

我们知道ViewGroup的绘制流程基本分为onMeasure ,onLayout ,onDraw三部分

那么就首先看onMeasure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//Log.e(TAG, "onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"ScrollLayout only canmCurScreen run at EXACTLY mode!");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException(
"ScrollLayout only can run at EXACTLY mode!");
} // The children are given the same width and height as the scrollLayout
final int count = getChildCount();
for (int i = ; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
// Log.e(TAG, "moving to screen "+mCurScreen);
scrollTo(mCurScreen * width, );
}

widthMode != MeasureSpec.EXACTLY

那么scrollTo的作用是什么呢?

其实我们可以把android View 认为是一个桌布,屏幕的左上角是 0,0 scrollTo 就是把这个view移动到某个位置.

如图来说明 0,0 表示屏幕的左上角 view调用了view.scrollTo(2,3)后就可以跳转到这个位置了~

至于我们的viewpager是如何工作的我们在看完onLayout后再说~

这一句话其实是检查是否width是"绝对大小" 其实也就是检查是否是确定的像素 如100dp或者 match_parent

如果是wrap_content就抛异常了.

然后就是把这个layout的孩子的宽高都和他自己一样:

final int count = getChildCount();
for (int i = ; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}

最后是scrollTo(mCurScreen * width, 0);  滚动的当前的屏幕page中去.

然后重写了onLayout来layout 子View

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = ;
final int childCount = getChildCount();
for (int i = ; i < childCount; i++) {
final View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
final int childWidth = childView.getMeasuredWidth();
childView.layout(childLeft, , childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}

代码其实也很简单 就是把他的孩子横向排开, 宽度是前面measure获取的 然后调用父类的dispatchDraw 和 onDraw把他们画出来这些都不表了

接上面,那么这个pager是怎么像我们看到的那样可以左右滑动呢?

其实在layout的时候 这个控件会把他的孩子一字排开,如下图的红色方框所示.

我们知道,这个控件只有一个屏幕大小,那么他就会使用scrollTo 左右移动,如下图蓝色的部分,那么我们可以看到左右滑动的效果了.

当然 这样其实只是实现"计算机"意义的滚动,因为这个滚动只用手机才能知道用户看上去只不过是其中一个屏幕而已,从一个屏幕跳转到另一个屏幕也没有什么过渡动画,这就想osc客户端关闭的左右滑动一样.虽然这个app确实是在左右滑动把各个孩子屏幕显示给用户,但是用户只能看到当前的屏幕而已

那么怎么让用户有看到左右滑动时候一个屏幕进入另一个屏幕退出的效果呢?

 

public void snapToScreen(int whichScreen) {
//是否可滑动
if(!isScroll) {
this.setToScreen(whichScreen);
return;
} scrollToScreen(whichScreen);
} public void scrollToScreen(int whichScreen) {
// get the valid layout page
whichScreen = Math.max(, Math.min(whichScreen, getChildCount() - ));
if (getScrollX() != (whichScreen * getWidth())) {
final int delta = whichScreen * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), , delta, ,
Math.abs(delta) * );//持续滚动时间 以毫秒为单位
mCurScreen = whichScreen;
invalidate(); // Redraw the layout if (mOnViewChangeListener != null)
{
mOnViewChangeListener.OnViewChange(mCurScreen);
}
}
}

不可滑动的我们就不看了,其实就是个scrollTo 着重看可以滑动界面的实现,也就是scrollToScreen

我们知道,如果想让一个空间滑动,本质上其实是改变这个控件的坐标,然后不断的刷新屏幕,这样很多帧和在一起连续播放用户就可以感觉这个屏幕是在滚动了:

为了实现滚动这里用到了Scroller. Scroller可以认为是一个存储屏幕参数的容器,View需要做动画的时候就从Scroller中取出已经计算好坐标, 使用这个坐标不断的刷新屏幕,view的位置就不断变化了.

代码实现如下:

public void scrollToScreen(int whichScreen) {
// get the valid layout page
whichScreen = Math.max(, Math.min(whichScreen, getChildCount() - ));
if (getScrollX() != (whichScreen * getWidth())) {
final int delta = whichScreen * getWidth() - getScrollX();
mScroller.startScroll(getScrollX(), , delta, ,
Math.abs(delta) * );//持续滚动时间 以毫秒为单位
mCurScreen = whichScreen;
invalidate(); // Redraw the layout if (mOnViewChangeListener != null)
{
mOnViewChangeListener.OnViewChange(mCurScreen);
}
}
}

核心代码是startScroll()函数 这个函数是android源码中的函数,具体作用其实是改变一些数值,他有五个参数

从(startx,starty) 到 (dx ,dy) 最后一个参数是在多少时间内完成这个操作  这个函数只是在这一段时间中计算移动到的坐标,并不会改变view的位置,view的位置一定是由draw来做的.

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}

除了移动位置 ,还需要知道是否移动结束了,如果结束了就不要再刷新屏幕了 这个是通过Scroller的computeScrollOffset 函数实现的,如果移动没有结束就返回true否则返回false

这样完事具备就剩下刷新屏幕了~ 在scrollToScreen函数中一定调用了 invalidate()函数告诉View重新进行绘制.在绘制的过程中,其父View会调用Scrolllayout实现的computeScroll函数来真正的移动view的坐标这个是通过scrollTo函数实现的,而坐标就是从scroller中取到的.ok 上面图中的蓝色方框终于开始移动了,移动了一段距离后就执行postInvalidate()函数,我们知道,postInvallidate函数是 异步进行刷新 ,最后还是执行invalidate()函数,invalidate()又开始调用computeScroll  ...这个死循环在mScroller.computeScrollOffset()为false的时候才会结束,这样动画也就执行完了,那他就滑动到下一屏了~

@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE)
&& (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
final float y = ev.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
final int xDiff = (int) Math.abs(mLastMotionX - x);
if (xDiff > mTouchSlop) {
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
: TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchState = TOUCH_STATE_REST;
break;
}
return mTouchState != TOUCH_STATE_REST;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
//是否可滑动
if(!isScroll) {
return false;
} if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
//Log.e(TAG, "event down!");
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
mLastMotionX = x; //---------------New Code----------------------
mLastMotionY = y;
//--------------------------------------------- break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) (mLastMotionX - x); //---------------New Code----------------------
int deltaY = (int) (mLastMotionY - y);
if(Math.abs(deltaX) < && Math.abs(deltaY) > )
break;
mLastMotionY = y;
//------------------------------------- mLastMotionX = x;
scrollBy(deltaX, );
break;
case MotionEvent.ACTION_UP:
//Log.e(TAG, "event : up");
// if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity();
int velocityX = (int) velocityTracker.getXVelocity();
//Log.e(TAG, "velocityX:" + velocityX);
if (velocityX > SNAP_VELOCITY && mCurScreen > ) {
// Fling enough to move left
//Log.e(TAG, "snap left");
snapToScreen(mCurScreen - );
} else if (velocityX < -SNAP_VELOCITY
&& mCurScreen < getChildCount() - ) {
// Fling enough to move right
//Log.e(TAG, "snap right");
snapToScreen(mCurScreen + );
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
// }
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
mTouchState = TOUCH_STATE_REST;
break;
}
return true;
}

int deltaX = (int) (mLastMotionX - x);

            //---------------New Code----------------------
int deltaY = (int) (mLastMotionY - y);
if(Math.abs(deltaX) < && Math.abs(deltaY) > )
break;
mLastMotionY = y;
//------------------------------------- mLastMotionX = x;
scrollBy(deltaX, );

final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity();
int velocityX = (int) velocityTracker.getXVelocity();
//Log.e(TAG, "velocityX:" + velocityX);
if (velocityX > SNAP_VELOCITY && mCurScreen > ) {
// Fling enough to move left
//Log.e(TAG, "snap left");
snapToScreen(mCurScreen - );
} else if (velocityX < -SNAP_VELOCITY
&& mCurScreen < getChildCount() - ) {
// Fling enough to move right
//Log.e(TAG, "snap right");
snapToScreen(mCurScreen + );
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
// }
mTouchState = TOUCH_STATE_REST;

http://my.oschina.net/sfshine/blog/151673

Android开源中国客户端学习 (自定义View)左右滑动控件ScrollLayout的更多相关文章

  1. Android开源系列:仿网易Tab分类排序控件实现

    前言 产品:网易新闻那个Tab排序好帅. 开发:哦~ 然后这个东东在几天后就出现了..... (PS:差不多一年没回来写博客了~~~~(>_<)~~~~,顺便把名字从 enjoy风铃 修改 ...

  2. Android怎么使用字体图标 自定义FontTextView字体图标控件-- 使用方法

    首先我想说明一下字体图标的好处,最大的好处就是自适应了,而且是使用TextView 不用去切图,是矢量图 灵活调用 第一步我要说明一下一般字体图标的来源,我这里使用的是  --阿里巴巴矢量图标库 -网 ...

  3. Android 在程序中动态添加 View 布局或控件

    有时我们需要在程序中动态添加布局或控件等,下面用程序来展示一下相应的方法: 1.addView 添加View到布局容器 2.removeView 在布局容器中删掉已有的View 3.LayoutPar ...

  4. Android 开源项目及其学习

    Android 系统研究:http://blog.csdn.net/luoshengyang/article/details/8923485 Android 腾讯技术人员博客 http://hukai ...

  5. Android View中的控件和监听方法...

    PS:居然三天没写博客了...今天补上...东西虽多,但是都是一些基础...代码多了一些,有人可能会这样问,粘这么多代码有毛用..其实对于一个Android的初学者来说,一个完整的代码是最容易帮助理解 ...

  6. android - 自定义(组合)控件 + 自定义控件外观

    转载:http://www.cnblogs.com/bill-joy/archive/2012/04/26/2471831.html android - 自定义(组合)控件 + 自定义控件外观   A ...

  7. Android创建自定义的布局和控件

    Android的自带布局有framelayout.linerlayout.relativelayout,外加两个百分比布局,但是这些无法灵活的满足我们的需要,所以我们要自己自定义并引入自己的布局.首先 ...

  8. 使用VideoView自定义一个播放器控件

    介绍 最近要使用播放器做一个简单的视频播放功能,开始学习VideoView,在横竖屏切换的时候碰到了点麻烦,不过在查阅资料后总算是解决了.在写VideoView播放视频时候定义控制的代码全写在Actv ...

  9. 获取 AlertDialog自定义的布局 的控件

    AlertDialog自定义的布局 效果图: 创建dialog方法的代码如下: 1 LayoutInflater inflater = getLayoutInflater(); 2 View layo ...

随机推荐

  1. 基数---SQL Server 2008 Bible

    关系类型 主要实体的键 次要实体的键 一对一 主要实体-主键-单个元组 主要实体-主键-单个元组 一对多 主要实体-主键-单个元组 次要实体-外键-多个元组 多对多 多个元组 多个元组

  2. Asp.Net 构架(Http Handler 介绍) - Part.2

    原文地址:http://www.cnblogs.com/JimmyZhang/archive/2007/09/15/894124.html 引言 在 Part.1 Http请求处理流程 一文中,我们了 ...

  3. Hadoop 2、配置HDFS HA (高可用)

    前提条件 先搭建 http://www.cnblogs.com/raphael5200/p/5152004.html 的环境,然后在其基础上进行修改 一.安装Zookeeper 由于环境有限,所以在仅 ...

  4. Android 之 Shape (圆角输入框)

    1 简介 本文主要介绍通过 shape 来设置 EditText 的圆角.   2 shape 的设置   shape_life_search.xml 放在 res/drawable 文件夹内 < ...

  5. hdu1047(模拟大量的循环添加)

    标题信息:总结多个大整数,(使用add循环相加的功能) http://acm.hdu.edu.cn/showproblem.php? pid=1047 AC代码: /**  *大数的循环加法,转化为字 ...

  6. python - 面向对象(一)

    python是一门面向对象的编程语言,python中的一切均是对象. 有对象就提到类,对象和类就像是儿子和老子的关系,是不可分的一对. 什么是类     类就是具有一些共同特性的事物的统称.好比人类, ...

  7. highcharts的使用

    步骤: 1. 去highcharts官网下载最新版本 2. 在.aspx页面添加引用 例: <link href="../JS/highcharts/css/highslide.css ...

  8. textField:shouldChangeCharactersInRange:replacementString:

    http://blog.csdn.net/mamong/article/details/44964801

  9. c#使用Dictionary统计字符串中出现次数最多字符

    最近在找工作,遇到这样一道面试题: 对于给定的一个字符串,统计出该串中各个字符出现的次数,并打印出出现次数最多的那个字符 因为本人是个菜鸟,所以当时写的思路是用递归 /*str 字符串, strA 第 ...

  10. (三)CodeMirror - Event

    "change" (instance: CodeMirror, changeObj: object) { from, // object to, // object text, / ...