參考網址:
http://orgcent.com/android-custom-vertical-scroll-textview/
http://orgcent.com/android-vertical-horizontal-scroll-textview/
下面將介紹TextView實現滾動的三種方式:
1、嵌套在ScrollView或者HorizontalScrollView中
垂直滾動:
<scrollview android:layout_width="fill_parent" android:layout_height="fill_parent" android:scrollbars="vertical"> <textview android:text="http://orgcent.com ..."/> </scrollview>
水平滾動:使用標簽<horizontalscrollview></horizontalscrollview>
2、設置ScrollingMovementMethod
TextView.setMovementMethod(new ScrollingMovementMethod());
XML中配置:
android:scrollbars="vertical"
3、使用Scroller來自定義TextView
源代碼如下:
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; } }