效果
废话不多说上代码
自定义组件
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