有的时候我们为了追求界面的美观性,希望将数字显示出来的时候有动画效果,比如界面显示315,我们可以让这几个数字自动纵向滚动,最后停留在315,这样显示出来效果更好一些。


要实现这种自动滚动的效果,我们首先想到的是自定义SurfaceView,在SurfaceView中启动一个线程来完成需要的工作。我这里自定义的SurfaceView有一个方法

setCircleAndNumber(int scrollCircle, int


package com.example.scrollnumber;

import com.example.scrollnumber.R;
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
	ScrollNumber scrollNumber;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		scrollNumber = (ScrollNumber) findViewById(R.id.scroll_number);
		scrollNumber.setCircleAndNumber(2, 315);
	}
}



这里旋转2圈后,停留在315这个数字


首先我们需要设置一个背景,这个背景是正好包含我们要滚动的数字,四周留一点空隙即可

float startX = 30f; // 数字的初始x坐标
			float startY = 30f; // 数字的初始y坐标
			float curY = startY; // 当前的y坐标

			FontMetrics fontMetrics = paint.getFontMetrics();
			float numberHeight = fontMetrics.bottom - fontMetrics.top; // 数字的高度
			RectF r = new RectF(startX, startY - numberHeight / 2 - 10, startX
					+ paint.measureText("" + scrollNumber), startY + numberHeight / 2);



完后我们从绘制000开始(以滚动到315为例),我们需要同时绘制出111,在000的上面,但是在背景外,所以111初始时是看不见的,完后我们开始向下滚动000,每次滚动10,每50毫秒滚动一次,这样让上面的111慢慢显示出来,基本原理就是用canvas.drawText,不断改变y坐标,当y坐标大于初始坐标加上字体高度时,就应该绘制下一个数字了,这里也就是等到111显示出来的时候,我们就绘制上面的222

if (curY < startY + numberHeight) {
						curY += movingStep;
						
						if (elapseCount == scrollCircle * radix + toNumber - 1 && curY > startY + numberHeight) { // 如果已经在滚向最后一位数字,并且如果滚动movingStep会超过要移动到的最终距离,则只滚动需要的部分
							curY = startY + numberHeight;
						}
					} else { // 两个数字之间的高度差是numberHeight,所以当新的数字出现时,要给curY和elapseCount重新赋值
						curY = startY;
						elapseCount++;
					。。。 }



以此类推,直到我们转过了传过来的旋转的圈数后(这个例子里面是2圈),我们再从0旋转到3,那么最高位就停止了,我们记录下来,然后我们用alreadyBits记录已经滚动到位的位数,用totalBits记录总位数,两者相比,如果不相等说明没有滚动完,就接着继续滚动下一位,以此类推

if (alreadyBits == totalBits) { // 所有位数滚动完毕
								isRun = false;
							} else { // 还有位数没有滚动完毕
								int nextValue = (scrollNumber + "").charAt(alreadyBits) - 48; // 下一位要滚动到的值
								
								toNumber = elapseCount; // 刚滚动完毕那位的值
								
								if (toNumber >= nextValue) { // 如果下一位比当前位要小或者相等,那么要滚动到的位数就要+10,比如21,那么当2滚动到位的时候,1那位的值是2,它还要滚动一圈才能到1
									toNumber = radix + nextValue;
								} else {
									toNumber = nextValue;
								}
							}



直到最后alreadyBits和totalBits相等,我们将isRun设为false,这个布尔型变量用来控制线程是否继续执行,完整代码如下:


package com.example.scrollnumber;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;

public class ScrollNumber extends SurfaceView implements Callback {
	private boolean isRun;
	private SurfaceHolder holder;
	private int scrollCircle; // 滚动圈数,0-9为一圈
	private int scrollNumber; // 滚动到的数字

	public ScrollNumber(Context context) {
		super(context);
	}
	
