您的位置:首页 > 移动开发 > Android开发

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

2014-07-01 16:40 453 查看
From:http://orgcent.com/android-custom-vertical-scroll-textview/

其实要让TextView能够滚动,可以使用ScrollView/HorizontalScrollView或者设置ScrollingMovementMethod来实现。

点击查看:android实现TextView垂直或水平滚动

下面自定义垂直滚动的TextView,主要是用来学习Scroller的使用。关于ScrollTextView的实现,可以看下面的介绍和源码。

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;

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: