android自定义View-垂直滚动的TextView

自定义垂直滚动的TextView,主要是用来学习Scroller的使用。关于ScrollTextView的实现,可以看下面的介绍和源码。
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package com.orgcent.demo.view ;

import android.content.Context ;
import android.util.AttributeSet ;
import android.view.MotionEvent ;
import android.view.VelocityTracker ;
import android.view.ViewConfiguration ;
import android.view.animation.DecelerateInterpolator ;
import android.widget.Scroller ;
import android.widget.TextView ;
/**
 * 配置android:scrollbars="vertical",启用垂直滚动条
 *
 * 实现水平滚动就是修改mScrollX,可参考HorizontalScrollView
 *
 * 滚动原理: 在绘制前对画布进行偏移操作
 *
 * 下面是View的绘制机制:
 * |- view.computeScroll()  --用来对mScrollX/Y进行修改。由于在绘制前调用,可调用invalite()来触发
 * |- canvas.translate(-mScrollX,-mScrollY)  --偏移画布
 * |- view.draw()  --绘制
 *  
 * 上述内容可以在View.buildDrawingCache()或ViewGroup.dispatchDraw()->drawChild()中找到.直接查看方法名即可
 *
 * 滚动帮助类:
 * Scroller --用来计算滚动后的偏移值.具体请参考ScrollView和HorizontalScrollView
 * VelocityTracker --速度计算类。根据fling时的按下、抬起动作,计算滚动初速度
 *
 * ScrollTextView--流程解析:
 * 1、onTouchEvent() --使用Scroller来计算滚动偏移值
 * 2、重写computeScroll() --对View的mScrollY进行修改, 此处控制滚动范围
 *
 * 滚动范围:
 * 最小值:0
 * 最大值:所有文本高度+内边距-View高度。也就是超出屏幕的文本高度
 */

public class ScrollTextView extends TextView {
    private Scroller mScroller ;
    private int mTouchSlop ;
    private int mMinimumVelocity ;
    private int mMaximumVelocity ;
   
    private float mLastMotionY ;
    private boolean mIsBeingDragged ;
    private VelocityTracker mVelocityTracker ;
    private int mActivePointerId = INVALID_POINTER ;
   
   
    private static final int INVALID_POINTER = - 1 ;

    public ScrollTextView ( Context context, AttributeSet attrs, int defStyle ) {
    super (context, attrs, defStyle ) ;
    initView ( ) ;
    }

    public ScrollTextView ( Context context, AttributeSet attrs ) {
    super (context, attrs ) ;
    initView ( ) ;
    }

    public ScrollTextView ( Context context ) {
    super (context ) ;
    initView ( ) ;
    }

    private void initView ( ) {
    final Context cx = getContext ( ) ;
    //设置滚动减速器,在fling中会用到
    mScroller = new Scroller (cx, new DecelerateInterpolator (0.5f ) ) ;
    final ViewConfiguration configuration = ViewConfiguration. get (cx ) ;
        mTouchSlop = configuration. getScaledTouchSlop ( ) ;
        mMinimumVelocity = configuration. getScaledMinimumFlingVelocity ( ) ;
        mMaximumVelocity = configuration. getScaledMaximumFlingVelocity ( ) ;
       
    }
   
    /**
     * 此方法为最后机会来修改mScrollX,mScrollY.
     * 这方法后将根据mScrollX,mScrollY来偏移Canvas已实现内容滚动
     */

    @Override
    public void computeScroll ( ) {
    super. computeScroll ( ) ;
       
    final Scroller scroller = mScroller ;
    if (scroller. computeScrollOffset ( ) ) { //正在滚动,让view滚动到当前位置
        int scrollY = scroller. getCurrY ( ) ;
        final int maxY = (getLineCount ( ) * getLineHeight ( ) + getPaddingTop ( ) + getPaddingBottom ( ) ) - getHeight ( ) ;
        boolean toEdge = scrollY < 0 || scrollY > maxY ;
        if (scrollY < 0 )
            scrollY = 0 ;
        else if (scrollY > maxY )
            scrollY = maxY ;
           
        /*
         *下面等同于:
         * mScrollY = scrollY;
         * awakenScrollBars(); //显示滚动条,必须在xml中配置。
         * postInvalidate();
         */

        scrollTo ( 0, scrollY ) ;
        if (toEdge ) //移到两端,由于位置没有发生变化,导致滚动条不显示
            awakenScrollBars ( ) ;
        }
    }
   
