最近在做電視項目,關於TV搜臺,當搜臺時,搜臺頻率會變化,需要一個數字滾動的效果。
如效果圖:
此效果實現比numberpicker簡單很多,不需要處理滑動事件,只需要相應按鍵事件或者其他觸發滾動事件。
先來看佈局文件main.xml
<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" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="horizontal" >
<com.whuthm.rollnumber.RollNumberView
android:id="@+id/roll_number_hundreds_digit"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.whuthm.rollnumber.RollNumberView
android:id="@+id/roll_number_tens_digit"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.whuthm.rollnumber.RollNumberView
android:id="@+id/roll_number_ones_digit"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="29dp"
android:layout_height="@dimen/roll_num_item_Height"
android:orientation="vertical" >
<View
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="center"
android:src="@drawable/roll_number_point" />
</LinearLayout>
<com.whuthm.rollnumber.RollNumberView
android:id="@+id/roll_number_first_decimal_place"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.whuthm.rollnumber.RollNumberView
android:id="@+id/roll_number_second_decimal_place"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/roll_num_item_Height"
android:orientation="vertical" >
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/roll_number_unit"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top"
android:paddingLeft="15dp"
android:text="Khz"
android:textColor="@android:color/white"
android:textSize="54sp"
android:textStyle="bold" >
</TextView>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
字體比較特殊,每一個文字是使用一張圖片,roll_number_image.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="center" />
每一個RollNumberView對應一個數字。
RolNumberView.java
package com.whuthm.rollnumber;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.Scroller;
//此類顯示白色底圖以及遮罩
public class RollNumberView extends ViewGroup {
private static final boolean DEBUG = true;
public static final int DIRECTION_BACKWARD = 0;
public static final int DIRECTION_FORWARD = 1;
public static final int MAX_NUMBER_COUNT = 10;
int mDirection = DIRECTION_FORWARD;
private int mRollWidth = 0;
private int mRollHeight = 0;
private RollNumberChildrenView mChildrenView;
public RollNumberView(Context context) {
this(context, null);
}
public RollNumberView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RollNumberView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 此方法一定要調用
setWillNotDraw(false);
final Resources res = getResources();
mRollWidth = res.getDimensionPixelSize(R.dimen.roll_num_item_width);
mRollHeight = res.getDimensionPixelSize(R.dimen.roll_num_item_Height);
setBackgroundResource(R.drawable.roll_number_bg);
mChildrenView = new RollNumberChildrenView(context);
addView(mChildrenView);
}
public void setNumber(int number) {
mChildrenView.setNumber(number);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mRollWidth, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mRollHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
setMeasuredDimension(mRollWidth, mRollHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View child = getChildAt(0);
if (child != null) {
child.layout(0, 0, mRollWidth, mRollHeight);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// 處理遮罩
canvas.save();
Drawable shadeDrawable = getResources().getDrawable(R.drawable.roll_number_shade);
shadeDrawable.setBounds(0, 0, getWidth(), getHeight());
shadeDrawable.draw(canvas);
canvas.restore();
}
// 此類真正是每一個數字view的parent,此類中onLayout和setnumber是重點,通過Scroller來控制滑動
private class RollNumberChildrenView extends ViewGroup {
// 前一個數字
private int mPreNum = 0;
// 當前數字
private int mCurNum = 0;
// 根據10個數字保存view
SparseArray<ImageView> mViews = new SparseArray<ImageView>();
protected Scroller mScroller;
public RollNumberChildrenView(Context context) {
this(context, null);
}
public RollNumberChildrenView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RollNumberChildrenView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFocusable(true);
final LayoutInflater inflater = LayoutInflater.from(context);
for (int i = 0; i < MAX_NUMBER_COUNT; i++) {
ImageView image = (ImageView) inflater.inflate(R.layout.roll_number_image, this, false);
int imageResId = getResources().getIdentifier("roll_number_" + i, "drawable",
getContext().getPackageName());
if (imageResId > 0) {
image.setImageResource(imageResId);
}
mViews.put(i, image);
addView(image);
}
// 滑動動畫,滑動逐漸遞減
mScroller = new Scroller(getContext(), new DecelerateInterpolator());
scrollTo(0, 0);
}
// 控制滾動到設置的數字,計算滾動距離,並且通過requestLayout重新調用onLayout,子view重新佈局
public void setNumber(int number) {
if (number >= 0 && number < MAX_NUMBER_COUNT) {
mPreNum = mCurNum;
mCurNum = number;
if (mCurNum == mPreNum) {
return;
} else {
// 當滑動爲結束時,終止滑動,並且滑動到指定位置
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
scrollTo(mScroller.getFinalX(), mScroller.getFinalY());
}
requestLayout();
// 計算滑動距離
int scrollY = getScrollY();
int dy;
if (mCurNum > mPreNum) {
dy = (mCurNum - mPreNum) * mRollHeight;
} else {
dy = (mCurNum + MAX_NUMBER_COUNT - mPreNum) * mRollHeight;
}
// 開始滑動,並且invalidate
mScroller.startScroll(0, scrollY, 0, dy, 500);
invalidate();
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mRollWidth, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(mRollHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
setMeasuredDimension(mRollWidth, mRollHeight);
}
// 通過mPreNum和scrollY萊佈局子view,以便滑動控制
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int scrollY = getScrollY();
int left = 0;
int right = 0;
int top = scrollY;
int bottom = 0;
for (int i = mPreNum; i < MAX_NUMBER_COUNT + mPreNum; i++) {
int num = i;
if (i >= MAX_NUMBER_COUNT) {
num = i - MAX_NUMBER_COUNT;
}
View child = mViews.get(num);
left = 0;
right = left + mRollWidth;
top = scrollY + (i - mPreNum) * mRollHeight;
bottom = top + mRollHeight;
child.layout(left, top, right, bottom);
}
}
// 計算是否滑動完畢,未完繼續invalidate
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
if (getScrollX() != mScroller.getCurrX() || getScrollY() != mScroller.getCurrY()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
}
invalidate();
} else if (mScroller.isFinished()) {
}
}
// 按鍵處理
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (!DEBUG) {
return super.onKeyUp(keyCode, event);
}
switch (keyCode) {
case KeyEvent.KEYCODE_0:
setNumber(0);
break;
case KeyEvent.KEYCODE_1:
setNumber(1);
break;
case KeyEvent.KEYCODE_2:
setNumber(2);
break;
case KeyEvent.KEYCODE_3:
setNumber(3);
break;
case KeyEvent.KEYCODE_4:
setNumber(4);
break;
case KeyEvent.KEYCODE_5:
setNumber(5);
break;
case KeyEvent.KEYCODE_6:
setNumber(6);
break;
case KeyEvent.KEYCODE_7:
setNumber(7);
break;
case KeyEvent.KEYCODE_8:
setNumber(8);
break;
case KeyEvent.KEYCODE_9:
setNumber(9);
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
}
static class ScrollInterpolator implements Interpolator {
public ScrollInterpolator() {
}
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1;
}
}
}
MainActivity.java中使用handler方式來操作數字滾動
package com.whuthm.rollnumber;
import java.text.DecimalFormat;
import java.util.Random;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
import android.app.Activity;
import com.whuthm.rollnumber.R;
public class MainActivity extends Activity {
private RollNumberView mHundredsDigit;
private RollNumberView mTensDigit;
private RollNumberView mOnesDigit;
private RollNumberView mFirstDecimalPlace;
private RollNumberView mSecondDecimalPlace;
TextView mUnit;
private Handler mHandler = new Handler();
private Runnable mTimerRunnable = new TimerRunnable();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHundredsDigit = (RollNumberView) findViewById(R.id.roll_number_hundreds_digit);
mTensDigit = (RollNumberView) findViewById(R.id.roll_number_tens_digit);
mOnesDigit = (RollNumberView) findViewById(R.id.roll_number_ones_digit);
mFirstDecimalPlace = (RollNumberView) findViewById(R.id.roll_number_first_decimal_place);
mSecondDecimalPlace = (RollNumberView) findViewById(R.id.roll_number_second_decimal_place);
mUnit = (TextView) findViewById(R.id.roll_number_unit);
}
private void setFrequency(int frequency) {
DecimalFormat fnum = new DecimalFormat("##000.00");
String result = fnum.format(((float) frequency) / 1000);
int length = result.length();
int secondDecimalPlace = Integer.parseInt(result.substring(length - 1, length));
int firstDecimalPlace = Integer.parseInt(result.substring(length - 2, length - 1));
int onesDigit = Integer.parseInt(result.substring(length - 4, length - 3));
int tensDigit = Integer.parseInt(result.substring(length - 5, length - 4));
int hundredsDigit = Integer.parseInt(result.substring(length - 6, length - 5));
mSecondDecimalPlace.setNumber(secondDecimalPlace);
mFirstDecimalPlace.setNumber(firstDecimalPlace);
mOnesDigit.setNumber(onesDigit);
mTensDigit.setNumber(tensDigit);
mHundredsDigit.setNumber(hundredsDigit);
}
@Override
protected void onResume() {
super.onResume();
mHandler.postDelayed(mTimerRunnable, 1000);
}
@Override
protected void onPause() {
super.onPause();
mHandler.removeCallbacks(mTimerRunnable);
}
class TimerRunnable implements Runnable {
static final int MAX_COUNT = 1000;
int index;
int frequency = 0;
TimerRunnable() {
}
@Override
public void run() {
if (index < MAX_COUNT) {
frequency += (int) (Math.random() * 900 + 100);
setFrequency(frequency);
}
index++;
mHandler.postDelayed(mTimerRunnable, 2000);
}
}
}