Android Scroller完全解析,关于Scroller你所需知道的一切

 

Android Scroller完全解析,关于Scroller你所需知道的一切 

标签: androidViewPagerscrollByscrollToScroller
  42272人阅读  评论(142)  收藏  举报
  分类:

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/48719871 
2016大家新年好!这是今年的第一篇文章,那么应CSDN工作人员的建议,为了能给大家带来更好的阅读体验,我也是将博客换成了宽屏版。另外,作为一个对新鲜事物从来后知后觉的人,我终于也在新的一年里改用MarkDown编辑器来写博客了,希望大家在我的博客里也能体验到新年新的气象。 
我写博客的题材很多时候取决于平时大家问的问题,最近一段时间有不少朋友都问到ViewPager是怎么实现的。那ViewPager相信每个人都再熟悉不过了,因此它实在是太常用了,我们可以借助ViewPager来轻松完成页面之间的滑动切换效果,但是如果问到它是如何实现的话,我感觉大部分人还是比较陌生的, 为此我也是做了一番功课。其实说到ViewPager最基本的实现原理主要就是两部分内容,一个是事件分发,一个是Scroller,那么对于事件分发,其实我在很早之前就已经写过了相关的内容,感兴趣的朋友可以去阅读  Android事件分发机制完全解析,带你从源码的角度彻底理解,但是对于Scroller我还从来没有讲过,因此本篇文章我们就先来学习一下Scroller的用法,并结合事件分发和Scroller来实现一个简易版的ViewPager。


Scroller是一个专门用于处理滚动效果的工具类,可能在大多数情况下,我们直接使用Scroller的场景并不多,但是很多大家所熟知的控件在内部都是使用Scroller来实现的,如ViewPager、ListView等。而如果能够把Scroller的用法熟练掌握的话,我们自己也可以轻松实现出类似于ViewPager这样的功能。那么首先新建一个ScrollerTest项目,今天就让我们通过例子来学习一下吧。 
先撇开Scroller类不谈,其实任何一个控件都是可以滚动的,因为在View类当中有scrollTo()和scrollBy()这两个方法,如下图所示: 


这两个方法都是用于对View进行滚动的,那么它们之间有什么区别呢?简单点讲,scrollBy()方法是让View相对于当前的位置滚动某段距离,而scrollTo()方法则是让View相对于初始的位置滚动某段距离。这样讲大家理解起来可能有点费劲,我们来通过例子实验一下就知道了。 
修改activity_main.xml中的布局文件,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.guolin.scrollertest.MainActivity">

    <Button
        android:id="@+id/scroll_to_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="scrollTo"/>

    <Button
        android:id="@+id/scroll_by_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="scrollBy"/>

</LinearLayout>
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

外层我们使用了一个LinearLayout,然后在里面包含了两个按钮,一个用于触发scrollTo逻辑,一个用于触发scrollBy逻辑。 
接着修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

    private LinearLayout layout;

    private Button scrollToBtn;

    private Button scrollByBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layout = (LinearLayout) findViewById(R.id.layout);
        scrollToBtn = (Button) findViewById(R.id.scroll_to_btn);
        scrollByBtn = (Button) findViewById(R.id.scroll_by_btn);
        scrollToBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                layout.scrollTo(-60, -100);
            }
        });
        scrollByBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                layout.scrollBy(-60, -100);
            }
        });
    }
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

没错,代码就是这么简单。当点击了scrollTo按钮时,我们调用了LinearLayout的scrollTo()方法,当点击了scrollBy按钮时,调用了LinearLayout的scrollBy()方法。那有的朋友可能会问了,为什么都是调用的LinearLayout中的scroll方法?这里一定要注意,不管是scrollTo()还是scrollBy()方法,滚动的都是该View内部的内容,而LinearLayout中的内容就是我们的两个Button,如果你直接调用button的scroll方法的话,那结果一定不是你想看到的。 
另外还有一点需要注意,就是两个scroll方法中传入的参数,第一个参数x表示相对于当前位置横向移动的距离,正值向左移动,负值向右移动,单位是像素。第二个参数y表示相对于当前位置纵向移动的距离,正值向上移动,负值向下移动,单位是像素。 
那说了这么多,scrollTo()和scrollBy()这两个方法到底有什么区别呢?其实运行一下代码我们就能立刻知道了: 