	public ScrollNumber(Context context, AttributeSet attr) {
		super(context, attr);
		
		holder = getHolder();
		holder.addCallback(this);
	}
	
	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder arg0) {
		isRun = true;
		new NumberThread(holder).start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder arg0) {
		isRun = false;
	}
	
	public void setCircleAndNumber(int scrollCircle, int scrollNumber) {
		this.scrollCircle = scrollCircle;
		this.scrollNumber = scrollNumber;
	}

	class NumberThread extends Thread {
		SurfaceHolder holder;

		public NumberThread(SurfaceHolder holder) {
			this.holder = holder;
		}

		@Override
		public void run() {
			super.run();

			float startX = 30f; // 数字的初始x坐标
			float startY = 30f; // 数字的初始y坐标
			float curY = startY; // 当前的y坐标

			Paint paint = new Paint();
			paint.setColor(Color.WHITE);
			paint.setAntiAlias(true);
			paint.setTextSize(20);
			FontMetrics fontMetrics = paint.getFontMetrics();
			float numberHeight = fontMetrics.bottom - fontMetrics.top; // 数字的高度
			RectF r = new RectF(startX, startY - numberHeight / 2 - 10, startX
					+ paint.measureText("" + scrollNumber), startY + numberHeight / 2);
			
			int elapseCount = 0; // 每一位要转过的数字的个数
			float width = paint.measureText("0"); // 一个数字的宽度
			int totalBits = ("" + scrollNumber).length(); // 要滚动的数字的位数
			boolean[] scrollFinished = new boolean[totalBits]; // 纪录每一位是否滚动完毕
			int toNumber = scrollNumber / (int)Math.pow(10, totalBits - 1); // 每一位要滚动到的数字,初始值为最高位的值
			int alreadyBits = 0; // 当前已滚动完毕的位数
			int movingStep = 10; // 每次滚动的距离
			int radix = 10; // 基数,也就是0-9共10个数字
			int interval = 50; // 每次滚动的间隔

			while (isRun) {
				Canvas canvas = null;

				try {
					canvas = holder.lockCanvas();

					canvas.clipRect(r);

					canvas.drawColor(Color.BLACK);

					for (int i = 0; i < totalBits; i++) {
						if (scrollFinished[i]) { // 该位滚动完成,直接绘制该位数字
							canvas.drawText(("" + scrollNumber).charAt(i) - 48 + "", startX + width * i, startY, paint);
						} else { // 尚在滚动中,需要绘制该位以及下一位数字
							canvas.drawText("" + (elapseCount + 1) % radix, startX + width * i, curY - numberHeight, paint);
							canvas.drawText("" + elapseCount % radix, startX + width * i, curY, paint);
						}
					}
					
					if (curY < startY + numberHeight) {
						curY += movingStep;
						
						if (elapseCount == scrollCircle * radix + toNumber - 1 && curY > startY + numberHeight) { // 如果已经在滚向最后一位数字,并且如果滚动movingStep会超过要移动到的最终距离,则只滚动需要的部分
							curY = startY + numberHeight;
						}
					} else { // 两个数字之间的高度差是numberHeight,所以当新的数字出现时,要给curY和elapseCount重新赋值
						curY = startY;
						elapseCount++;
						
						if (elapseCount == scrollCircle * radix + toNumber) { // alreadyBits位已滚动到位
							scrollFinished[alreadyBits] = true;
							
							scrollCircle = 0; // 一旦最高位转动完毕,后面的位数转动都不再会超过一圈,所以圈数赋值为0
							elapseCount = (scrollNumber + "").charAt(alreadyBits) - 48; // 获取到alreadyBits位的值
							
							alreadyBits++;
							
							if (alreadyBits == totalBits) { // 所有位数滚动完毕
								isRun = false;
							} else { // 还有位数没有滚动完毕
								int nextValue = (scrollNumber + "").charAt(alreadyBits) - 48; // 下一位要滚动到的值
								
								toNumber = elapseCount; // 刚滚动完毕那位的值
								
								if (toNumber >= nextValue) { // 如果下一位比当前位要小或者相等,那么要滚动到的位数就要+10,比如21,那么当2滚动到位的时候,1那位的值是2,它还要滚动一圈才能到1
									toNumber = radix + nextValue;
								} else {
									toNumber = nextValue;
								}
							}
						}
					}

					Thread.sleep(interval);
				} catch (Exception e) {
					Log.d("ScrollNumber", "Error:" + e.toString());
				} finally {
					if (canvas != null) {
						holder.unlockCanvasAndPost(canvas);
					}
				}
			}
		}
	}
}

布局文件如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"
    >

    <com.example.scrollnumber.ScrollNumber
        android:id="@+id/scroll_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</RelativeLayout>



如果有更好的方法或者别的思路,欢迎提出来共同学习