标签:
转载请注明出处:http://blog.csdn.net/qinjuning
前言: 虽然本文标题的有点标题党的感觉,但无论如何,通过这篇文章的学习以及你自己的实践认知,写个简单的滑屏小
Demo还是just so so的。
友情提示:
在继续往下面读之前,希望您对以下知识点有一定程度掌握,否则,继续看下去对您意义也不大。
1、掌握View(视图)的"视图坐标"以及"布局坐标",以及scrollTo()和scrollBy()方法的作用 ----- 必须理解
如果对这方面知识不太清楚的话,建议先看看我的这篇博客
<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明>,
不夸张地说,这篇博客理论上来说是我们这篇博文的基础。
2、知道onInterceptTouchEvent()以及onTouchEvent()对触摸事件的分发流程 ---- 不是必须
3、知道怎么绘制自定义ViewGroup即可 ---- 不是必须
OK。 继续往下看,请一定有所准备 。大家跟着我一步一步来咯。
知识点一: 关于scrollTo()和scrollBy()以及偏移坐标的设置/取值问题
在前面一篇博文中《Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用说明》,我们掌握了scrollTo()和
scrollBy()方法的作用,这两个方法的主要作用是将View/ViewGroup移至指定的坐标中,并且将偏移量保存起来。另外:
mScrollX 代表X轴方向的偏移坐标
mScrollY 代表Y轴方向的偏移坐标
关于偏移量的设置我们可以参看下源码:
- package com.qin.customviewgroup;
-
- public class View {
- ....
- protected int mScrollX;
- protected int mScrollY;
-
- public final int getScrollX() {
- return mScrollX;
- }
- public final int getScrollY() {
- return mScrollY;
- }
- public void scrollTo(int x, int y) {
-
- if (mScrollX != x || mScrollY != y) {
- int oldX = mScrollX;
- int oldY = mScrollY;
- mScrollX = x;
- mScrollY = y;
-
- onScrollChanged(mScrollX, mScrollY, oldX, oldY);
- if (!awakenScrollBars()) {
- invalidate();
- }
- }
- }
-
- public void scrollBy(int x, int y) {
- scrollTo(mScrollX + x, mScrollY + y);
- }
-
- }
于是,在任何时刻我们都可以获取该View/ViewGroup的偏移位置了,即调用getScrollX()方法和getScrollY()方法
知识点二: Scroller类的介绍
在初次看Launcher滑屏的时候,我就对Scroller类的学习感到非常蛋疼,完全失去了继续研究的欲望。如今,没得办法,
得重新看Launcher模块,基本上将Launcher大部分类以及功能给掌握了。当然,也花了一天时间来学习Launcher里的滑屏实现
,基本上业是拨开云雾见真知了。
我们知道想把一个View偏移至指定坐标(x,y)处,利用scrollTo()方法直接调用就OK了,但我们不能忽视的是,该方法本身
来的的副作用:非常迅速的将View/ViewGroup偏移至目标点,而没有对这个偏移过程有任何控制,对用户而言可能是不太
友好的。于是,基于这种偏移控制,Scroller类被设计出来了,该类的主要作用是为偏移过程制定一定的控制流程(后面我们会
知道的更多),从而使偏移更流畅,更完美。
可能上面说的比较悬乎,道理也没有讲透。下面我就根据特定情景帮助大家分析下:
情景: 从上海如何到武汉?
普通的人可能会想,so easy : 飞机、轮船、11路公交车...
文艺的人可能会想, 小 case : 时空忍术(火影的招数)、翻个筋斗(孙大圣的招数)...
不管怎么样,我们想出来的套路可能有两种:
1、有个时间控制过程才能抵达(缓慢的前进) ----- 对应于Scroller的作用
假设做火车,这个过程可能包括: 火车速率,花费周期等;
2、瞬间抵达(超神太快了,都眩晕了,用户体验不太好) ------ 对应于scrollTo()的作用
模拟Scroller类的实现功能:
假设从上海做动车到武汉需要10个小时,行进距离为1000km ,火车速率200/h 。采用第一种时间控制方法到达武汉的
整个配合过程可能如下:
我们每隔一段时间(例如1小时),计算火车应该行进的距离,然后调用scrollTo()方法,行进至该处。10小时过完后,
我们也就达到了目的地了。
相信大家心里应该有个感觉了。我们就分析下源码里去看看Scroller类的相关方法.
其源代码(部分)如下: 路径位于 \frameworks\base\core\java\android\widget\Scroller.java
- public class Scroller {
-
- private int mStartX;
- private int mStartY;
- private int mCurrX;
- private int mCurrY;
-
- private float mDeltaX;
- private float mDeltaY;
- private boolean mFinished;
-
-
- public Scroller(Context context) {
- this(context, null);
- }
- public final boolean isFinished() {
- return mFinished;
- }
-
- public final void forceFinished(boolean finished) {
- mFinished = finished;
- }
- public final int getCurrX() {
- return mCurrX;
- }
-
-
- public boolean computeScrollOffset() {
- if (mFinished) {
- return false;
- }
- int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
- if (timePassed < mDuration) {
- switch (mMode) {
- case SCROLL_MODE:
- float x = (float)timePassed * mDurationReciprocal;
- ...
- mCurrX = mStartX + Math.round(x * mDeltaX);
- mCurrY = mStartY + Math.round(x * mDeltaY);
- break;
- ...
- }
- else {
- mCurrX = mFinalX;
- mCurrY = mFinalY;
- mFinished = true;
- }
- return true;
- }
-
- public void startScroll(int startX, int startY, int dx, int dy, int duration) {
- mFinished = false;
- mDuration = duration;
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mStartX = startX; mStartY = startY;
- mFinalX = startX + dx; mFinalY = startY + dy;
- mDeltaX = dx; mDeltaY = dy;
- ...
- }
- }
其中比较重要的两个方法为:
public void startScroll(int startX, int startY, int dx, int dy, int duration)
函数功能说明:根据当前已经消逝的时间计算当前的坐标点,保存在mCurrX和mCurrY值中
public void startScroll(int startX, int startY, int dx, int dy, int duration)
函数功能说明:开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,到达坐标为
(startX+dx , startY+dy)处。
PS : 强烈建议大家看看该类的源码,便于后续理解。
知识点二: computeScroll()方法介绍
为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该
方法。因此, 再配合使用Scroller实例,我们就可以获得当前应该的偏移坐标,手动使View/ViewGroup偏移至该处。
computeScroll()方法原型如下,该方法位于ViewGroup.java类中
- public void computeScroll() {
-
- }
为了实现偏移控制,一般自定义View/ViewGroup都需要重载该方法 。
其调用过程位于View绘制流程draw()过程中,如下:
- @Override
- protected void dispatchDraw(Canvas canvas){
- ...
-
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, i)];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- }
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- ...
- child.computeScroll();
- ...
- }
Demo说明:
我们简单的复用了之前写的一个自定义ViewGroup,与以前一次有区别的是,我们没有调用scrollTo()方法去进行瞬间
偏移。 本次做法如下:
第一、调用Scroller实例去产生一个偏移控制(对应于startScroll()方法)
第二、手动调用invalid()方法去重新绘制,剩下的就是在 computeScroll()里根据当前已经逝去的时间,获取当前
应该偏移的坐标(由Scroller实例对应的computeScrollOffset()计算而得),
第三、当前应该偏移的坐标,调用scrollBy()方法去缓慢移动至该坐标处。
截图如下:
原始界面 点击按钮或者触摸屏之后的显示界面
附:由于滑动截屏很难,只是简单的截取了两个个静态图片,触摸的话可以实现左右滑动切屏了。
更多知识点,请看代码注释。。
- public class MultiViewGroup extends ViewGroup {
- ...
-
- public void startMove(){
- curScreen ++ ;
- Log.i(TAG, "----startMove---- curScreen " + curScreen);
-
-
- mScroller.startScroll((curScreen-1) * getWidth(), 0, getWidth(), 0,3000);
-
- invalidate();
-
-
- }
-
- @Override
- public void computeScroll() {
-
- Log.e(TAG, "computeScroll");
-
-
- if (mScroller.computeScrollOffset()) {
- Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());
-
- scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
-
- Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());
-
- postInvalidate();
- }
- else
- Log.i(TAG, "have done the scoller -----");
- }
-
- public void stopMove(){
-
- Log.v(TAG, "----stopMove ----");
-
- if(mScroller != null){
-
- if(!mScroller.isFinished()){
-
- int scrollCurX= mScroller.getCurrX() ;
-
-
-
-
- int descScreen = ( scrollCurX + getWidth() / 2) / getWidth() ;
-
- Log.i(TAG, "-mScroller.is not finished scrollCurX +" + scrollCurX);
- Log.i(TAG, "-mScroller.is not finished descScreen +" + descScreen);
- mScroller.abortAnimation();
-
-
- scrollTo(descScreen *getWidth() , 0);
- curScreen = descScreen ;
- }
- else
- Log.i(TAG, "----OK mScroller.is finished ---- ");
- }
- }
- ...
- }
如何实现触摸滑屏?
其实网上有很多关于Launcher实现滑屏的博文,基本上也把道理阐释的比较明白了 。我这儿也是基于自己的理解,将一些
重要方面的知识点给补充下,希望能帮助大家理解。
想要实现滑屏操作,值得考虑的事情包括如下几个方面:
其中:onInterceptTouchEvent()主要功能是控制触摸事件的分发,例如是子视图的点击事件还是滑动事件。
其他所有处理过程均在onTouchEvent()方法里实现了。
1、屏幕的滑动要根据手指的移动而移动 ---- 主要实现在onTouchEvent()方法中
2、当手指松开时,可能我们并没有完全滑动至某个屏幕上,这是我们需要手动判断当前偏移至去计算目标屏(当前屏或者
前后屏),并且优雅的偏移到目标屏(当然是用Scroller实例咯)。
3、调用computeScroll ()去实现缓慢移动过程。
知识点介绍:
VelocityTracker类
功能: 根据触摸位置计算每像素的移动速率。
常用方法有:
public void addMovement (MotionEvent ev)
功能:添加触摸对象MotionEvent , 用于计算触摸速率。
public void computeCurrentVelocity (int units)
功能:以每像素units单位考核移动速率。额,其实我也不太懂,赋予值1000即可。
参照源码 该units的意思如下:
参数 units : The units you would like the velocity in. A value of 1
provides pixels per millisecond, 1000 provides pixels per second, etc.
public float getXVelocity ()
功能:获得X轴方向的移动速率。
ViewConfiguration类
功能: 获得一些关于timeouts(时间)、sizes(大小)、distances(距离)的标准常量值 。
常用方法:
public int getScaledEdgeSlop()
说明:获得一个触摸移动的最小像素值。也就是说,只有超过了这个值,才代表我们该滑屏处理了。
public static int getLongPressTimeout()
说明:获得一个执行长按事件监听(onLongClickListener)的值。也就是说,对某个View按下触摸时,只有超过了
这个时间值在,才表示我们该对该View回调长按事件了;否则,小于这个时间点松开手指,只执行onClick监听
我能写下来的也就这么多了,更多的东西参考代码注释吧。 在掌握了上面我罗列的知识后(重点scrollTo、Scroller类),
其他方面的知识都是关于点与点之间的计算了以及触摸事件的分发了。这方面感觉也没啥可写的。
- public class MultiViewGroup extends ViewGroup {
-
- private static String TAG = "MultiViewGroup";
-
- private int curScreen = 0 ;
- private Scroller mScroller = null ;
-
- public MultiViewGroup(Context context) {
- super(context);
- mContext = context;
- init();
- }
- public MultiViewGroup(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- init();
- }
-
- private void init() {
- ...
-
- mScroller = new Scroller(mContext);
-
- ...
-
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
- }
-
- @Override
- public void computeScroll() {
-
- Log.e(TAG, "computeScroll");
-
-
- if (mScroller.computeScrollOffset()) {
- Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());
-
- scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
-
- Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());
-
- postInvalidate();
- }
- else
- Log.i(TAG, "have done the scoller -----");
- }
-
- private static final int TOUCH_STATE_REST = 0;
- private static final int TOUCH_STATE_SCROLLING = 1;
- private int mTouchState = TOUCH_STATE_REST;
-
-
- public static int SNAP_VELOCITY = 600 ;
- private int mTouchSlop = 0 ;
- private float mLastionMotionX = 0 ;
-
- private VelocityTracker mVelocityTracker = null ;
-
-
- @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:
- Log.e(TAG, "onInterceptTouchEvent move");
- final int xDiff = (int) Math.abs(mLastionMotionX - x);
-
- if (xDiff > mTouchSlop) {
- mTouchState = TOUCH_STATE_SCROLLING;
- }
- break;
-
- case MotionEvent.ACTION_DOWN:
- Log.e(TAG, "onInterceptTouchEvent down");
- mLastionMotionX = x;
- mLastMotionY = y;
- Log.e(TAG, mScroller.isFinished() + "");
- mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
-
- break;
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- Log.e(TAG, "onInterceptTouchEvent up or cancel");
- mTouchState = TOUCH_STATE_REST;
- break;
- }
- Log.e(TAG, mTouchState + "====" + TOUCH_STATE_REST);
- return mTouchState != TOUCH_STATE_REST;
- }
- public boolean onTouchEvent(MotionEvent event){
-
- super.onTouchEvent(event);
-
- Log.i(TAG, "--- onTouchEvent--> " );
-
-
- Log.e(TAG, "onTouchEvent start");
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
-
-
- float x = event.getX();
- float y = event.getY();
- switch(event.getAction()){
- case MotionEvent.ACTION_DOWN:
-
- if(mScroller != null){
- if(!mScroller.isFinished()){
- mScroller.abortAnimation();
- }
- }
- mLastionMotionX = x ;
- break ;
- case MotionEvent.ACTION_MOVE:
- int detaX = (int)(mLastionMotionX - x );
- scrollBy(detaX, 0);
-
- Log.e(TAG, "--- MotionEvent.ACTION_MOVE--> detaX is " + detaX );
- mLastionMotionX = x ;
- break ;
- case MotionEvent.ACTION_UP:
-
- final VelocityTracker velocityTracker = mVelocityTracker ;
- velocityTracker.computeCurrentVelocity(1000);
-
- int velocityX = (int) velocityTracker.getXVelocity() ;
- Log.e(TAG , "---velocityX---" + velocityX);
-
-
- if (velocityX > SNAP_VELOCITY && curScreen > 0) {
-
- Log.e(TAG, "snap left");
- snapToScreen(curScreen - 1);
- }
-
- else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){
- Log.e(TAG, "snap right");
- snapToScreen(curScreen + 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 ;
- }
-
- private void snapToDestination(){
-
- int scrollX = getScrollX() ;
- int scrollY = getScrollY() ;
-
- Log.e(TAG, "### onTouchEvent snapToDestination ### scrollX is " + scrollX);
-
-
-
-
-
- int destScreen = (getScrollX() + MultiScreenActivity.screenWidth / 2 ) / MultiScreenActivity.screenWidth ;
-
- Log.e(TAG, "### onTouchEvent ACTION_UP### dx destScreen " + destScreen);
-
- snapToScreen(destScreen);
- }
-
- private void snapToScreen(int whichScreen){
-
-
-
-
-
-
- curScreen = whichScreen ;
-
- if(curScreen > getChildCount() - 1)
- curScreen = getChildCount() - 1 ;
-
- int dx = curScreen * getWidth() - getScrollX() ;
-
- Log.e(TAG, "### onTouchEvent ACTION_UP### dx is " + dx);
-
- mScroller.startScroll(getScrollX(), 0, dx, 0,Math.abs(dx) * 2);
-
-
- invalidate();
- }
-
- public void startMove(){
- ...
- }
-
- public void stopMove(){
- ...
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- ...
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- ...
- }
- }
最后,希望大家能多多实践,最好能写个小Demo出来。那样才正在的掌握了所学所得。
本文代码示例下载地址:
http://download.csdn.net/detail/qinjuning/4194324 (二合一的Demo)
最后, 请一定要自己实践理解。否则,效果也不会很好。
版权声明:本文为博主原创文章,未经博主允许不得转载。
Android中滑屏实现----手把手教你如何实现触摸滑屏以及Scroller类详解
标签:
原文地址:http://www.cnblogs.com/Free-Thinker/p/4800122.html