2016-05-02 05:37:25 Sponge_CMZ 阅读数 870

说明:

拆析系列通过分析第三方的Demo, 本文重新整理Demo中涉及到的重点API并以知识点的方式进行分段, 以方便大家学习.

文章中尽量不使用或少使用封装, 目的是让大家清楚为了实现功能所需要的官方核心API是哪些(如果使用封装, 会在封装外面加以注释)

本拆析Demo源码地址: https://github.com/Neojoke/flippingPage
感谢Demo作者Neojoke

  • 此文章由 @Scott 编写. 经 @春雨 审核. 若转载此文章,请注明出处和作者

<功能描述>

iOS实现3D翻页效果

核心API

CALayer的anchorPoint属性, CGImage, CAShapeLayer, UIRectConner, CIFilter, CAGradientLayer, CATransform3D

功能实现

Code:

1. CALayer属性 anchorPoint(锚点)

    /**
     * 每一个UIView内部都默认关联着一个CALayer, UIView有frame、bounds和center三个属性,CALayer也有类似的属性,分别为frame、bounds、position、anchorPoint。frame和bounds比较好理解,bounds可以视为x坐标和y坐标都为0的frame,那position、anchorPoint是什么呢?先看看两者的原型,可知都是CGPoint点。

     * @property CGPoint position
     * @property CGPoint anchorPoint
     *
     * anchorPoint 介绍:
     * 从一个例子开始入手吧,想象一下,把一张A4白纸用图钉订在书桌上,如果订得不是很紧的话,白纸就可以沿顺时针或逆时针方向围绕图钉旋转,这时候图钉就起着支点的作用。我们要解释的anchorPoint就相当于白纸上的图钉,它主要的作用就是用来作为变换的支点,旋转就是一种变换,类似的还有平移、缩放。

       继续扩展,很明显,白纸的旋转形态随图钉的位置不同而不同,图钉订在白纸的正中间与左上角时分别造就了两种旋转形态,这是由图钉(anchorPoint)的位置决定的。如何衡量图钉(anchorPoint)在白纸中的位置呢?在iOS中,anchorPoint点的值是用一种相对bounds的比例值来确定的,在白纸的左上角、右下角,anchorPoint分为为(0,0), (1, 1),也就是说anchorPoint是在单元坐标空间(同时也是左手坐标系)中定义的。类似地,可以得出在白纸的中心点、左下角和右上角的anchorPoint为(0.5,0.5), (0,1), (1,0)。
     */

    // 设置右边ImageView的锚点
    self.imageViewForRight.layer.anchorPoint = CGPointMake(0, 0.5);

2: CGImage

- (UIImage *)clipImageWithImage:(UIImage *)image isLeftImage:(BOOL)isLeft {

    CGRect imgRect = CGRectMake(0, 0, image.size.width / 2, image.size.height);

    if (!isLeft) {
        imgRect.origin.x = image.size.width / 2;
    }

    CGImageRef imgRef = CGImageCreateWithImageInRect(image.CGImage, imgRect);

    UIImage *clipImage = [UIImage imageWithCGImage:imgRef];

    return clipImage;
}

3: CAShapeLayer, UIRectConner

- (CAShapeLayer *)getCornerRidusMashWithIsLeft:(BOOL)isLeft rect:(CGRect)rect {

    //  创建CAShapeLayer对象.
    CAShapeLayer *layer = [CAShapeLayer layer];

    // 创建矩形圆角结构体
    UIRectCorner corner = isLeft ? UIRectCornerTopLeft | UIRectCornerBottomLeft : UIRectCornerTopRight | UIRectCornerBottomRight;

    // 通过贝塞尔曲线创建矩形圆角.
    layer.path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corner cornerRadii:CGSizeMake(10, 10)].CGPath;

    return  layer;
}

4: CIFilter (iOS 滤镜).

- (UIImage *)getBlurAndReversalImage:(UIImage *)image {

    CIContext *context = [CIContext contextWithOptions:nil];
    CIImage *inputImage = [CIImage imageWithCGImage:image.CGImage];

    // 高斯滤镜.
    CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];

    [filter setValue:inputImage forKey:kCIInputImageKey];
    [filter setValue:@10.0 forKey:@"inputRadius"];

    CIImage *result = [filter valueForKey:kCIOutputImageKey];
    result = [result imageByApplyingTransform:CGAffineTransformMakeTranslation(-1, 1)];

    CGImageRef ref = [context createCGImage:result fromRect:[inputImage extent]];

    UIImage *returnImage = [UIImage imageWithCGImage:ref];

    CGImageRelease(ref);

    return returnImage;


}

5: 渐变图层(CAGradientLayer)