可以看到,当我们点击scrollTo按钮时,两个按钮会一起向右下方滚动,因为我们传入的参数是-60和-100,因此向右下方移动是正确的。但是你会发现,之后再点击scrollTo按钮就没有任何作用了,界面不会再继续滚动,只有点击scrollBy按钮界面才会继续滚动,并且不停点击scrollBy按钮界面会一起滚动下去。 
现在我们再来回头看一下这两个方法的区别,scrollTo()方法是让View相对于初始的位置滚动某段距离,由于View的初始位置是不变的,因此不管我们点击多少次scrollTo按钮滚动到的都将是同一个位置。而scrollBy()方法则是让View相对于当前的位置滚动某段距离,那每当我们点击一次scrollBy按钮,View的当前位置都进行了变动,因此不停点击会一直向右下方移动。 
通过这个例子来理解,相信大家已经把scrollTo()和scrollBy()这两个方法的区别搞清楚了,但是现在还有一个问题,从上图中大家也能看得出来,目前使用这两个方法完成的滚动效果是跳跃式的,没有任何平滑滚动的效果。没错,只靠scrollTo()和scrollBy()这两个方法是很难完成ViewPager这样的效果的,因此我们还需要借助另外一个关键性的工具,也就我们今天的主角Scroller。 
Scroller的基本用法其实还是比较简单的,主要可以分为以下几个步骤: 
1. 创建Scroller的实例 
2. 调用startScroll()方法来初始化滚动数据并刷新界面 
3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 
那么下面我们就按照上述的步骤,通过一个模仿ViewPager的简易例子来学习和理解一下Scroller的用法。 
新建一个ScrollerLayout并让它继承自ViewGroup来作为我们的简易ViewPager布局,代码如下所示:

/**
 * Created by guolin on 16/1/12.
 */
public class ScrollerLayout extends ViewGroup {

    /**
     * 用于完成滚动操作的实例
     */
    private Scroller mScroller;

    /**
     * 判定为拖动的最小移动像素数
     */
    private int mTouchSlop;

    /**
     * 手机按下时的屏幕坐标
     */
    private float mXDown;

    /**
     * 手机当时所处的屏幕坐标
     */
    private float mXMove;

    /**
     * 上次触发ACTION_MOVE事件时的屏幕坐标
     */
    private float mXLastMove;

    /**
     * 界面可滚动的左边界
     */
    private int leftBorder;

    /**
     * 界面可滚动的右边界
     */
    private int rightBorder;

