類似NumberPicker的效果實現

最近在做電視項目,關於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);
        }

    }

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章