- (void)gradientLayer {

    // 渐变颜色
    self.gradientForLeft = [CAGradientLayer layer];
    self.gradientForLeft.opacity = 0; /**< 不透明度. */
    self.gradientForLeft.colors = @[(id)[UIColor clearColor].CGColor, (id)[UIColor blackColor].CGColor];
    self.gradientForLeft.frame = self.imageViewForLeft.bounds;
    self.gradientForLeft.startPoint = CGPointMake(1, 1);
    self.gradientForLeft.startPoint = CGPointMake(0, 1);
    [self.imageViewForLeft.layer addSublayer:self.gradientForLeft];


    self.gradientForRight = [CAGradientLayer layer];
    self.gradientForRight.opacity = 0;
    self.gradientForRight.colors = @[(id)[UIColor clearColor].CGColor, (id)[UIColor blackColor].CGColor];
    self.gradientForRight.frame = self.imageViewForRight.bounds;
    self.gradientForRight.startPoint = CGPointMake(0, 1);
    self.gradientForRight.startPoint = CGPointMake(1, 1);
    [self.imageViewForRight.layer addSublayer:self.gradientForRight];

}

6: CATransform3D

- (CATransform3D)getTransForm3DWithAngle:(CGFloat)angle {

    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = 4.5 / 2000;
    transform = CATransform3DRotate(transform, angle, 0, 1, 0);
    return transform;
}

完整Demo链接:https://github.com/yuancaoscott/CATransform3D

2011-04-20 22:21:36 Hobo86 阅读数 72

大致思路使用两层辅助UIView的旋转来实现添加后的View的横向翻页效果

    CATransform3D transformA = CATransform3DRotate(CATransform3DIdentity, degreesToRadian(90), 0, 0, 1.0f);
    CATransform3D transformB = CATransform3DRotate(CATransform3DIdentity, degreesToRadian(180), 0.0f, 1.0f, 0.0f);
    bgview.layer.transform = CATransform3DConcat(transformA, transformB);
   
    CATransform3D transform3DA = CATransform3DRotate(CATransform3DIdentity, degreesToRadian(90), 0, 0, 1.0f);
    CATransform3D transform3DB = CATransform3DRotate(CATransform3DIdentity, degreesToRadian(180), 0.0f, 1.0f, 0.0f);
   
    superView.layer.transform = CATransform3DConcat(transform3DA, transform3DB);

 

View的层次:superView──bgView──自己的View

 

    向bgView中添加自己的View(注:要在bgView的subViews多于一个时才有翻页效果)

    [UIView beginAnimations:@"view transition" context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:bgView cache:NO];

    [bgView addSubview:viewController.view];

 

 

这样做一是实现了横向翻页,二是保证你自己的View添加之后的方向是正确的,试试就知道为什么非要弄两层来辅助了,至于旋转后的Frame变化问题就看自己的使用情况调整了,尤其注意顶层View(添加进去的View)的Touch事件可能无法识别到,是因为底层View(bg和super View)旋转后的Frame出了问题!!!

不知道有没有其他的好方法,如果各位有更好的方法还望赐教!

 

 

2017-04-22 22:13:42 android_cmos 阅读数 3130

这是一个自定义的倒计时控件,具有3D上下翻页翻转效果。最近项目中需要做一个倒计时控件,需要和iOS端的效果保持一样。大致效果是这样的,如下图所示:


由于暂时还不会怎么样制作gif动态图,所以想看具体效果的,可以在下面的源码中下载运行查看。

废话不说了,开干吧。那么是怎么实现呢?我们首先得找到3D翻页的效果,这个效果我是参考的一个github项目,https://github.com/emilsjolander/android-FlipView。

其次,倒计时的逻辑,这个不难,最后是自定义的View了。

下面是动画的效果


1.动画的效果

(1).这里的动画效果是由Scroller类来实现的。

public void startScroll (int startX, int startY, int dx, int dy, int duration)
(2).翻页效果
           主要分为三部分

           |___绘制控件翻转页面的上半部分:

 canvas.clipRect(mTopRect);

           |___绘制控件翻转页面的下半部分: 

canvas.clipRect(mBottomRect);

           |___绘制控件中间的翻页部分:这里利用的是camera类的方法,

camera.rotateX()//看意思就明白表示沿X轴翻转,这是翻转效果的核心方法。
camera.clipRect(mTopRect,mBottom) //根据当前翻转的度数(0~180度),决定画布裁剪部分。
camera.getMatrix(mMatrix) //设置matrix矩阵的值,对中间页进行变形,达到翻页的视觉效果。
//然后通过矩阵变换,绘制视图和控件
mMatrix.preScale(0.25f, 0.25f);
mMatrix.postScale(4.0f, 4.0f);
mMatrix.preTranslate(-getWidth() / 2, -getHeight() / 2);
mMatrix.postTranslate(getWidth() / 2, getHeight() / 2);
canvas.concat(mMatrix) 
drawChild();
其中,控件中间的黑线,也是自定义的一个TextView,因为原生的TextView没有设置文字上画黑线的方法,有画线的方法,但是是跟着字体的颜色变化的,所以不符合需求,需要自定义一下,设置绘画的颜色。

不说了,上代码

