效果
废话不多说上代码
自定义组件
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;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);layoutWidth = MeasureSpec.getSize(widthMeasureSpec);layoutHeight = MeasureSpec.getSize(heightMeasureSpec);setMeasuredDimension(layoutWidth,layoutHeight);}@Overrideprotected 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();}@Overridepublic void computeScroll() {// Log.d(TAG,"computeScroll");// if(!mScroller.isFinished() && mScroller.computeScrollOffset()){// lastX = mScroller.getCurrX();// lastY = mScroller.getCurrY();// scrollTo(lastX,lastY);// postInvalidate();// }}@Overrideprotected 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
public class TimeTAG {public static String hour = "HOUR";public static String min = "MINUTE";public static String sec = "SECOND";}
MainActivity代码
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(); // 记录上次时间@Overrideprotected 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() {@Overridepublic void run() {start();}}, 1000, 1000); // 每一秒执行一次更新时间// bit_hour.addFlipOverListener(this);
// bit_minute.addFlipOverListener(this);
// bit_second.addFlipOverListener(this);}@Overridepublic void onClick(View v) {start();}@Overridepublic 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 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"><LinearLayoutandroid: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.CardViewandroid:layout_height="match_parent"android:layout_width="0dp"android:layout_weight="1"app:cardCornerRadius="20dp"><FrameLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"><com.oraycn.time.FlipLayoutandroid: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上面的一条线 --><Viewandroid:layout_width="match_parent"android:layout_height="4dp"android:layout_gravity="center"android:background="@color/black"/></FrameLayout></androidx.cardview.widget.CardView><androidx.cardview.widget.CardViewandroid:layout_height="match_parent"android:layout_width="0dp"android:layout_weight="1"app:cardCornerRadius="20dp"android:layout_marginLeft="10dp"><FrameLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"><com.oraycn.time.FlipLayoutandroid: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上面的一条线 --><Viewandroid: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.CardViewandroid:layout_height="match_parent"android:layout_width="0dp"android:layout_weight="1"app:cardCornerRadius="20dp"android:layout_marginLeft="10dp"><FrameLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"><com.oraycn.time.FlipLayoutandroid: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上面的一条线 --><Viewandroid: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