当前位置: 首页 > 编程日记 > 正文

Android开源中国客户端学习 (自定义View)左右滑动控件ScrollLayout

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

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

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

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

那么就首先看onMeasure

@Overrideprotected 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 scrollLayoutfinal int count = getChildCount();for (int i = 0; i < count; i++) {getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);}// Log.e(TAG, "moving to screen "+mCurScreen);scrollTo(mCurScreen * width, 0);}

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 = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}

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

然后重写了onLayout来layout 子View

@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int childLeft = 0;final int childCount = getChildCount();for (int i = 0; i < childCount; i++) {final View childView = getChildAt(i);if (childView.getVisibility() != View.GONE) {final int childWidth = childView.getMeasuredWidth();childView.layout(childLeft, 0, 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 pagewhichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));if (getScrollX() != (whichScreen * getWidth())) {final int delta = whichScreen * getWidth() - getScrollX();mScroller.startScroll(getScrollX(), 0, delta, 0,Math.abs(delta) * 1);//持续滚动时间 以毫秒为单位mCurScreen = whichScreen;invalidate(); // Redraw the layoutif (mOnViewChangeListener != null){mOnViewChangeListener.OnViewChange(mCurScreen);}}}

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

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

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

代码实现如下:

public void scrollToScreen(int whichScreen) {        // get the valid layout pagewhichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));if (getScrollX() != (whichScreen * getWidth())) {final int delta = whichScreen * getWidth() - getScrollX();mScroller.startScroll(getScrollX(), 0, delta, 0,Math.abs(delta) * 1);//持续滚动时间 以毫秒为单位mCurScreen = whichScreen;invalidate(); // Redraw the layoutif (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的时候才会结束,这样动画也就执行完了,那他就滑动到下一屏了~

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

@Overridepublic 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;}

@Overridepublic 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) < 200 && Math.abs(deltaY) > 10)break;mLastMotionY = y;//-------------------------------------
            mLastMotionX = x;scrollBy(deltaX, 0);break;case MotionEvent.ACTION_UP://Log.e(TAG, "event : up");// if (mTouchState == TOUCH_STATE_SCROLLING) {final VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000);int velocityX = (int) velocityTracker.getXVelocity();//Log.e(TAG, "velocityX:" + velocityX);if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {// Fling enough to move left//Log.e(TAG, "snap left");snapToScreen(mCurScreen - 1);} else if (velocityX < -SNAP_VELOCITY&& mCurScreen < getChildCount() - 1) {// Fling enough to move right//Log.e(TAG, "snap right");snapToScreen(mCurScreen + 1);} 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) < 200 && Math.abs(deltaY) > 10)break;mLastMotionY = y;//-------------------------------------
            mLastMotionX = x;scrollBy(deltaX, 0);

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

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

相关文章:

【Python】有道翻译的爬虫实现(前篇)