clock_view_activity.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:customs="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#3B96FD"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <com.example.timeticker.MyClockView
        android:id="@+id/clockView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="14dp"
        android:background="@mipmap/tcd_timer_bg"
        customs:dayTextBackground="@drawable/time_bg"
        customs:dayTextColor="#ffffff"
        customs:hourTextBackground="@drawable/time_bg"
        customs:hourTextColor="#ffffff"
        customs:minTextBackground="@drawable/time_bg"
        customs:minTextColor="#ffffff"
        customs:secTextBackground="@drawable/time_bg"
        customs:secTextColor="#ffffff" >
    </com.example.timeticker.MyClockView>

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="倒计时" />

</LinearLayout>
attrs.xml 自定义的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyClock">
        <attr name="dayTextSize" format="dimension" />
        <attr name="hourTextSize" format="dimension" />
        <attr name="minTextSize" format="dimension" />
        <attr name="secTextSize" format="dimension" />
        <attr name="dayTextColor" format="color" />
        <attr name="hourTextColor" format="color" />
        <attr name="minTextColor" format="color" />
        <attr name="secTextColor" format="color" />
        <attr name="dayTextBackground" format="reference|color" />
        <attr name="hourTextBackground" format="reference|color" />
        <attr name="minTextBackground" format="reference|color" />
        <attr name="secTextBackground" format="reference|color" />
    </declare-styleable>
</resources>
ids.xml 因为用到的是动态布局,为了能找到控件,得为每个控件设置一个id

<?xml version="1.0" encoding="utf-8"?>
<resources>
     <item name="dayTextView" type="id" />
     <item name="hourTextView" type="id" />
     <item name="minTextView" type="id" />
     <item name="secTextView" type="id" />
</resources>
time_bg.xml 控件的样式

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <corners android:radius="3dp" />

    <!-- 实心 即填充颜色 -->
    <solid android:color="#0B315C" />
    <!-- 按钮文字和边缘距离(内边距) -->
    <padding
        android:bottom="3dp"
        android:left="6dp"
        android:right="6dp"
        android:top="3dp" />

</shape>
clock_view_bg.xml 控件外框的样式
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <corners android:radius="10dp" />

    <!-- 实心 即填充颜色 -->
    <solid android:color="#00000000" />

    <stroke
        android:width="2dp"
        android:color="#2F80E9" />
    <!-- 按钮文字和边缘距离(内边距) -->
    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />

</shape>
FlipClockView.java
package com.example.timeticker;

import android.content.Context;
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.drawable.Drawable;
import android.util.AttributeSet;
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;

/**
 日历3D翻转效果
 */
public class FlipClockView extends FrameLayout {

	private TextView mVisibleTextView;// 可见的
	private TextView mInvisibleTextView;// 不可见

	private int layoutWidth;
	private int layoutHeight;
	private Scroller mScroller;

	private Camera mCamera = new Camera();
	private Matrix mMatrix = new Matrix();
	private Rect mTopRect = new Rect();
	private Rect mBottomRect = new Rect();
	private boolean isUp2Down = true;
	private Paint mShinePaint = new Paint();
	private Paint mShadePaint = new Paint();
	private boolean isFlipping = false;

	public FlipClockView(Context context) {
		this(context, null);
	}

	public FlipClockView(Context context, AttributeSet attrs) {
		super(context, attrs, 0);
		init(context);
	}

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

