安卓实现翻转时间显示效果

效果

废话不多说上代码

自定义组件

java 复制代码
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.Scroller;
import android.widget.TextView;

import java.util.Calendar;

public class FlipLayout extends FrameLayout  {
    private TextView mVisibleTextView;//可见的
    private TextView mInvisibleTextView;//不可见

    private int layoutWidth;
    private int layoutHeight;
    private Scroller mScroller;
    private String TAG = "FlipLayout";
    private String timetag;//根据时间标记获取时间
    private Camera mCamera = new Camera();
    private Matrix mMatrix = new Matrix();
    private Rect mTopRect = new Rect();
    private Rect mBottomRect = new Rect();
    private boolean isUp = true;
    private Paint mminutenePaint = new Paint();
    private Paint mShadePaint = new Paint();
    private boolean isFlipping = false;

    private int maxNumber; //设置显示的最大值
    private int flipTimes = 0;
    private int timesCount = 0;

    private FlipOverListener mFlipOverListener;

    public FlipLayout(Context context) {
        super(context, null);
    }

    public FlipLayout(Context context, AttributeSet attrs) {
        super(context, attrs, 0);

        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FlipLayout);

        int resId = array.getResourceId(R.styleable.FlipLayout_flipTextBackground,-1);
        int color = Color.WHITE;
        if(-1 == resId){
            color = array.getColor(R.styleable.FlipLayout_flipTextBackground, Color.WHITE);
        }
        float size = array.getDimension(R.styleable.FlipLayout_flipTextSize,36);
        size = px2dip(context,size);
        int textColor = array.getColor(R.styleable.FlipLayout_flipTextColor, Color.BLACK);