    public ScrollerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 第一步,创建Scroller的实例
        mScroller = new Scroller(context);
        ViewConfiguration configuration = ViewConfiguration.get(context);
        // 获取TouchSlop值
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            // 为ScrollerLayout中的每一个子控件测量大小
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                // 为ScrollerLayout中的每一个子控件在水平方向上进行布局
                childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
            }
            // 初始化左右边界值
            leftBorder = getChildAt(0).getLeft();
            rightBorder = getChildAt(getChildCount() - 1).getRight();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mXDown = ev.getRawX();
                mXLastMove = mXDown;
                break;
            case MotionEvent.ACTION_MOVE:
                mXMove = ev.getRawX();
                float diff = Math.abs(mXMove - mXDown);
                mXLastMove = mXMove;
                // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
                if (diff > mTouchSlop) {
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mXMove = event.getRawX();
                int scrolledX = (int) (mXLastMove - mXMove);
                if (getScrollX() + scrolledX < leftBorder) {
                    scrollTo(leftBorder, 0);
                    return true;
                } else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
                    scrollTo(rightBorder - getWidth(), 0);
                    return true;
                }
                scrollBy(scrolledX, 0);
                mXLastMove = mXMove;
                break;
            case MotionEvent.ACTION_UP:
                // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
                int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
                int dx = targetIndex * getWidth() - getScrollX();
                // 第二步,调用startScroll()方法来初始化滚动数据并刷新界面
                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public void computeScroll() {
        // 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

整个Scroller用法的代码都在这里了,代码并不长,一共才100多行,我们一点点来看。 
首先在ScrollerLayout的构造函数里面我们进行了上述步骤中的第一步操作,即创建Scroller的实例,由于Scroller的实例只需创建一次,因此我们把它放到构造函数里面执行。另外在构建函数中我们还初始化的TouchSlop的值,这个值在后面将用于判断当前用户的操作是否是拖动。 
接着重写onMeasure()方法和onLayout()方法,在onMeasure()方法中测量ScrollerLayout里的每一个子控件的大小,在onLayout()方法中为ScrollerLayout里的每一个子控件在水平方向上进行布局。如果有朋友对这两个方法的作用还不理解,可以参照我之前写的一篇文章 Android视图绘制流程完全解析,带你一步步深入了解View(二) 。 
接着重写onInterceptTouchEvent()方法, 在这个方法中我们记录了用户手指按下时的X坐标位置,以及用户手指在屏幕上拖动时的X坐标位置,当两者之间的距离大于TouchSlop值时,就认为用户正在拖动布局,然后我们就将事件在这里拦截掉,阻止事件传递到子控件当中。 
那么当我们把事件拦截掉之后,就会将事件交给ScrollerLayout的onTouchEvent()方法来处理。如果当前事件是ACTION_MOVE,说明用户正在拖动布局,那么我们就应该对布局内容进行滚动从而影响拖动事件,实现的方式就是使用我们刚刚所学的scrollBy()方法,用户拖动了多少这里就scrollBy多少。另外为了防止用户拖出边界这里还专门做了边界保护,当拖出边界时就调用scrollTo()方法来回到边界位置。 
如果当前事件是ACTION_UP时,说明用户手指抬起来了,但是目前很有可能用户只是将布局拖动到了中间,我们不可能让布局就这么停留在中间的位置,因此接下来就需要借助Scroller来完成后续的滚动操作。首先这里我们先根据当前的滚动位置来计算布局应该继续滚动到哪一个子控件的页面,然后计算出距离该页面还需滚动多少距离。接下来我们就该进行上述步骤中的第二步操作,调用startScroll()方法来初始化滚动数据并刷新界面。startScroll()方法接收四个参数,第一个参数是滚动开始时X的坐标,第二个参数是滚动开始时Y的坐标,第三个参数是横向滚动的距离,正值表示向左滚动,第四个参数是纵向滚动的距离,正值表示向上滚动。紧接着调用invalidate()方法来刷新界面。 
现在前两步都已经完成了,最后我们还需要进行第三步操作,即重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 。在整个后续的平滑滚动过程中,computeScroll()方法是会一直被调用的,因此我们需要不断调用Scroller的computeScrollOffset()方法来进行判断滚动操作是否已经完成了,如果还没完成的话,那就继续调用scrollTo()方法,并把Scroller的curX和curY坐标传入,然后刷新界面从而完成平滑滚动的操作。 
现在ScrollerLayout已经准备好了,接下来我们修改activity_main.xml布局中的内容,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.example.guolin.scrollertest.ScrollerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is first child view"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is second child view"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="This is third child view"/>

</com.example.guolin.scrollertest.ScrollerLayout>
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

可以看到,这里我们在ScrollerLayout中放置了三个按钮用来进行测试,其实这里不仅可以放置按钮,放置任何控件都是没问题的。 
最后MainActivity当中删除掉之前测试的代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

好的,所有代码都在这里了,现在我们可以运行一下程序来看一看效果了,如下图所示: 


怎么样,是不是感觉有点像一个简易的ViewPager了?其实借助Scroller,很多漂亮的滚动效果都可以轻松完成,比如实现图片轮播之类的特效。当然就目前这一个例子来讲,我们只是借助它来学习了一下Scroller的基本用法,例子本身有很多的功能点都没有去实现,比如说ViewPager会根据用户手指滑动速度的快慢来决定是否要翻页,这个功能在我们的例子中并没有体现出来,不过大家也可以当成自我训练来尝试实现一下。


好的,那么本篇文章就到这里,相信通过这篇文章的学习,大家已经能够熟练掌握Scroller的使用方法了,当然ViewPager的内部实现要比这复杂得多,如果有朋友对ViewPager的源码感兴趣也可以尝试去读一下,不过一定需要非常扎实的基本功才行。

关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

微信扫一扫下方二维码即可关注:

         

153
 
9
 
 
我的同类文章

参考知识库

img
Android知识库

img
微信开发知识库

猜你在找
Android高级界面控件难点精讲
Android《自定义控件》视频,震撼发布!
【Android APP开发】Android高级商业布局快速实现
Android自定义控件系列之九宫格解锁
Android开发之初窥门径
Android Scroller完全解析
Android Service完全解析关于服务你所需知道的一切上
Android Fragment完全解析关于碎片你所需知道的一切
Android Service完全解析关于服务你所需知道的一切转载
Android Fragment完全解析关于碎片你所需知道的一切
查看评论
113楼  JGAMPTM_2016-12-29 12:22发表  [回复]
前前后后琢磨了一天了,总算弄懂了。 哈哈!
112楼  KumuiP2016-12-16 15:48发表  [回复]
大神您好,我使用RecyclerView适配模式,通过对item位置的函数映射做了VP+GV表情的那种分页。想给我的RecyclerView加上VP的分页滑动选择效果,但是在具有适配器的VIew中,getScrollX此类方法,都是0。我觉得是因为回收机制导致不能计算正确的ScrollX值导致的。VP直接继承的是ViewGroup并且通过方向layout实现的。
我这种情况不知道怎么回到一个固定的边界。可以帮忙研究一下吗。
Re:  KumuiP2016-12-16 16:45发表  [回复]
回复KumuiP:想到办法了,在Action_up的时候拿到显示出来的页面的第一个itemVIew,判断它在在屏幕上的位置。哇哦
111楼  qq_354847822016-10-28 15:35发表  [回复]
如果想实现左右拉到头,会有一个回弹的效果好做吗?
Re:  mangues2016-11-08 15:55发表  [回复]
回复qq_35484782: leftBorder = getChildAt(0).getLeft()-100;
rightBorder = getChildAt(getChildCount() - 1).getRight()+100;
这样可以实现
110楼  你只看到我的帅2016-09-19 14:17发表  [回复]
viewgroup里面为什么getwidth是720px。而且getMeasureWidth也是720px---114行
109楼  hu_ba_lin2016-07-29 11:08发表  [回复]
写得很好。但例子确实不够完美,必须在 onTouchEvent() 中处理 ACTION_DOWN 事件,并返回 true,否则子 View 只是按 button 这类也处理 ACTION_DOWN 事件的 View,换成 textview, imageview 就不行了。
Re:  lmo280210802016-10-27 17:04发表  [回复]
回复hu_ba_lin:确实确实,我也发现了这个问题~如果弄成TextView,整个View Tree上就没有view响应down事件了
108楼  hu_ba_lin2016-07-29 11:08发表  [回复]
写得很好。但例子确实不够完美,必须在 onTouchEvent() 中处理 ACTION_DOWN 事件,并返回 true,否则子 View 只是按 button 这类也处理 ACTION_DOWN 事件的 View,换成 textview, imageview 就不行了。
107楼  朱玉刚2016-06-14 14:56发表  [回复]
啃完郭神所有帖子!!!
106楼  y4497567702016-06-07 11:17发表  [回复]
http://hukai.me/android-deeper-touch-event-dispatch-process/ 按照这个篇文章说的事件分发,当onInterceptEvent的Move事件返回true时,对应的是第六种情况,然后本身不会在onEvent中响应Move事件,子控件也只会响应Action_Cancel事件,可郭神这Move事件是怎么执行的???
105楼  cyxevil2016-05-13 16:13发表  [回复]
我改成imageview就不行了,什么鬼
Re:  cyxevil2016-05-13 16:33发表  [回复]
回复cyxevil:好吧,忘记加点击了
104楼  cyxevil2016-05-13 15:53发表  [回复]
如果是不确定的个数怎么办?
103楼  adougangan2016-05-09 16:40发表  [回复]
放textview 怎么滑动不了啊
Re:  hu_hu12322016-12-13 16:43发表  [回复]
回复adougangan:从如果不消费从onInter..传下来的down事件默认也不会响应move 和up事件 所以在ontochEven down的时候reture ture;
102楼  帅气大果果2016-04-22 13:57发表  [回复]
学习了,最后一个布局文件截图内容不是很清楚,照着做弄不出来,最后自己把那个自定义空间放在一个现行布局里好了
101楼  sky琪仔2016-04-13 09:54发表  [回复]
不错!言简意赅!!!顶下~
100楼  蜗牛搬砖2016-04-04 20:53发表  [回复]
可以分析下DrawerLayout吗?
99楼  内裤小王子2016-03-28 10:56发表  [回复]
谢谢
98楼  郭朝2016-03-22 12:15发表  [回复]
椅子
97楼  meiwangli2016-03-17 16:41发表  [回复]
各位大神商量好的换头像么
96楼  guiying7122016-03-17 10:30发表  [回复]
听说你换了头像,特意前来围观。嫂子真心漂亮
95楼  a628943102016-03-17 04:28发表  [回复]
大神, 问一下你的动态图片是怎么弄的?
94楼  _汪_洋_2016-03-16 09:43发表  [回复]
我就来看看
93楼  五斗星君2016-03-15 16:33发表  [回复]
明白了,正值向左移动负值向右移动,这点跟坐标体系有点出入
92楼  五斗星君2016-03-15 14:35发表  [回复]
101 int scrolledX = (int) (mXLastMove - mXMove); 请问郭神,101行为什么不是 int scrolledX = (int) (mXMove - mXLastMove); onIntercept方法不是只会调用一次吗?
91楼  梦一场yu2016-03-15 14:34发表  [回复]
为什么ViewConfiguration.get(context).getTouchSlop() 这种方法获取到值和你用的那种方法 获取到的值不一样,在实际开发中,我们应该用那种方法获取到这个值那?
90楼  pengmixx2016-03-14 00:14发表  [回复]
不错!
89楼  yxhuangCH2016-03-12 21:40发表  [回复]
《Android 开发艺术探索》一书的第三章 3.3.1 使用 Scroller 结合郭大侠在这篇博客,刚好是完美的组合。
88楼  G_sharloks2016-03-09 22:34发表  [回复]
受益~~~~~~~~
87楼  _jeevan2016-03-08 09:40发表  [回复]
哈哈,感谢分享。看第一行代码时就习惯了这种排版,现在看着感觉真亲切,仿若第一行代码的续版
86楼  outdoorsmanm2016-03-05 21:26发表  [回复]
回复outdoorsmanm:我修正下,想了好久,其实在OnTouchEvent不管返回什么,都会去执行,因为拦截了,根本不会传到子view中,而且子view是返回true的,就代表整个事件会触发move和up
85楼  hyy_hyy2016-03-05 13:47发表  [回复]
有Bug, 需要处理OnTouchEvent的Down事件,不然对于TextView之类不可点击View无法移动。
84楼  hyy_hyy2016-03-05 13:45发表  [回复]
其实有bug, 把内部View换成一个不可点击按钮(Button -> TextView)会发现不可以移动,原因是ViewGroup的OnTouchEvent默认返回false, TextView 的OnTouchEvent默认返回也是false,这样down事件往上传的时候ViewGroup也不能消费,后续的的一系列事件都不会传给该ViewGroup,导致不可移动,解决方案就是让ViewGroup的OnTouchEvent方法里在处理down事件时返回true就行啦。
Re:  帅气大果果2016-04-22 14:22发表  [回复]
回复hyy_hyy:我也发现这个bug
添加 一行代码:case MotionEvent.ACTION_DOWN:return true;就好了
83楼  7潜伏72016-03-04 18:21发表  [回复]
demo链接还没给?
82楼  qq_225364832016-02-18 14:29发表  [回复]
666
81楼  长城Great2016-02-15 11:26发表  [回复]
看郭神的文章受益匪浅
80楼  郭朝2016-02-14 11:03发表  [回复]
郭神继续加油。。
79楼  qq_334150272016-02-07 14:47发表  [回复]
郭神第一行代码有没有再版的打算?
78楼  lyj10053535532016-02-03 18:08发表  [回复]
大神,eclipse上没有AppCompatActivity,怎么在eclipse上也通用?
Re:  guolin2016-02-14 18:15发表  [回复]
回复lyj1005353553:这有什么关系,你就用Activity好了,对于本篇文章来说没有任何影响。
Re:  outdoorsmanm2016-03-04 15:20发表  [回复]
回复guolin:郭神,onTouchEvent返回false,应该不是这样的吧,返回false当前的Move事件都不会被消费
77楼  仰子瞻2016-01-31 09:51发表  [回复]
再看郭神写的,又有很多收获
76楼  12期-王金龙2016-01-29 19:57发表  [回复]
学习了,
75楼  qq_338702702016-01-28 08:33发表  [回复]
尽管我不懂 看着仍然高大上
74楼  aguica2016-01-27 15:44发表  [回复]
onTouchEvnet()应该返回true才行吧,super.onTouchEvent默认为false。那么在ACTION_DOWN事件之后onTouchEvnet()方法返回false之后的事件全部不会响应了。
Re:  WuRuiF2016-03-01 17:33发表  [回复]
回复aguica:如果一个空间不拦截除action_down之外的事件,那么后续的事件还是会继续传递给它 不影响的,如果不拦截action_down事件,那么后续的事件也不会再交给它处理了。
Re:  outdoorsmanm2016-02-27 14:15发表  [回复]
回复aguica:但是确实是返回false也能传递move和up事件,这点我都没搞清楚为什么
Re:  sun4win2016-04-25 19:58发表  [回复]
回复outdoorsmanm:当内部控件是button的时候,ScrollerLayout中的onTouchEvent的ACTION_DOWN根本就没执行,被子view的onTouchEvent中的ACTION_DOWN执行了。
73楼  Weiggle2016-01-27 14:54发表  [回复]
向右滑动没效果呀
72楼  微凉的心情2016-01-26 18:17发表  [回复]
作为item添加到listview中无法显示,是什么原因
71楼  wbsld2016-01-26 17:07发表  [回复]
终于搞懂了,多谢啊
70楼  vicwudi2016-01-26 11:53发表  [回复]
ViewConfiguration configuration = ViewConfiguration.get(context);
// 获取TouchSlop值
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
大神,这两句代码没有看懂啊,ViewConfigurationCompat.getScaledPagingTouchSlop(configuration)是什么意思啊?
Re:  如晦2016-01-27 10:16发表  [回复]
回复vicwudi:ViewConfiguration顾名思义,就是定义了一些View需要的常量。郭神取得mPagingTouchSlop是判断是否滑动的最小距离。
69楼  祝你幸福3652016-01-25 17:13发表  [回复]
想郭神学习
68楼  冷漠的学徒2016-01-24 21:03发表  [回复]
郭神,期盼你能讲讲service可在后台长期运行的内容,总感觉这些东西被雪藏了,找不到相关资料,android5.0后,fork产生的守护进程也会被回收,现在不知道怎么去解决这种需求。谢谢
Re:  lvzhongyi2016-01-27 10:48发表  [回复]
回复冷漠的学徒:郭神恰好是非常不建议程序猿这么做的,他曾说过大概意思是,google想尽办法不让开发者用这种方式让自己的程序一直在后台跑,开发者却千方百计的想要在后台运行自己的程序
Re:  冷漠的学徒2016-03-27 18:58发表  [回复]
回复lvzhongyi:我也知道这个理,但是无脑的客户太多啊
67楼  yp1992442016-01-24 19:08发表  [回复]
不是作用在子view上,为什么clickable为true的子view才好使,这是什么原理呢?求大神解答!!!!!!!!!!!!!!
Re:  加冰雪碧2016-02-28 22:54发表  [回复]
回复yp199244:可以将viewGroup的clickable设置为true
Re:  bawomingtian1232016-01-26 10:04发表  [回复]
回复yp199244:经过研究发现,当view是不可点击的时候,Down事件会返回给ViewGroup的onTouchEvent来处理,但时ViewGroup的onTouchEvent默认返回super.onTouchEnent(),把Down事件提交给Activity的onTouchEvent来处理,那么后续的Move事件也就不会继续下发给自定义的ViewGroup了,所以 onInterceptTouchEvent也就不会执行,进而ViewGroup的OnTouchEvent也就不会执行了,所有也就不能左右滑动切换
Re:  adougangan2016-05-09 16:57发表  [回复]
回复bawomingtian123:我也发现了这个问题
Re:  adougangan2016-05-09 16:59发表  [回复]
回复adougangan:android:clickable="true" 加上这个确实好用,感谢楼上的方法
66楼  zhuqianbin2016-01-24 10:53发表  [回复]
Good job!
65楼  Looper景2016-01-23 20:43发表  [回复]
仰慕很久了
64楼  zzxlovezzy12016-01-23 17:11发表  [回复]
好啊
63楼  yixiaops2016-01-23 10:02发表  [回复]
占位
62楼  why的欢乐2016-01-22 18:39发表  [回复]
战略马克
61楼  路路路lbj2016-01-22 16:15发表  [回复]
滑动没有反应。。
60楼  fuyk122016-01-22 16:00发表  [回复]
郭神,下面的代码为什么要这样子写啊,看了好久,实在想不通
// 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
Re:  qq_186020352016-05-25 17:36发表  [回复]
回复fuyk12:这段代码的作用是:得到当前滚动的X坐标值+(getWidth() / 2)的作用是我滚动一半的宽度了,就可以得到下一个indexpage ,当然如果getWidth() / 3 。只要getScrollX() = getWidth() / 1就可以滚动到下一个indexpage了
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
这段就很容易理解了,得到当前滚动的差值(距离),怎么理解呢?比如你手动滚动了getWidth() +getWidth()/(/1/3) ,那你还需要接着滚getWidth()*(2/3)也就是 dx
int dx = targetIndex * getWidth() - getScrollX();
//这里只是设置当前view的参数还未正在的scroll
mScroller.startScroll(getScrollX(), 0, dx, 0);
所以手动的去调用invalidate ,会调到computeScroll
invalidate();

唉,郭神,只是提供了思路具体怎么实现了,没有说,多去看看这些方法,多理解,这些问题都不是问题了
59楼  fuyk122016-01-22 15:59发表  [回复]
郭神,看了一天,还是没搞懂下面两行代码为什么要这样子写啊,能指点下吗?
// 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
58楼  淡淡的蓝月2016-01-22 15:00发表  [回复]
onMeasure没有调用setMeasuredDimension方法,所以ViewGroup的宽度为MeasureSpec.getSize(mode)
57楼  星星de夜空2016-01-22 14:24发表  [回复]
大神就是666666666666
56楼  nowhere___boy2016-01-22 14:07发表  [回复]
做gif图是用的虚拟机吗
55楼  花-开-花-谢2016-01-22 13:59发表  [回复]
亲 能否讲一下增量升级,或其它一些比较高级且实用的东东,好让我们大开眼界
Re:  月出惊弓鸟2016-06-02 15:30发表  [回复]
回复花-开-花-谢:先把四大组件搞清楚吧
Re:  socketsyuhai2016-01-22 17:54发表  [回复]
回复花-开-花-谢:增量更新+1
54楼  cgangnan2016-01-22 13:50发表  [回复]
学习了
53楼  刘某人程序员2016-01-22 11:31发表  [回复]
前排没了
52楼  huxq172016-01-22 09:39发表  [回复]
还以为是重写adapterview的,总体不错,但是有点小失望
51楼  张腾元_Ternence2016-01-21 18:50发表  [回复]
mark
50楼  l13343881652016-01-21 18:05发表  [回复]
看郭神的头像,每次看到郭神的名字,我就想起那个相声演员,是谁来着
49楼  home乐乐呵2016-01-21 17:40发表  [回复]
哈哈,在android开发艺术探索昨天刚看见,但没试过。
48楼  阿七哟2016-01-21 17:40发表  [回复]
围观boy
47楼  絕地獊狼2016-01-21 16:24发表  [回复]
int targetIndex = 0;
if (scrollX > 0) {
targetIndex = (getScrollX() + getWidth() * 3 / 4) / getWidth();
} else {
targetIndex = (getScrollX() + getWidth() / 4) / getWidth();
}
超过宽度四分之一时就可以滚动了
46楼  絕地獊狼2016-01-21 16:17发表  [回复]
在布局文件中设置组件属性clickble为true就行了
45楼  浅浅无奈2016-01-21 15:17发表  [回复]
哎。。沙发不是我的
44楼  jr_T2016-01-21 15:16发表  [回复]
虽然还没看,但是先顶!先顶!再说
43楼  cheyiliu2016-01-21 14:52发表  [回复]
mark
42楼  f4971966892016-01-21 14:36发表  [回复]
长知识了……
41楼  巴顿将军6262016-01-21 14:18发表  [回复]
确实很厉害,唉,看不懂哎
40楼  zm199303202016-01-21 14:10发表  [回复]
留名,非常感谢!
39楼  Daniel_Renx2016-01-21 13:54发表  [回复]
郭神终于写博客啦!
38楼  奥黛丽-赫本2016-01-21 12:50发表  [回复]
引用“laoxidi”的评论: 郭神,怎么样才算得上基础扎实?或者说 基础扎实怎么提高?
37楼  liverandroid2016-01-21 12:13发表  [回复]
郭神 出本Android高级工程师的书吧
36楼  a1017839170a2016-01-21 11:46发表  [回复]
只有可点击的view才能在这个scrollerlayout中用
Re:  絕地獊狼2016-01-21 16:15发表  [回复]
回复a1017839170a:在布局文件中设置组件属性clickble为true就行了
35楼  絕地獊狼2016-01-21 11:34发表  [回复]
先顶再看
34楼  lvzhongyi2016-01-21 11:32发表  [回复]
郭婶再次上头条,必顶
33楼  彼岸花开洒落一地温热2016-01-21 11:14发表  [回复]
学习了。
32楼  江清清2016-01-21 11:03发表  [回复]
精彩
Re:  _鸿博_2016-01-21 11:30发表  [回复]
回复江清清:
Re:  江清清2016-01-21 12:34发表  [回复]
回复_鸿博_:这是什么表情
31楼  DonnieSky2016-01-21 10:43发表  [回复]
火钳刘明
30楼  haoshili1232016-01-21 10:39发表  [回复]
来晚了
29楼  ming198811242016-01-21 10:39发表  [回复]
123456
28楼  RoccoCJ2016-01-21 10:29发表  [回复]
留个言,感染点大神的气息。
Re:  hyb7452506182016-02-01 10:40发表  [回复]
回复RoccoCJ:陈俊
27楼  三精-大精wing2016-01-21 10:26发表  [回复]
26楼  阿拉灯神灯2016-01-21 10:24发表  [回复]
郭神必须先顶后看
25楼  JackLondon-2016-01-21 10:23发表  [回复]
郭神你好,最近在慢慢摸索package android.view, android.view.Window.Callback,android.content.context这些东西,对于大方向有点了解,但是还是觉得迷迷糊糊的,对于基本的原理还是把握的不够,请问如何再提高呢?
24楼  cyq7on2016-01-21 10:02发表  [回复]
lz棒棒哒
23楼  cyq7on2016-01-21 10:02发表  [回复]
lz棒棒哒
22楼  南淮雪2016-01-21 10:00发表  [回复]
(╯‵□′)╯︵┻━┻终究还是来晚了一步
21楼  更年期般的小青年2016-01-21 09:49发表  [回复]
终于等到了新文章
20楼  Mark4s2016-01-21 09:47发表  [回复]
沙发。
19楼  唐志杰2016-01-21 09:46发表  [回复]
非常值得一看,又学到了
18楼  mengqianyue2016-01-21 09:37发表  [回复]
先码再看。。。,另外什么时候出本Android进阶的书啊
17楼  zxw1365114852016-01-21 09:36发表  [回复]
学习了!
16楼  mizno2016-01-21 09:31发表  [回复]
郭神,先回复顶上,然后在拜读新作哈!
15楼  rex_J2016-01-21 09:27发表  [回复]
郭神不出新书了么?很喜欢郭神的书啊
Re:  wenmin922016-01-21 19:23发表  [回复]
回复rex_J:在年终总结中,郭神说了2016年木有出书打算。
14楼  soledadzz2016-01-21 09:26发表  [回复]
真早啊,郭神
13楼  love_download2016-01-21 09:25发表  [回复]
又有新知识学习了^_^
12楼  MacToWin2016-01-21 09:16发表  [回复]
郭神,换成宽版后代码在手机上的阅读体验有所下降啊
11楼  SnowWitch2016-01-21 09:17发表  [回复]
谁抢了我的沙发。。。。
10楼  BHGXZB2016-01-21 09:11发表  [回复]
抢个10L压压惊 顶郭神
9楼  ilike_ly2016-01-21 09:09发表  [回复]
我去 就剩下地板了
8楼  Bluesaaa2016-01-21 09:09发表  [回复]
挖槽 我来晚了
7楼  逍遥若诗2016-01-21 09:08发表  [回复]
先顶再看
6楼  一盒小盒饭2016-01-21 09:08发表  [回复]
不愧郭神啊
5楼  不倦2016-01-21 09:08发表  [回复]
卧槽 天花板
4楼  小阿蒙2016-01-21 09:06发表  [回复]
卧槽,我的地板
3楼  奔流的溪2016-01-21 09:06发表  [回复]
板凳 只有了
2楼  白骆驼之爱2016-01-21 09:06发表  [回复]
板凳
Re:  SnowWitch2016-01-21 09:17发表  [回复]
回复白骆驼之爱:你不记得那句话了。。。
Re:  白骆驼之爱2016-01-21 13:54发表  [回复]
回复SnowWitch:我不是一楼啊
1楼  csdnay12016-01-21 09:06发表  [回复] [引用] [举报]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值