	public void init(Context context) {
		mScroller = new Scroller(context, new DecelerateInterpolator());// 减速 // 动画插入器

		mInvisibleTextView = new MyTextView(context);
		mInvisibleTextView.setText("0");
		mInvisibleTextView.setGravity(Gravity.CENTER);
		mInvisibleTextView.setIncludeFontPadding(false);
		addView(mInvisibleTextView);

		mVisibleTextView = new MyTextView(context);
		mVisibleTextView.setText("0");
		mVisibleTextView.setGravity(Gravity.CENTER);
		mVisibleTextView.setIncludeFontPadding(false);
		addView(mVisibleTextView);

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

	@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();
		//将两个textView放置进去
		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
	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;
			}
		}
	}

	/**
	 * 显示需要显示的数字
	 * 
	 * @param canvas
	 */
	private void showViews(Canvas canvas) {
		String current = mVisibleTextView.getText().toString();
		String past = mInvisibleTextView.getText().toString();
		// Log.e("需要显示的数字--->",current+ "%%    "+past);

		mVisibleTextView.setText(past);
		mInvisibleTextView.setText(current);

		// 防止切换抖动
		drawChild(canvas, mVisibleTextView, 0);
	}

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

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

		canvas.restore();
	}

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

		canvas.clipRect(mTopRect);
		View drawView = !isUp2Down ? 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(!isUp2Down ? mTopRect : mBottomRect);
			mCamera.rotateX(!isUp2Down ? deg - 180 : -(deg - 180));
			view = mInvisibleTextView;
		} else {
			canvas.clipRect(!isUp2Down ? mBottomRect : mTopRect);
			mCamera.rotateX(!isUp2Down ? deg : -deg);
			view = mVisibleTextView;
		}

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

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

		drawFlippingShadeShine(canvas);

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

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

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

	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 = Integer.parseInt(mVisibleTextView.getText()
				.toString());
	//	int invisibleValue = isUp2Down ? visibleValue - 1 : visibleValue + 1;//这里控制是 + 还是 -
		int invisibleValue = visibleValue - 1;
		if (invisibleValue < 10) {
			mInvisibleTextView.setText("0" + invisibleValue);
		} else {
			mInvisibleTextView.setText("" + invisibleValue);
		}
	}

	/**
	 *
	 * @param isUp2Down
	 *     方向标识 true: 从上往下翻  , false: 从下往上翻
	 */
	public void setFlipDirection(boolean isUp2Down) {
	    this.isUp2Down = isUp2Down;
	}
	
	public void smoothFlip() {
		//Log.e(TAG, "翻动 ");
		initTextView();
		isFlipping = true;

		mScroller.startScroll(0, 0, 0, layoutHeight, 700);
		postInvalidate();
	}

	public TextView getmVisibleTextView() {
		return mVisibleTextView;
	}

	public TextView getmInvisibleTextView() {
		return mInvisibleTextView;
	}

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

	/**
	 * 获取当前View值
	 * 
	 * @return
	 */
	public int getCurrentValue() {
		return Integer.parseInt(mVisibleTextView.getText().toString());
	}

	/**
	 * 设置view的时间值
	 * @param textTime
	 */
	public void setClockTime(String textTime) {
		mVisibleTextView.setText(textTime);
	}

	/**
	 * 设置时间数字的背景
	 * @param drawable
	 */
	public void setClockBackground(Drawable drawable) {
		mVisibleTextView.setBackground(drawable);
		mInvisibleTextView.setBackground(drawable);
	}

	/**
	 * 设置时间数字的颜色
	 * @param color
	 */
	public void setClockTextColor(int color) {
		mVisibleTextView.setTextColor(color);
		mInvisibleTextView.setTextColor(color);
	}

	/**
	 * 设置时间数字的大小
	 * @param size
	 */
	public void setClockTextSize(float size){
		mVisibleTextView.setTextSize(size);
		mInvisibleTextView.setTextSize(size);
	}

}

