有的时候我们为了追求界面的美观性,希望将数字显示出来的时候有动画效果,比如界面显示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>
如果有更好的方法或者别的思路,欢迎提出来共同学习