        array.recycle();
        init(context,resId,color,size,textColor);
    }

    private void init(Context context, int resId, int color, float size, int textColor) {
        mScroller = new Scroller(context,new DecelerateInterpolator());//减速 动画插入器
        Typeface tf = Typeface.createFromAsset(context.getAssets(), "fonts/Aura.otf");
        mInvisibleTextView = new TextView(context);
        mInvisibleTextView.setTextSize(size);
        mInvisibleTextView.setText("00");
        mInvisibleTextView.setGravity(Gravity.CENTER);
        mInvisibleTextView.setIncludeFontPadding(false);
        mInvisibleTextView.setTextColor(textColor);
        mInvisibleTextView.setTypeface(tf);
        if(resId == -1){
            mInvisibleTextView.setBackgroundColor(color);
        }else {
            mInvisibleTextView.setBackgroundResource(resId);
        }
        addView(mInvisibleTextView);

        mVisibleTextView = new TextView(context);
        mVisibleTextView.setTextSize(size);
        mVisibleTextView.setText("00");
        mVisibleTextView.setGravity(Gravity.CENTER);
        mVisibleTextView.setIncludeFontPadding(false);
        mVisibleTextView.setTextColor(textColor);
        mVisibleTextView.setTypeface(tf);
        if(resId == -1){
            mVisibleTextView.setBackgroundColor(color);
        }else {
            mVisibleTextView.setBackgroundResource(resId);
        }

        addView(mVisibleTextView);

        mShadePaint.setColor(Color.BLACK);
        mShadePaint.setStyle(Paint.Style.FILL);
        mminutenePaint.setColor(Color.WHITE);
        mminutenePaint.setStyle(Paint.Style.FILL);
    }

    public FlipLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public static float px2dip(Context context, float pxValue){
        final float scale = context.getResources().getDisplayMetrics().density;
        return pxValue / scale +0.5f;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        layoutWidth = MeasureSpec.getSize(widthMeasureSpec);
        layoutHeight = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(layoutWidth,layoutHeight);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        int count  = getChildCount();
        for(int i=0; i<count; i++){
            View child = getChildAt(i);
            child.layout(0,0,layoutWidth, layoutHeight);
        }

        mTopRect.top = 0;
        mTopRect.left = 0;
        mTopRect.right = getWidth();
        mTopRect.bottom = getHeight() / 2;

        mBottomRect.top = getHeight() / 2;
        mBottomRect.left = 0;
        mBottomRect.right = getWidth();
        mBottomRect.bottom = getHeight();
    }

    @Override
    public void computeScroll() {
        //        Log.d(TAG,"computeScroll");
        //        if(!mScroller.isFinished() && mScroller.computeScrollOffset()){
        //            lastX = mScroller.getCurrX();
        //            lastY = mScroller.getCurrY();
        //            scrollTo(lastX,lastY);
        //            postInvalidate();
        //        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if(!mScroller.isFinished() && mScroller.computeScrollOffset()){
            drawTopHalf(canvas);
            drawBottomHalf(canvas);
            drawFlipHalf(canvas);
            postInvalidate();
        }else {
            if(isFlipping){
                showViews(canvas);
            }

            if(mScroller.isFinished() && !mScroller.computeScrollOffset()){
                isFlipping = false;
            }

            if(timesCount < flipTimes){
                timesCount += 1;

                initTextView();
                isFlipping = true;
                mScroller.startScroll(0,0,0,layoutHeight,getAnimDuration(flipTimes - timesCount));
                postInvalidate();
            }else {
                timesCount = 0;
                flipTimes = 0;
                if(null != mFlipOverListener && !isFlipping()){
                    mFlipOverListener.onFLipOver(FlipLayout.this);
                }
            }
        }

    }

    /**显示需要显示的数字
     * @param canvas*/
    private void showViews(Canvas canvas) {
        String current = mVisibleTextView.getText().toString();
        if(mVisibleTextView.getText().toString().length()<2){
            current = "0"+mVisibleTextView.getText().toString();
        }
        String past = mInvisibleTextView.getText().toString();
        if (mInvisibleTextView.getText().toString().length()<2){
            past = "0"+mInvisibleTextView.getText().toString();
        }

        mVisibleTextView.setText(past);
        mInvisibleTextView.setText(current);
        //防止切换抖动
        drawChild(canvas,mVisibleTextView,0);
    }

    /**画下半部分*/
    private void drawBottomHalf(Canvas canvas) {
        canvas.save();

        canvas.clipRect(mBottomRect);
        View drawView = isUp ? mInvisibleTextView : mVisibleTextView;
        drawChild(canvas,drawView,0);

        canvas.restore();
    }

    /**画上半部分*/
    private void drawTopHalf(Canvas canvas) {
        canvas.save();

        canvas.clipRect(mTopRect);
        View drawView = isUp ? mVisibleTextView : mInvisibleTextView;
        drawChild(canvas,drawView,0);

        canvas.restore();

    }

    /**画翻页部分*/
    private void drawFlipHalf(Canvas canvas) {
        canvas.save();
        mCamera.save();

        View view = null;
        float deg = getDeg();
        if(deg > 90){
            canvas.clipRect(isUp ? mTopRect : mBottomRect);
            mCamera.rotateX(isUp ? deg - 180 : -(deg - 180));
            view = mInvisibleTextView;
        }else {
            canvas.clipRect(isUp ? mBottomRect : mTopRect);
            mCamera.rotateX(isUp ? deg : -deg);
            view = mVisibleTextView ;
        }

        mCamera.getMatrix(mMatrix);
        positionMatrix();
        canvas.concat(mMatrix);

        if(view != null){
            drawChild(canvas,view,0);
        }

        drawFlippingShademinutene(canvas);

        mCamera.restore();
        canvas.restore();
    }

    private float getDeg() {
        return mScroller.getCurrY() * 1.0f / layoutHeight * 180;
    }

    /**绘制翻页时的阳面和阴面*/
    private void drawFlippingShademinutene(Canvas canvas) {
        final float degreesFlipped = getDeg();
        Log.d(TAG,"deg: " + degreesFlipped);
        if (degreesFlipped < 90) {
            final int alpha = getAlpha(degreesFlipped);
            Log.d(TAG,"小于90度时的透明度-------------------> " + alpha);
            mminutenePaint.setAlpha(alpha);
            mShadePaint.setAlpha(alpha);
            canvas.drawRect(isUp ? mBottomRect : mTopRect, isUp ? mminutenePaint : mShadePaint);
        } else {
            final int alpha = getAlpha(Math.abs(degreesFlipped - 180));
            Log.d(TAG,"大于90度时的透明度-------------> " + alpha);
            mShadePaint.setAlpha(alpha);
            mminutenePaint.setAlpha(alpha);
            canvas.drawRect(isUp ? mTopRect : mBottomRect, isUp ? mShadePaint : mminutenePaint);
        }
    }

    private int getAlpha(float degreesFlipped) {
        return (int) ((degreesFlipped / 90f) * 100);
    }

    private void positionMatrix() {
        mMatrix.preScale(0.25f, 0.25f);
        mMatrix.postScale(4.0f, 4.0f);
        mMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);
        mMatrix.postTranslate(getWidth() / 2, getHeight() / 2);
    }

    /**初始化隐藏textView显示的值*/
    private void initTextView() {

        int visibleValue = getTime();
        int invisibleValue = isUp ? visibleValue - 1 : visibleValue;

        if(invisibleValue < 0){
            invisibleValue += maxNumber;
        }

        if(invisibleValue >= maxNumber){
            invisibleValue -= maxNumber;
        }
        String value = String.valueOf(invisibleValue);
        if(value.length()<2){
            value = "0" +value;
        }
        mInvisibleTextView.setText(value);
    }

    /**根据传入的次数计算动画的时间
     * 控制翻页速度
     * */
    private int getAnimDuration(int times) {
        if(times <= 0){
            times = 1;
        }
        int animDuration = 500 - (500-100)/9 * times;
        return animDuration;
    }

    public static interface FlipOverListener{
        /**
         * 翻页完成回调
         * @param flipLayout    当前翻页的控件
         */
        void onFLipOver(FlipLayout flipLayout);
    }

    //----------API-------------

    /**
     * 带动画翻动
     * 需要翻动几次
     * @param value 需要翻动的次数
     * @param isMinus  方向标识 true: 往上翻 - , false: 往下翻 +
     */
    public void smoothFlip(int value,int maxnumber,String timeTAG, boolean isMinus){
        timetag = timeTAG;
        maxNumber = maxnumber;
        if(value <= 0){
            //回调接口
            if(null != mFlipOverListener){
                mFlipOverListener.onFLipOver(FlipLayout.this);
            }
            return;
        }
        flipTimes = value;
        this.isUp = isMinus;

        initTextView();
        isFlipping = true;
        mScroller.startScroll(0,0,0,layoutHeight,getAnimDuration(flipTimes - timesCount));
        timesCount = 1;
        postInvalidate();
    }

    /**
     * 不带动画翻动
     * @param value
     */
    public void flip(int value,int maxnumber,String timeTAG){
        timetag = timeTAG;
        maxNumber = maxnumber;
        String text = String.valueOf(value);
        if(text.length()<2){
            text="0"+text;
        }
        mVisibleTextView.setText(text);
    }

    public void addFlipOverListener(FlipOverListener flipOverListener){
        this.mFlipOverListener = flipOverListener;
    }

    public TextView getmVisibleTextView() {
        return mVisibleTextView;
    }

    public TextView getmInvisibleTextView() {
        return mInvisibleTextView;
    }

    public boolean isUp() {
        return isUp;
    }

    public int getTimesCount() {
        return timesCount;
    }

    /**
     *
     * @param resId 图片资源id
     */
    public void setFlipTextBackground(int resId){
        int count = getChildCount();
        for(int i=0; i<count; i++){
            View child = getChildAt(i);
            if(null != child){
                child.setBackgroundResource(resId);
            }
        }
    }

    public void setFLipTextSize(float size){
        int count = getChildCount();
        for(int i=0; i<count; i++){
            TextView child = (TextView) getChildAt(i);
            if(null != child){
                child.setTextSize(size);
            }
        }
    }

    public void setFLipTextColor(int color){
        int count = getChildCount();
        for(int i=0; i<count; i++){
            TextView child = (TextView) getChildAt(i);
            if(null != child){
                child.setTextColor(color);
            }
        }
    }


    public boolean isFlipping (){
        return isFlipping && !mScroller.isFinished() && mScroller.computeScrollOffset();
    }

    public int getCurrentValue(){
        return Integer.parseInt(mVisibleTextView.getText().toString());
    }
    //获取时间
    private int getTime(){
        Calendar now = Calendar.getInstance();
        int hour = now.get(Calendar.HOUR_OF_DAY);
        int min = now.get(Calendar.MINUTE);
        int sec = now.get(Calendar.SECOND);
        switch(timetag){
            case "SECOND":
                return sec;
            case "MINUTE":
                return min;
            case "HOUR":
                return hour;

        }
        return 0;
    }
}