MyClockView.java

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MyClockView extends RelativeLayout {
	private float dayTextSize, hourTextSize, minTextSize, secTextSize;
	private FlipClockView dayTextView, hourTextView, minTextView, secTextView;
	private TextView dTextView, hTextView, mTextView, sTextView;
	private LayoutParams dayLayoutParams, hourLayoutParams, minLayoutParams,
			secLayoutParams;
	private DownCountTimerListener mDownCountTimerListener;
	private Handler mHandler;
	private Runnable mRunnable;
	private long totalTime = 0;
	private boolean isRunning = true;
    private int screenW;
	private long outNumber=0;//超过的最大计数的时间(秒数)

	public MyClockView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView(context, attrs);
	}

	public MyClockView(Context context) {
		this(context, null);
	}

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

	public void initView(Context context, AttributeSet attrs) {
		screenW=getScreenWidth(context);
		TypedArray tArray = context.obtainStyledAttributes(attrs,
				R.styleable.MyClock);
		dayTextSize = tArray.getDimension(R.styleable.MyClock_dayTextSize, 26f);
		hourTextSize = tArray.getDimension(R.styleable.MyClock_hourTextSize,
				26f);
		minTextSize = tArray.getDimension(R.styleable.MyClock_minTextSize, 26f);
		secTextSize = tArray.getDimension(R.styleable.MyClock_secTextSize, 26f);
		int dayTextColor = tArray.getColor(R.styleable.MyClock_dayTextColor,
				0xffffff);
		int hourTextColor = tArray.getColor(R.styleable.MyClock_hourTextColor,
				0xffffff);
		int minTextColor = tArray.getColor(R.styleable.MyClock_minTextColor,
				0xffffff);
		int secTextColor = tArray.getColor(R.styleable.MyClock_secTextColor,
				0xffffff);
		Drawable dayTextBg = tArray
				.getDrawable(R.styleable.MyClock_dayTextBackground);
		Drawable hourTextBg = tArray
				.getDrawable(R.styleable.MyClock_hourTextBackground);
		Drawable minTextBg = tArray
				.getDrawable(R.styleable.MyClock_minTextBackground);
		Drawable secTextBg = tArray
				.getDrawable(R.styleable.MyClock_secTextBackground);

		tArray.recycle();

		dayTextView = new FlipClockView(context);
		hourTextView = new FlipClockView(context);
		minTextView = new FlipClockView(context);
		secTextView = new FlipClockView(context);
		
		dayTextView.setId(R.id.dayTextView);
		hourTextView.setId(R.id.hourTextView);
		minTextView.setId(R.id.minTextView);
		secTextView.setId(R.id.secTextView);
		
		dTextView = new TextView(context);
		hTextView = new TextView(context);
		mTextView = new TextView(context);
		sTextView = new TextView(context);
		dTextView.setText("DAYS");
		hTextView.setText("HOURS");
		mTextView.setText("MINUTES");
		sTextView.setText("SECONDS");
		dTextView.setTextColor(Color.parseColor("#ffffff"));
		hTextView.setTextColor(Color.parseColor("#ffffff"));
		mTextView.setTextColor(Color.parseColor("#ffffff"));
		sTextView.setTextColor(Color.parseColor("#ffffff"));
		dTextView.setTextSize(10f);
		hTextView.setTextSize(10f);
		mTextView.setTextSize(10f);
		sTextView.setTextSize(10f);

		dayTextView.setClockBackground(dayTextBg);
		dayTextView.setClockTextSize(dayTextSize);
		dayTextView.setClockTextColor(dayTextColor);
		hourTextView.setClockBackground(hourTextBg);
		hourTextView.setClockTextSize(dayTextSize);
		hourTextView.setClockTextColor(hourTextColor);
		minTextView.setClockBackground(minTextBg);
		minTextView.setClockTextSize(dayTextSize);
		minTextView.setClockTextColor(minTextColor);
		secTextView.setClockBackground(secTextBg);
		secTextView.setClockTextSize(dayTextSize);
		secTextView.setClockTextColor(secTextColor);
		//secTextView.setFlipDirection(false);

		//Log.e("---->","屏幕的宽"+screenW);
        int viewWidth=(int)(screenW*0.14);
		int viewMargin=(int)(screenW*0.05);
		dTextView.setWidth(viewWidth);
		dTextView.setGravity(Gravity.CENTER);
		hTextView.setWidth(viewWidth);
		hTextView.setGravity(Gravity.CENTER);
		mTextView.setWidth(viewWidth);
		mTextView.setGravity(Gravity.CENTER);
		sTextView.setWidth(viewWidth);
		sTextView.setGravity(Gravity.CENTER);

		dayLayoutParams = new LayoutParams(viewWidth,
                viewWidth);
		dayLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, TRUE);
		dayLayoutParams.setMargins(0, 60, 0, 0);
		addView(dayTextView, dayLayoutParams);
		LayoutParams dLayoutParams = new LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		dLayoutParams.addRule(RelativeLayout.BELOW, R.id.dayTextView);
		dLayoutParams.addRule(RelativeLayout.ALIGN_LEFT, R.id.dayTextView);
		dLayoutParams.setMargins(0, 5, 0, 60);
		addView(dTextView, dLayoutParams);

		hourLayoutParams = new LayoutParams(viewWidth,
                viewWidth);
		hourLayoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.dayTextView);
		hourLayoutParams.setMargins(viewMargin, 60, viewMargin, 0);
		addView(hourTextView, hourLayoutParams);
		LayoutParams hLayoutParams = new LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		hLayoutParams.addRule(RelativeLayout.ALIGN_LEFT, R.id.hourTextView);
		hLayoutParams.addRule(RelativeLayout.BELOW, R.id.hourTextView);
		hLayoutParams.setMargins(0, 5, 0, 0);
		addView(hTextView, hLayoutParams);

		minLayoutParams = new LayoutParams(viewWidth,
				viewWidth);
		minLayoutParams.setMargins(0, 60, 0, 0);
		minLayoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.hourTextView);
		addView(minTextView, minLayoutParams);
		LayoutParams mLayoutParams = new LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		mLayoutParams.addRule(RelativeLayout.ALIGN_LEFT, R.id.minTextView);
		mLayoutParams.addRule(RelativeLayout.BELOW, R.id.minTextView);
		mLayoutParams.setMargins(0, 5, 0, 0);
		addView(mTextView, mLayoutParams);

		secLayoutParams = new LayoutParams(viewWidth,
                viewWidth);
		secLayoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.minTextView);
		secLayoutParams.setMargins(viewMargin, 60, 0, 0);
		addView(secTextView, secLayoutParams);
		LayoutParams sLayoutParams = new LayoutParams(
				ViewGroup.LayoutParams.WRAP_CONTENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		sLayoutParams.addRule(RelativeLayout.ALIGN_LEFT, R.id.secTextView);
		sLayoutParams.addRule(RelativeLayout.BELOW, R.id.secTextView);
		sLayoutParams.setMargins(0, 5, 0, 0);
		addView(sTextView, sLayoutParams);

		dayTextView.setClockTime("00");
		hourTextView.setClockTime("00");
		minTextView.setClockTime("00");
		secTextView.setClockTime("00");

		mHandler = new Handler();
	}

	public interface DownCountTimerListener {

		void stopDownCountTimer();

	}

	/**
	 * 暂停计时
	 */
	public void pauseDownCountTimer() {
		if (mRunnable != null) {
			mHandler.removeCallbacks(mRunnable);
			dayTextView.setClockTime("00");
			hourTextView.setClockTime("00");
			minTextView.setClockTime("00");
			secTextView.setClockTime("00");
		//	Log.e("暂停计时", "-=-=-=-=-=");
		}
	}

	public void setDownCountTimerListener(DownCountTimerListener listener) {
		this.mDownCountTimerListener = listener;
	}

	/**
	 * 获取设置倒计时的总时间
	 * @return
	 */
	public long getDownCountTime() {
		return totalTime;
	}

	/**
	 * 设定需要倒计时的总共时间
	 *
	 * @param totalDownCountTimes
	 */
	public void setDownCountTime(long totalDownCountTimes) {
		this.totalTime = totalDownCountTimes;
		pauseDownCountTimer();
	}

	/**
	 * 设定倒计时的时间开始时间和结束时间
	 *
	 * @param startTime
	 * @param endTime
	 */
	public void setDownCountTime(long startTime, long endTime) {
		this.totalTime = endTime - startTime;
		pauseDownCountTimer();
	}

	/**
	 * 开始倒计时
	 */
	public void startDownCountTimer() {
		isRunning=true;
		setTime2Text(getDownCountTime());
		mRunnable = new Runnable() {

			@Override
			public void run() {
				int i=secTextView.getCurrentValue();
			   // Log.e("sec时间----->",i + "");
				outNumber--;
				if (outNumber<=0) {
					if (i > 0) {
						secTextView.smoothFlip();
					} else {
						int j = getClockMinValue();
						j--;
						if (j >= 0 && i == 0) {
							//Log.e("分钟时间----->", j + "");
							minTextView.smoothFlip();
							secTextView.setClockTime("60");
							secTextView.smoothFlip();
						} else {
							int k = getClockHourValue();
							k--;

							if (k >= 0 && j < 0 && i == 0) {
								hourTextView.smoothFlip();
								minTextView.setClockTime("60");
								minTextView.smoothFlip();
								secTextView.setClockTime("60");
								secTextView.smoothFlip();
							} else {
								int d = getClockDayValue();
								d--;
								if (d < 0) {
									//	Log.e("时间结束----->", j + "    " + i);
									isRunning = false;
									if (null != mDownCountTimerListener) {
										mDownCountTimerListener.stopDownCountTimer();
									}
								} else {
									Log.e("day----->", d + "    ");
									secTextView.setClockTime("60");
									minTextView.setClockTime("60");
									hourTextView.setClockTime("24");
									d++;
									dayTextView.setClockTime("" + d);
									secTextView.smoothFlip();
									minTextView.smoothFlip();
									hourTextView.smoothFlip();
									dayTextView.smoothFlip();
								}
							}
						}
					}
				}
				if (isRunning) {
					mHandler.postDelayed(this, 1000);
				} else {
					mHandler.removeCallbacks(this);
				}
			}
		};
		mHandler.postDelayed(mRunnable, 1000);

	}
	/**
	 * 获取倒计时剩余的时间,数组的4个元素分别代表剩余的天、时、分、秒
	 * @return
	 */
	public String[] getClockRestTime(){
		String[] restTime=new String[4];
		restTime[0]=String.valueOf(getClockDayValue());
		restTime[1]=String.valueOf(getClockHourValue());
		restTime[2]=String.valueOf(getClockMinValue());
		restTime[3]=String.valueOf(getClockSecValue());
		return restTime;
	}

	public int getScreenWidth(Context mContext) {
       return mContext.getResources().getDisplayMetrics().widthPixels;
	}

	/**
	 * 根据给定的时间转换为 天、时、分
	 *
	 * @param startTime
	 */
	private void setTime2Text(long startTime) {

		int ss = 1000;
		int mi = ss * 60;
		int hh = mi * 60;
		int dd = hh * 24;

		long day = startTime / dd;
		long hour = (startTime - day * dd) / hh;
		long minute = (startTime - day * dd - hour * hh) / mi;
		long second = (startTime - day * dd - hour * hh - minute * mi) / ss;

		String strDay = day < 10 ? "0" + day : "" + day; // 天
		String strHour = hour < 10 ? "0" + hour : "" + hour;// 小时
		String strMinute = minute < 10 ? "0" + minute : "" + minute;// 分钟
		String strSecond = second < 10 ? "0" + second : "" + second;// 秒

		Log.e("时间----》", strDay+"  "+strHour +"  "+strMinute+"  "+strSecond);
		if (Integer.parseInt(strDay) >= 100) {
			dayTextView.setClockTime("99");
			hourTextView.setClockTime("23");
			minTextView.setClockTime("59");
			secTextView.getmInvisibleTextView().setText("59");
			secTextView.getmVisibleTextView().setText("59");
			outNumber=((startTime)-1000L*60L*60L*24L*100L)/1000L;
		//	Log.e("多余的时间----》",outNumber+"");
		} else {
			dayTextView.setClockTime(strDay);
			hourTextView.setClockTime(strHour);
			minTextView.setClockTime(strMinute);
			secTextView.getmVisibleTextView().setText(strSecond);
			secTextView.getmInvisibleTextView().setText(strSecond);
			outNumber=0;
		}
	}

	public int getClockDayValue() {
		return dayTextView.getCurrentValue();
	}

	public int getClockHourValue() {
		return hourTextView.getCurrentValue();
	}

	public int getClockMinValue() {
		return minTextView.getCurrentValue();
	}

	public int getClockSecValue(){
		return secTextView.getCurrentValue();
	}
	
}
MyTextView.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