import requestsimport jsonurl "http://fanyi.youdao.com/translate_o?smartresultdict&smartresultrule"data {"i": "我喜欢学习", "from": "AUTO", "to": "AUTO", "smartresult":&q…

自动生成纯文本表格的工具

https://tableconvert.com/?outputtext 有时候需要写文档的时候生成这种纯文本表格&#xff0c;这个工具真的很方便&#xff0c;贴上数据就可以了。

《Java编程思想》笔记13.字符串

点击进入我的博客 字符串操作是计算机程序设计中最常见的行为 13.1 不可变String String底层是由char[]实现的&#xff0c;是不可变的。看起来会改变String的方法&#xff0c;实际上都是创建了一个新的String对象&#xff0c;任何指向它的引用都不可能改变它本身的值。 13.2 重…

【Python】有道翻译的爬虫实现(后篇)

前面说到&#xff0c;有道翻译和百度翻译不同 &#xff08;百度翻译是模拟iPhone手机&#xff0c;可能百度翻译用Pc端也会有类似的问题&#xff0c;有道翻译的User—Agent是Pc端&#xff09; 每一次的salt和sign都不一样&#xff0c;这是什么原因产生的呢&#xff1f; 一、每…

Redis客户端JetCache的单机版和集群版的配置

jetcache基础应用参考这个:阿里巴巴开源的缓存框架JetCache创建缓存 1.JetCache的jedis配置 https://github.com/alibaba/jetcache/wiki/Config_CN jedis配置:apollo版 jetcache.remote.default.type = redis jetcache.remote.default.keyConvertor = fastjson jetcache.r…

5 分钟一次理解 Spring IOC !

今天我们分析一下 spring 的 IOC&#xff0c;梳理一下 IOC 和 DI 的概念与原理。在网上看到开涛有篇文章写的不错&#xff0c;提取其中一部分精华内容并做一些解读。 1.1.IOC是什么&#xff1f; Ioc—Inversion of Control&#xff0c;即“控制反转”&#xff0c;不是什么技术&…

工作两年的编程感想

2019独角兽企业重金招聘Python工程师标准>>> 工作已有两年了&#xff0c;两年不长也不短了&#xff0c;程序员的辛酸苦乐也都体验了一些&#xff0c;故写此博客既为留念&#xff0c;也为接下来的两年留下一个参考点。 首先需要声明的是&#xff0c;本人的工作是Java…

微服务重构心得

现在都在做微服务,看起来就是做服务拆分比较简单,但是实际上真正重构起来又遇到许许多多的问题。 微服务重构常见问题 1.领域驱动模型的困扰 比如听到很多理论比如领域驱动,那么到底需要不需要学习或者使用领域驱动呢? 2.系统的复杂性 重构的时候发现系统之间调用非常…

【Python】百度翻译的爬虫实现(后篇)

这个程序可以实现中英文的自动识别然后进行翻译 看着程序就很好理解。 import requestsimport jsonclass Translation():翻译def __init__(self,content):self.content contentself.url "http://fanyi.baidu.com/basetrans"self.headers {"User-Agent"…

美团即时物流的分布式系统架构设计

背景 美团外卖已经发展了五年&#xff0c;即时物流探索也经历了3年多的时间&#xff0c;业务从零孵化到初具规模&#xff0c;在整个过程中积累了一些分布式高并发系统的建设经验。最主要的收获包括两点&#xff1a; 即时物流业务对故障和高延迟的容忍度极低&#xff0c;在业务复…

Intellij IDEA单元测试提示Test events were not received

Intellij IDEA单元测试时提示Test events were not received 也就是可以运行test方法&#xff0c;也提示成功&#xff0c;但是看不到具体的执行结果。 Intellij IDEA从2019.2.1版本开始&#xff0c;会将Gradle管理的项目的测试代码&#xff0c;默认使用Gradle来运行&#xff0…

Linux下task_struct详解

背景&#xff1a;为了管理进程&#xff0c;操作系统必须对每个进程所做的事情进行清楚地描述&#xff0c;为此&#xff0c;操作系统使用数据结构来代表处理不同的实体&#xff0c;这个数据结构就是通常所说的进程描述符或进程控制块。在linux系统中&#xff0c;这就是task_stru…

【Python】数据提取xpath和lxml模块(豆瓣电影排行榜的爬虫)

xpath xpath&#xff1a;一门从html中提取数据的语言 xpath语法 1、选择节点(标签) /html/head/meta &#xff1a;能够选中html下的head下的所有的meta标签 2、// &#xff1a;能够从任意节点开始选择 //li&#xff1a;当前页面上所有的li标签 //html/head/link &a…

qt5.6.3下使用firebird

有人把firebird比作数据库界的瑞士军刀&#xff0c;想学习一下其在QT5.6中的使用&#xff0c;于是便开始了一场自己挖坑&#xff0c;自己埋的旅程。 环境说明&#xff1a;win7 64位QT5.6 mingw4.9 32位&#xff08;好像官网上也没有64位&#xff0c;当然mingw也是32位的&#x…

【Python】数据提取xpath和lxml模块(糗事百科的爬虫)

程序中用到的一些零碎知识点&#xff1a; 一、列表推导式&#xff1a;帮助我们快速生成一堆数据的列表 1、format&#xff1a;字符串格式化的一种方式 >>> ["10月{}日".format(i) for i in range(1,10)] [10月1日, 10月2日, 10月3日, 10月4日, 10月5日, 1…

Linux Performance

性能专家Brendan Gregg的网站。 Linux性能 该页面链接到我创建的各种Linux性能材料&#xff0c;包括右侧的工具图。它们使用大字体来适合滑盖。您也可以将它们打印出来用于办公室墙壁。它们显示&#xff1a;Linux可观察性工具&#xff0c; Linux静态性能分析工具&#xff0c;…

07.LoT.UI 前后台通用框架分解系列之——强大的文本编辑器

LOT.UI分解系列汇总&#xff1a;http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下&#xff1a;https://github.com/dunitian/LoTCodeBase/tree/master/LoTUI 先看在LoT.UI里面的应用效果图&#xff1a; 完整Demo&#xff1a;&#xff08;https://gith…

workerman结合laravel开发在线聊天应用的示例代码

项目背景&#xff1a; 最近由于公司的业务需求&#xff0c;需要用到聊天功能。而且有比较多的个性化需求需要定制。之前使用别人的聊天组件是基于微擎的。如果要移植到普通的H5在逻辑修改还有定制上存在比较多的困难。为此只能克服困难&#xff0c;自己搭建一个吧 什么是Worker…

复杂系统设计 企业开发的困境

复杂系统设计源自我多年对企业复杂系统的设计的一些思考&#xff0c;类似日记吧&#xff0c;不断完善。 为什么从一个大公司的引入架构师甚至架构师组还是很难架构企业开发中的很多问题&#xff1f; 这些问题表现出架构上的复杂性&#xff0c;和业务上的复杂性。 有时候架构…

数据库服务器跟网站服务器间传输慢的问题

数据库服务器和网站服务器是分开的&#xff0c;现在从网站服务器这边查数据比较慢&#xff0c;什么原因&#xff1f;&#xff1f;&#xff1f; 一、首先确定服务器之间的网络有没有问题 可以简单的在网站服务器上ping数据库服务器&#xff08;反过来也可以&#xff09;&#xf…

【Python】百度贴吧图片的爬虫实现(努力努力再努力)

学会爬取图片以后&#xff0c;第一时间去了张艺兴吧&#xff0c;哈哈哈哈哈哈 一定要放上一张爬取的照片&#xff0c;哼唧 import reimport requestsimport urllibclass Baidutieba():def __init__(self):self.url "http://tieba.baidu.com/p/4876047826?pn{}"#u…

cin、cout的重载

一、cin重载 1.cin为ostream类的成员 2.cin重载应为全局函数&#xff08;毕竟ostream是别人写好的&#xff09; 3.代码 a.核心代码 ostream & operator<<(ostream &os,const A &a)//const A &a是为了避免复制函数的调用 &#xff1b;ostream &o 相当…

第二次冲刺第十天

第二次冲刺今天就结束了&#xff0c;这十天来学会了不少的东西。 简单说一下昨天做的&#xff1a;整合各个部件的功能。 今天&#xff1a;小组进行总结&#xff0c;加强性能。 这十天来遇到的问题&#xff1a; 一&#xff0c;对于网络云端&#xff0c;之前都是其他小组成员在使…

说透泛型类和泛型方法以及Class<T>和Class<?>的差异

泛型类和泛型方法看起来似乎可以实现类似的功能&#xff0c;但是很多人并未真正掌握泛型方法&#xff0c;网上很多文章说了很多还是似是而非&#xff0c;特别是初学者还是搞不明白。 一.关于泛型方法 1.泛型方法可以独立于泛型类 2.泛型方法等效于泛型类里泛型参数方法&…

win10 +python 3.6.4安装scrapy

第一步&#xff1a; 首先&#xff0c;我们先在电脑上安装好python3.6并且配置好环境变量&#xff0c;以可以直接在命令行界面输入python命令可以出现如图的界面为主。 第二步&#xff1a; 升级pip &#xff0c;在cmd窗口中会有提示&#xff0c;没有提示的话就已经是最新版本了…

7 种 Javascript 常用设计模式学习笔记

7 种 Javascript 常用设计模式学习笔记 由于 JS 或者前端的场景限制&#xff0c;并不是 23 种设计模式都常用。 有的是没有使用场景&#xff0c;有的模式使用场景非常少&#xff0c;所以只是列举 7 个常见的模式 本文的脉络&#xff1a; 设计与模式5 大设计原则 7 种常见的设计…

从难免的线上bug说起代码的思考

经常是某司线上又出bug了&#xff0c;然后是给公司造成多少损失&#xff0c;追根究底总是可以找到一些原因&#xff0c;诸如&#xff1a;写代码逻辑考虑不全面&#xff0c;或者代码有硬伤&#xff0c;也有测试不充分&#xff0c;甚至不测试都有&#xff0c;也有是运维的问题等等…

PHP-Fpm应用池配置

//php.net php-fpm配置简介http://php.net/manual/zh/install.fpm.configuration.php//Global Options//pool defined[www]user nobodygroup nobodylisten 127.0.0.1:9000pm dynamicpm.max_children 5pm.start_servers 2pm.min_spare_servers 1pm.max_spare_servers 3s…

【Python】百度首页GIF动画的爬虫

今天百度首页的GIF动画很可爱&#xff0c;就想着用才学的爬虫爬取一下&#xff0c;虽然直接点击“图片另存为”就可以了 import requestsimport urllibclass Gif():def __init__(self):self.url "https://www.baidu.com/"self.headers {"User-Agent": …

CSS题目系列(3)- 实现文字切割效果

描述 有一天逛 Codepen 的时候&#xff0c;看到这么一个效果&#xff1a;将文字上下切开两半。 点进去看了一下代码&#xff0c;发现原理很简单&#xff0c;大概就是通过伪类使用attr()函数获取内容&#xff0c;然后进行定位。 你可以点下方链接查看效果&#xff1a; gd4ark.gi…