接着上一篇Android View自定义专题一 (view的绘制),我们接下来要在上一篇的基础上为不能换行的textview加上横向滚动条,这里只说x方向滑动(y方向类似)。
实现scroll的思路:
重写onTouchEvent,
- ACTION_DOWN纪录按下x位置,
- ACTION_MOVE用当按下x减去当前x获得需要滑动距离调用scrollBy滑动
- 在ACTION_UP抬起时需要根据当前的速度来惯性的再滑动一段距离,所以需要纪录手指抬起的速度,和需要滑动的最大距离
具体实现:
一、在昨天代码基础上重写onTouchEvent并在ACTION_DOWN纪录手指按下的x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
caseMotionEvent.ACTION_DOWN:
downX = (int) event.getX();//纪录按下位置
break;
caseMotionEvent.ACTION_MOVE:
break;
caseMotionEvent.ACTION_UP:
break;
default:
break;
}
returntrue;
}
|
二、在ACTION_MOVE中获取移动距离,并调用滑动响应距离
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
caseMotionEvent.ACTION_DOWN:
downX = (int) event.getX();
break;
caseMotionEvent.ACTION_MOVE:
intdx = (int) (downX - event.getX());
scrollBy(dx,0);
break;
caseMotionEvent.ACTION_UP:
break;
default:
break;
}
returntrue;
}
|
三、在滑动时要根据滑动边界来限制滑动距离(最小scrollX为0即初始最左位置,最大scrollX=内容最大宽度-view宽度,即可滑动最大距离)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| /**
* 计算view的宽度
* @param measureSpec
* @return
*/
private int measureWidth(intmeasureSpec) {
intresult =0;
intspecMode = MeasureSpec.getMode(measureSpec);
intspecSize = MeasureSpec.getSize(measureSpec);
viewWidth = specSize;//视图view宽度
if(specMode == MeasureSpec.EXACTLY) {// match_parent或具体数值,直接使用
result = specSize;
}else{// 否则自己计算
// 计算文字宽度
result = (int) mTextPaint.measureText(mText) + getPaddingLeft()+ getPaddingRight();
maxWidth = result;//内容宽度
if(specMode == MeasureSpec.AT_MOST) {// wrap_content
// 取specSize和计算出的文字宽度最小数值,如果result大于specSize说明文字超出了view宽度范围
result = Math.min(result, specSize);
}
}
returnresult;
}
|
1
2
3
4
5
6
7
8
9
10
11
| /**
* 获取最大的滑动距离
* @return
*/
public int getMaxScrollX() {
if(maxWidth - viewWidth >0) {
return(maxWidth - viewWidth);
}else{
return0;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| /**
* 对超出范围进行判断
*/
public void scrollBy(intdx,intdy) {
if(getScrollX() + dx > getMaxScrollX()) {//超出最大范围
super.scrollBy(getMaxScrollX() - getScrollX(),0);
}elseif(getScrollX() + dx <0) {//超出最小范围
super.scrollBy(-getScrollX(),0);
}else{
super.scrollBy(dx,0);
}
}
|
四、根据滑动速度进行ACTION_UP的惯性滑动
1
2
3
4
5
6
7
8
| public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initLabelView();
this.flinger =newFlinger(context);
finalViewConfiguration configuration = ViewConfiguration.get(context);
this.minimumVelocity = configuration.getScaledMinimumFlingVelocity();
this.maximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
|
- onTouchEvent进行相关配置并启动滑动线程
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
| @Override
public boolean onTouchEvent(MotionEvent event) {
if(velocityTracker ==null) {
velocityTracker = VelocityTracker.obtain();// 初始化速度追踪器
}
velocityTracker.addMovement(event);// 添加事件到速度追踪器中
switch(event.getAction()) {
caseMotionEvent.ACTION_DOWN:
downX = (int) event.getX();
if(!flinger.isFinished()) {// 如果正在滚动马上停止
flinger.forceFinished();
}
break;
caseMotionEvent.ACTION_MOVE:
intdx = (int) (downX - event.getX());
scrollBy(dx,0);
break;
caseMotionEvent.ACTION_UP:
finalVelocityTracker velocityTracker =this.velocityTracker;
velocityTracker.computeCurrentVelocity(1000, maximumVelocity);//计算当前速度(按1秒为单位)
intvelocityX = (int) velocityTracker.getXVelocity();//获取x方向速度
intvelocityY = (int) velocityTracker.getYVelocity();//获取y方向速度
if(Math.abs(velocityX) > minimumVelocity|| Math.abs(velocityY) > minimumVelocity) {
flinger.start(getScrollX(), getScrollY(), velocityX,0,getMaxScrollX(),0);
}else{// 记得回收
if(this.velocityTracker !=null) {
this.velocityTracker.recycle();
this.velocityTracker =null;
}
}
break;
default:
break;
}
returntrue;
}
|
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
| /**
* 控制滚动的线程
* @author pangff
*/
private class Flinger implements Runnable {
privatefinalScroller scroller;
privateintlastX =0;
privateintlastY =0;
Flinger(Context context) {
scroller =newScroller(context);
}
voidstart(intinitX,intinitY,intinitialVelocityX,intinitialVelocityY,intmaxX,intmaxY) {
scroller.fling(initX, initY, initialVelocityX, initialVelocityY,0,maxX,0, maxY);
lastX = initX;
lastY = initY;
post(this);
}
publicvoidrun() {
if(scroller.isFinished()) {
return;
}
booleanmore = scroller.computeScrollOffset();//获取是否需要继续滑动
intx = scroller.getCurrX();//获取滑动中的当前scrollX
inty = scroller.getCurrY();//获取滑动中的当前scrollY
intdiffX = lastX - x;//取增量
intdiffY = lastY - y;//取增量
if(diffX !=0|| diffY !=0) {
scrollBy(diffX, diffY);
lastX = x;//纪录当前位置
lastY = y;//纪录当前位置
}
if(more) {//如果还需要继续滑动,再次执行
post(this);
}
}
booleanisFinished() {
returnscroller.isFinished();
}
voidforceFinished() {
if(!scroller.isFinished()) {
scroller.forceFinished(true);
}
}
}
|
参考代码
https://github.com/pangff/DemoView/tree/v2