public class MyTextView extends TextView {

	Paint mPaint;
	
	public MyTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaint();
	}

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

	public MyTextView(Context context) {
		this(context, null);
		initPaint();
	}
	
	private void initPaint() {
		mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
		mPaint.setStyle(Paint.Style.FILL);
		mPaint.setColor(Color.BLACK);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawRect(0,getMeasuredHeight()/2-1,getMeasuredWidth(),getMeasuredHeight()/2+1, mPaint);
		
	}

}

MainActivity.java
MainActivity.java
package com.example.timeticker;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener, MyClockView.DownCountTimerListener {
    Button mBtn;
    private MyClockView myClockView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.clock_view_activity);
        mBtn = (Button) findViewById(R.id.button1);
        mBtn.setOnClickListener(this);
        myClockView = (MyClockView) findViewById(R.id.clockView);
        myClockView.setDownCountTimerListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.button1:
                myClockView.setDownCountTime(1000L * 60L + 1000L * 12L);
                myClockView.startDownCountTimer();

                break;

            default:
                break;
        }
    }

    @Override
    public void stopDownCountTimer() {
        Toast.makeText(this,"结束了",Toast.LENGTH_SHORT).show();
    }
}


PS.上面的效果,可能在某些手机上翻转时的效果会出现闪烁的现象,是因为手机的硬件加速功能开启了的原因,为此。你需要在初始化view的时候关闭硬件加速。