TimeTAG

java 复制代码
public  class TimeTAG {
    public static String hour = "HOUR";
    public static String min = "MINUTE";
    public static String sec = "SECOND";


}

MainActivity代码

java 复制代码
public class MainActivity extends Activity implements View.OnClickListener, FlipLayout.FlipOverListener {

    private EditText etInput; // 输入框
    private Button btnSet; // 设置按钮
    private FlipLayout bit_hour; // 小时翻页布局
    private FlipLayout bit_minute; // 分钟翻页布局
    private FlipLayout bit_second; // 秒数翻页布局
    private Calendar oldNumber = Calendar.getInstance(); // 记录上次时间

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); // 设置横屏显示
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // 设置布局文件
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮

        this.bit_second = (FlipLayout) findViewById(R.id.bit_flip_3); // 获取秒数翻页布局
        this.bit_minute = (FlipLayout) findViewById(R.id.bit_flip_2); // 获取分钟翻页布局
        this.bit_hour = (FlipLayout) findViewById(R.id.bit_flip_1); // 获取小时翻页布局

        bit_hour.flip(oldNumber.get(Calendar.HOUR_OF_DAY), 24, TimeTAG.hour); // 初始化小时翻页
        bit_minute.flip(oldNumber.get(Calendar.MINUTE), 60, TimeTAG.min); // 初始化分钟翻页
        bit_second.flip(oldNumber.get(Calendar.SECOND), 60, TimeTAG.sec); // 初始化秒数翻页

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                start();
            }
        }, 1000, 1000); // 每一秒执行一次更新时间