    public void fling ( int velocityY ) {
    final int maxY = (getLineCount ( ) * getLineHeight ( ) + getPaddingTop ( ) + getPaddingBottom ( ) ) - getHeight ( ) ;

        mScroller. fling (getScrollX ( ), getScrollY ( ), 0, velocityY, 0, 0, 0,
                Math. max ( 0, maxY ) ) ;

        //刷新,让父控件调用computeScroll()
        invalidate ( ) ;
    }
   
   
    @Override
    public boolean onTouchEvent (MotionEvent ev ) {
    /*
     * 事件处理方式:先自己处理后交给父类处理。
     * PS:方式不同,可能导致效果不同。请根据需求自行修改。
     */

    boolean handled = false ;
    final int contentHeight = getLineCount ( ) * getLineHeight ( ) ;
    if (contentHeight > getHeight ( ) ) {
        handled = processScroll (ev ) ;  
    }
       
    return handled | super. onTouchEvent (ev ) ;
    }

    private boolean processScroll (MotionEvent ev ) {
    boolean handled = false ;
    if (mVelocityTracker == null ) {
            mVelocityTracker = VelocityTracker. obtain ( ) ;
        }
        mVelocityTracker. addMovement (ev ) ; //帮助类,用来在fling时计算移动初速度
       
    final int action = ev. getAction ( ) ;
       
    switch (action ) {
    case MotionEvent. ACTION_DOWN : {
        if ( !mScroller. isFinished ( ) ) {
        mScroller. forceFinished ( true ) ;
        }
           
        mLastMotionY = ev. getY ( ) ;
            mActivePointerId = ev. getPointerId ( 0 ) ;
            mIsBeingDragged = true ;
            handled = true ;
        break ;
    }
    case MotionEvent. ACTION_MOVE : {
        final int pointerId = mActivePointerId ;
        if (mIsBeingDragged && INVALID_POINTER != pointerId ) {
        final int pointerIndex = ev. findPointerIndex (pointerId ) ;
        final float y = ev. getY (pointerIndex ) ;
        int deltaY = ( int ) (mLastMotionY - y ) ;
               
        if ( Math. abs (deltaY ) > mTouchSlop ) { //移动距离(正负代表方向)必须大于ViewConfiguration设置的默认值
            mLastMotionY = y ;
                   
            /*
             * 默认滚动时间为250ms,建议立即滚动,否则滚动效果不明显
             * 或者直接使用scrollBy(0, deltaY);
             */

            mScroller. startScroll (getScrollX ( ), getScrollY ( ), 0, deltaY, 0 ) ;
            invalidate ( ) ;
            handled = true ;
        }
        }
        break ;
    }
    case MotionEvent. ACTION_UP : {
        final int pointerId = mActivePointerId ;
        if (mIsBeingDragged && INVALID_POINTER != pointerId ) {
        final VelocityTracker velocityTracker = mVelocityTracker ;
        velocityTracker. computeCurrentVelocity ( 1000, mMaximumVelocity ) ;
        int initialVelocity = ( int ) velocityTracker. getYVelocity (pointerId ) ;
               
        if ( Math. abs (initialVelocity ) > mMinimumVelocity ) {
            fling ( -initialVelocity ) ;
        }
               
        mActivePointerId = INVALID_POINTER ;
                mIsBeingDragged = false ;

                if (mVelocityTracker != null ) {
                    mVelocityTracker. recycle ( ) ;
                    mVelocityTracker = null ;
                }
               
                handled = true ;
        }
        break ;
    }
    }
    return handled ;
    }   
}

测试DEMO: 点击查看

你可能感兴趣的:(android自定义View-垂直滚动的TextView)