myClockView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

还有,有些手机上适配效果不好,可以在布局文件里添加padding的属性,保证控件都在所包裹的父布局中。

好了,上面应该比较清楚了,有不清楚的,可以留言,欢迎探讨。
喜欢本文的,点个赞。


点击下载Demo

2018-08-27 17:11:48 qq_41264674 阅读数 263
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>transition绘制翻页效果</title>
	<style>
		.btn{
			margin:40px;	
		}
		#book{
			background-color: black;
			margin: 40px;
			width: 400px;
			height: 200px;
			padding: 0;
			perspective: 800;
			transform-style: preserve-3d;
			-webkit-perspective: 800;
			-webkit-transform-style: -webkit-preserve-3d;
		}
		#page1{
			position: absolute;
			width: 200px;
			height: 200px;
			background-color: grey;
			transform-origin: right center;
			transition: transform 1s linear;
			-webkit-transition: transform 1s linear;
			-webkit-transform-origin: right center;
		}
		#page2, #page3, #page4{
			position: absolute;
			width: 200px;
			height: 200px;
			background-color: grey;
			transition: transform 1s linear;
			-webkit-transition: transform 1s linear;
			transform-origin: right center;
			-webkit-transform-origin: right center;
			transform: rotateY(180deg);
			-webkit-transform: rotateY(180deg);
		}
 
		#left{
			float: left;
			background-color: black;
			margin-left: 40px;
			width: 200px;
			height: 200px;
			padding: 0;
			overflow: hidden;
			perspective: 800;
			transform-style: preserve-3d;
			-webkit-perspective: 800;
			-webkit-transform-style: -webkit-preserve-3d;
		}
		#right{
			float: left;
			background-color: black;
			width: 200px;
			height: 200px;
			padding: 0;
			overflow: hidden;
			perspective: 800;
			transform-style: preserve-3d;
			-webkit-perspective: 800;
			-webkit-transform-style: -webkit-preserve-3d;
		}
		#pagel1,#pagel2,#pagel3,#pagel4{
			position: absolute;
			width: 158px;
			height: 158px;
			background-color: grey;
			padding: 20px;
			transform-origin: right center;
			transition: transform 1s linear;
			-webkit-transition: transform 1s linear;
			-webkit-transform-origin: right center;
			border: 1px solid red;
		}
		#pagel2,#pagel3,#pagel4{
			transform: rotateY(180deg);
			-webkit-transform: rotateY(180deg);
		}
		#pager1,#pager2,#pager3,#pager4{
			position: absolute;
			width: 158px;
			height: 158px;
			background-color: grey;
			padding: 20px;
			transition: transform 1s linear;
			-webkit-transition: transform 1s linear;
			transform-origin: left center;
			-webkit-transform-origin: left center;
			border: 1px solid red;
		}
		#pager1{
			z-index: -10;
		}
		#pager2{
			z-index: -20;
		}
		#pager3{
			z-index: -30;
		}
		#pager4{
			z-index: -40;
		}
	</style>