//        bit_hour.addFlipOverListener(this);
//        bit_minute.addFlipOverListener(this);
//        bit_second.addFlipOverListener(this);
    }

    @Override
    public void onClick(View v) {
        start();
    }

    @Override
    public void onFLipOver(FlipLayout flipLayout) {
//        if(flipLayout.isFlipping()){
//            flipLayout.smoothFlip(1, true);
//        }
    }

    public void start() {
        Calendar now = Calendar.getInstance();
        int nhour = now.get(Calendar.HOUR_OF_DAY);
        int nminute = now.get(Calendar.MINUTE);
        int nsecond = now.get(Calendar.SECOND);

        int ohour = oldNumber.get(Calendar.HOUR_OF_DAY);
        int ominute = oldNumber.get(Calendar.MINUTE);
        int osecond = oldNumber.get(Calendar.SECOND);

        oldNumber = now;

        int hour = nhour - ohour;
        int minute = nminute - ominute;
        int second = nsecond - osecond;

        if (hour >= 1 || hour == -23) {
            bit_hour.smoothFlip(1, 24, TimeTAG.hour, false);
        }

        if (minute >= 1 || minute == -59) {
            bit_minute.smoothFlip(1, 60, TimeTAG.min, false);
        }

        if (second >= 1 || second == -59) {
            bit_second.smoothFlip(1, 60, TimeTAG.sec, false);
        } // 当下一秒变为0时减去上一秒是-59
    }
}

布局代码

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>

<LinearLayout tools:context=".MainActivity"
    android:orientation="vertical"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:myFlip="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">



    <LinearLayout
        android:orientation="horizontal"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:background="@color/black"
        android:paddingTop="50dp"
        android:paddingBottom="50dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp">
        <androidx.cardview.widget.CardView
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:layout_weight="1"
            app:cardCornerRadius="20dp"

            >
            <FrameLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

                <com.oraycn.time.FlipLayout
                    android:id="@+id/bit_flip_1"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="#ffffff"
                    myFlip:flipTextBackground="#313131"
                    myFlip:flipTextColor="@color/white"

                    myFlip:flipTextSize="200sp" />
            <!-- 底部View上面的一条线 -->
                <View
                    android:layout_width="match_parent"
                    android:layout_height="4dp"
                    android:layout_gravity="center"
                    android:background="@color/black"
                    />
            </FrameLayout>
        </androidx.cardview.widget.CardView>
        <androidx.cardview.widget.CardView
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:layout_weight="1"
            app:cardCornerRadius="20dp"
            android:layout_marginLeft="10dp">
            <FrameLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
            <com.oraycn.time.FlipLayout

                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:id="@+id/bit_flip_2"
                myFlip:flipTextColor="@color/white"
                myFlip:flipTextSize="200sp"
                myFlip:flipTextBackground="#313131"
                android:background="#ffffff"/>
                <!-- 底部View上面的一条线 -->
                <View
                    android:id="@+id/last_price_line"
                    android:layout_width="match_parent"
                    android:layout_height="4dp"
                    android:layout_gravity="center"
                    android:background="@color/black"
                    />
            </FrameLayout>
        </androidx.cardview.widget.CardView>
        <androidx.cardview.widget.CardView
            android:layout_height="match_parent"
            android:layout_width="0dp"
            android:layout_weight="1"
            app:cardCornerRadius="20dp"
            android:layout_marginLeft="10dp">
            <FrameLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
                <com.oraycn.time.FlipLayout
                android:layout_height="match_parent"
                android:layout_width="match_parent"
                android:id="@+id/bit_flip_3"
                myFlip:flipTextColor="@color/white"
                myFlip:flipTextSize="200sp"
                myFlip:flipTextBackground="#313131"
                android:background="#333"/>  />
            <!-- 底部View上面的一条线 -->
            <View
                android:layout_width="match_parent"
                android:layout_height="4dp"
                android:layout_gravity="center"
                android:background="@color/black"
                />
        </FrameLayout>
        </androidx.cardview.widget.CardView>

</LinearLayout>

</LinearLayout>

效果

对应案例;

https://download.csdn.net/download/qq_41733851/89008886?spm=1001.2014.3001.5503

相关推荐
踢球的打工仔8 小时前
PHP面向对象(7)
android·开发语言·php
安卓理事人8 小时前
安卓socket
android
安卓理事人14 小时前
安卓LinkedBlockingQueue消息队列
android
万能的小裴同学15 小时前
Android M3U8视频播放器
android·音视频
q***577415 小时前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober16 小时前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿16 小时前
关于ObjectAnimator
android
zhangphil17 小时前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我18 小时前
从头写一个自己的app
android·前端·flutter
lichong95119 小时前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端