</head>
<body>
	<div id="book">
		<div id="page1">1</div>
		<div id="page2">2</div>
		<div id="page3">3</div>
		<div id="page4">4</div>
	</div>
	<div class="btn">
		<a href="javascript:prev()">上一页</a>  <a href="javascript:next()">下一页</a>
	</div>
 
	<div id="left">
		<div id="pagel1">第1页</div>
		<div id="pagel2">第3页</div>
		<div id="pagel3">第5页</div>
		<div id="pagel4">第7页</div>
	</div>
	<div id="right">
		<div id="pager1">第2页</div>
		<div id="pager2">第4页</div>
		<div id="pager3">第6页</div>
		<div id="pager4">第8页</div>
	</div>
	<div style="clear:both;"></div>
	<div class="btn">
		<a href="javascript:prev2()">上一页</a>  <a href="javascript:next2()">下一页</a>
	</div>
	<script>
		var curIndex = 1;
		function prev(){
			if(1==curIndex) return;
			var lPage = document.getElementById("page"+curIndex);
			var llPage = document.getElementById("page"+(curIndex-1));
			var rPage = document.getElementById("page"+(curIndex+1));
			if(llPage)llPage.style.zIndex = 100;
			if(rPage)rPage.style.zIndex = 0;
			lPage.style.transform = "rotateY(180deg)";
			lPage.style.webkitTransform = "rotateY(180deg)";
			curIndex--;
		}
		function next(){
			if(4==curIndex) return;
			curIndex++;
			var lPage = document.getElementById("page"+curIndex);
			var rPage = document.getElementById("page"+(curIndex+1));
			if(rPage)rPage.style.zIndex = 100;
			lPage.style.transform = "rotateY(0deg)";
			lPage.style.webkitTransform = "rotateY(0deg)";
		}
		var curPage = 1;
		function prev2(){
			if(1===curPage) return;
			//左边翻页
			var lPage = document.getElementById("pagel" + curPage);
			lPage.style.transform = "rotateY(180deg)";
			lPage.style.webkitTransform = "rotateY(180deg)";
			//右边翻页
			var rPage = document.getElementById("pager" + (curPage-1));
			rPage.style.transform = "rotateY(0deg)";
			rPage.style.webkitTransform = "rotateY(0deg)";
			curPage--;
		}
		function next2(){
			if(4===curPage) return;
			//左边翻页
			var lPage = document.getElementById("pagel" + (curPage+1));
			lPage.style.transform = "rotateY(0deg)";
			lPage.style.webkitTransform = "rotateY(0deg)";
			//右边翻页
			var rPage = document.getElementById("pager" + curPage);
			rPage.style.transform = "rotateY(-180deg)";
			rPage.style.webkitTransform = "rotateY(-180deg)";
			curPage++;
		}
	</script>
</body>
</html>

 

2013-05-30 16:19:31 donny_zhang 阅读数 736

  此例是封面沿x轴向上做3d形式的翻开动画效果。

如图:



核心代码分为打开和关闭


- (void)open:(NSTimer *)timer
{
    pi++;

     //CATransform3DMakeRotation 作用是沿x,y,z轴旋转一个弧度。secondView 就是要向上翻开的封面。
    _secondView.layer.transform = CATransform3DMakeRotation(M_PI*pi/180.0, 1, 0, 0);
    if (90 == pi) {
        UIImage* image;
        image = [UIImage imageNamed:@"Test3.png"];//Test3.png这张图片的顶部做折角阴影,因为翻转倒过来了,顶部在折角处
        [_secondView setImage:image];
        image = nil;
    }
    if (135 == pi) {
        [timer invalidate];
    }
}

- (void)close:(NSTimer *)timer
{
    pi--;
    _secondView.layer.transform = CATransform3DMakeRotation(M_PI*pi/180.0, 1, 0, 0);
    if (90 == pi) {
        UIImage* image;
        image = [UIImage imageNamed:@"Test2.png"];
        [_secondView setImage:image];
        image = nil;
    }
    if (0 == pi) {
        [timer invalidate];
    }
}



demo下载地址:http://download.csdn.net/detail/donny_zhang/5482069


追加一下;CATransform3DMakeRotation 相机的默认位置是屏幕的中心点。也就是说,_secondView.layer.transform = CATransform3DMakeRotation(M_PI*pi/180.0, 1, 0, 0);

这样设置,如果要翻开的图片是在屏幕中间那就没什么问题,如果要翻开的图片不在屏幕中间,那翻开效果会是一个偏的3d效果,如图:



我们可以通过设置 CATransform3D 的相机位置来解决,将相机位置设置成封面的中心点,就可以让封面正对这屏幕以3d的形式翻开。

_secondView.layer.transform=CATransform3DConcat( CATransform3DMakeRotation(M_PI*pi/180.0, 1, 0, 0), CATransform3DMakePerspective(CGPointMake(70, 50), 500));

这句话中CATransform3DMakePerspective 是指定相机位置和离屏幕的距离或者叫高度。

没有更多推荐了,返回首页