一.概述
最近在研究公司的代码,发现每次切换页面时做的进度条效果还挺不错的,所以想深入研究一下,今天就带大家来看看到底是如何实现的,首先上效果图
二.实现
上面的进度条最大的特点就是有种立体的感觉,中间的文字貌似是在垂直于平面在转动,下面看看具体的实现:
1.首先我们需要四张图片
SOUFUN_LOADING_BAR
SOUFUN_LOADING
SOUFUN_LOADING1
SOUFUN_LOADING2
然后我们开始写自定义的View,名字叫做PageLoadingView,那么这个类该继承什么呢?在这里我们需要继承FrameLayout,因为我们要把这些图片添加到当前页面中去的,所有我们必须继承一个具体的ViewGroup的实现类,这里我们选择最简单的FrameLayout。
public class PageLoadingView extends FrameLayout{
//背景图
protected static int SOUFUN_LOADING_BAR = R.drawable.page_loading_bar;
//文字"天"的图片
protected static int SOUFUN_LOADING = R.drawable.page_loading;
//文字"下"的图片
protected static int SOUFUN_LOADING1 = R.drawable.page_loading1;
//文字"贷"的图片
protected static int SOUFUN_LOADING2 = R.drawable.page_loading2;
然后我们做初始化动作,并且在构造函数中调用
/**
* 在布局文件中使用对象是会调用此方法
* @param context
* @param attrs
*/
public PageLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 在代码中创建对象是会调用此方法
* @param context
*/
public PageLoadingView(Context context) {
super(context);
init(context);
}
/**
* 初始化,把图片添加到布局中
* @param context
*/
public void init(Context context) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
//创建一个ImageView对象
iv_loading_bar = new ImageView(context);
//设置ImageView所显示的图片
iv_loading_bar.setImageResource(SOUFUN_LOADING_BAR);
iv_loading_bar.setLayoutParams(params);
iv_loading = new ImageView(context);
iv_loading.setImageResource(SOUFUN_LOADING2);
iv_loading.setLayoutParams(params);
//将背景图片添加到FrameLayout
this.addView(iv_loading_bar);
//把文字图片添加到FrameLayout中,这里可以随意添加一张,因为我们会在动画开始的时候动态替换
this.addView(iv_loading);
}
我们可以看到外面的圆圈很明显是个旋转动画,我们看看怎么实现:
private Animation createRotateAnimation() {
//绕自身中心旋转,从0到360度
Animation animation = new RotateAnimation(0, +360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
//动画自执行时长
animation.setDuration(900);
//无限旋转
animation.setRepeatCount(Animation.INFINITE);
//设置线性插值器,意思就是让匀速旋转
animation.setInterpolator(new LinearInterpolator());
return animation;
}
接下来该看看重点了,也就是中间文字的旋转动画,我们先上代码,然后解释
private Rotate3d createRotate3dAnimation() {
//获取旋转点所在的X坐标,这里为容器的中心getWidth获得的就是整个容器的宽度
final float centerX = this.getWidth() / 2.0f;
final Rotate3d rotation = new Rotate3d(-90, 90, centerX, 0, 0, true);
rotation.setDuration(500);
rotation.setRepeatCount(Animation.INFINITE);
rotation.setInterpolator(new LinearInterpolator());
rotation.setAnimationListener(mAnimationListener);
return rotation;
}
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
int time = 1;
/**
* 动画开始时执行此方法
* @param animation
*/
@Override
public void onAnimationStart(Animation animation) {
//将中心的文字设置为"天"
iv_loading.setImageResource(SOUFUN_LOADING);
}
/**
* 动画不断重复时执行此方法,因为我们把动画的模式都设置为了INFINITE,也就是无限循环
* 所以此方法会不断的执行
* @param animation
*/
@Override
public void onAnimationRepeat(Animation animation) {
//清除缓存
iv_loading.destroyDrawingCache();
if (time % 3 == 0) {
iv_loading.setImageResource(SOUFUN_LOADING);
} else if (time % 3 == 1) {
iv_loading.setImageResource(SOUFUN_LOADING1);
}else{
iv_loading.setImageResource(SOUFUN_LOADING2);
}
time++;
}
@Override
public void onAnimationEnd(Animation animation) { // 动画结束时执行此方法
}
};
我们先来看比较简单的,也就是图片是如何不断变化的,重点就在onAnimationRepeat方法里面,在这个方法里面我们定义了一个int类型的time,这里我们根据time值的不同,每次就会变化不同的图片,因为onAnimationRepeat方法是不断执行的,time的值是不断增加的,所以每隔固定的时间我们就可以看到进度条中间的文字图片在改变。
然后我们看看比较重要的一行
final Rotate3d rotation = new Rotate3d(-90, 90, centerX, 0, 0, true);
这里有一个类Rotate3d,重点在这里,我们进去看看,先给出完整代码:
public class Rotate3d extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
public Rotate3d(float fromDegrees, float toDegrees, float centerX, float centerY, float depthZ, boolean reverse) {
mFromDegrees = fromDegrees;
mToDegrees = toDegrees;
mCenterX = centerX;
mCenterY = centerY;
mDepthZ = depthZ;
mReverse = reverse;
}
/**
* 将初始化动画组件及其父容器的宽高;通常亦可进行另外的初始化工作
* @param width
* @param height
* @param parentWidth
* @param parentHeight
*/
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
//获得代表变换的矩阵对象
final Matrix matrix = t.getMatrix();
//保存Camera的状态
camera.save();
if (mReverse) {
//正向旋转,距离Z轴的距离从小到大
camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else {
//逆向旋转,距离Z轴的距离从小到大
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
//根据Y轴旋转
camera.rotateY(degrees);
//计算当前变换所对应的矩阵并且将其复制给应用此Matrix的对象
camera.getMatrix(matrix);
//恢复保存的状态
camera.restore();
//根据给定的转换设定matrix
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
我们看到Rotate3d 这个类继承了Animation,这里就是自定义动画。
比较重要的是两个方法initialize和applyTransformation,我们先来看initialize这个方法,
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
根据官方的介绍,这个方法的作用是初始化动画组件及其父容器的宽高;通常亦可进行另外的初始化工作,我们在这里创建了一个Camera对象,供后面的使用。
然后我们来看applyTransformation这个方法
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
}
该方法有两个参数。第一个为动画的进度时间值,范围是[0.0f,1.0f],第二个参数Transformation记录着动画某一帧中变形的原始数据。该方法在动画的每一帧显示过程中都会被调用。
然后我们在这里做了一个线性算法来生成中间角度:
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
因为interpolatedTime是不断变化的,所以degrees这个值也是不断变化的。Camera 类是用来实现绕 Y 轴旋转后透视投影的。我们只需要其返回的 Matrix 值 , 这个值会赋给 Transformation 中的矩阵成员,这里涉及到了动画的实现原理,这里就不详细介绍了,大家下来自己研究研究,最后给出PageLoadingView的全部代码:
public class PageLoadingView extends FrameLayout{
//背景图
protected static int SOUFUN_LOADING_BAR = R.drawable.page_loading_bar;
//文字"天"的图片
protected static int SOUFUN_LOADING = R.drawable.page_loading;
//文字"下"的图片
protected static int SOUFUN_LOADING1 = R.drawable.page_loading1;
//文字"贷"的图片
protected static int SOUFUN_LOADING2 = R.drawable.page_loading2;
private static Animation animation;
private static Rotate3d rotate3d;
private ImageView iv_loading_bar;
private ImageView iv_loading;
/**
* 在布局文件中使用对象是会调用此方法
* @param context
* @param attrs
*/
public PageLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 在代码中创建对象是会调用此方法
* @param context
*/
public PageLoadingView(Context context) {
super(context);
init(context);
}
/**
* 初始化,把图片添加到布局中
* @param context
*/
public void init(Context context) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
//创建一个ImageView对象
iv_loading_bar = new ImageView(context);
//设置ImageView所显示的图片
iv_loading_bar.setImageResource(SOUFUN_LOADING_BAR);
iv_loading_bar.setLayoutParams(params);
iv_loading = new ImageView(context);
iv_loading.setImageResource(SOUFUN_LOADING2);
iv_loading.setLayoutParams(params);
//将背景图片添加到FrameLayout
this.addView(iv_loading_bar);
//把文字图片添加到FrameLayout中,这里可以随意添加一张,因为我们会在动画开始的时候替换为第一张
//也就是"天"这种图片
this.addView(iv_loading);
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (visibility == View.VISIBLE) {
animation = createRotateAnimation();
iv_loading_bar.startAnimation(animation);
post(new Runnable() {
@Override
public void run() {
rotate3d = createRotate3dAnimation();
iv_loading.startAnimation(rotate3d);
}
});
}
}
private Animation createRotateAnimation() {
//绕自身中心旋转,从0到360度
Animation animation = new RotateAnimation(0, +360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
//动画自执行时长
animation.setDuration(900);
//无限旋转
animation.setRepeatCount(Animation.INFINITE);
//设置线性插值器,意思就是让匀速旋转
animation.setInterpolator(new LinearInterpolator());
return animation;
}
private Rotate3d createRotate3dAnimation() {
//获取旋转点所在的X坐标,这里为容器的中心getWidth获得的就是整个容器的宽度
final float centerX = this.getWidth() / 2.0f;
final Rotate3d rotation = new Rotate3d(-90, 90, centerX, 0, 0, true);
rotation.setDuration(500);
rotation.setRepeatCount(Animation.INFINITE);
rotation.setInterpolator(new LinearInterpolator());
rotation.setAnimationListener(mAnimationListener);
return rotation;
}
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
int time = 1;
/**
* 动画开始时执行此方法
* @param animation
*/
@Override
public void onAnimationStart(Animation animation) {
//将中心的文字设置为"天"
iv_loading.setImageResource(SOUFUN_LOADING);
}
/**
* 动画不断重复时执行此方法,因为我们把动画的模式都设置为了INFINITE,也就是无限循环
* 所以此方法会不断的执行
* @param animation
*/
@Override
public void onAnimationRepeat(Animation animation) {
//清除缓存
iv_loading.destroyDrawingCache();
if (time % 3 == 0) {
iv_loading.setImageResource(SOUFUN_LOADING);
} else if (time % 3 == 1) {
iv_loading.setImageResource(SOUFUN_LOADING1);
}else{
iv_loading.setImageResource(SOUFUN_LOADING2);
}
time++;
}
@Override
public void onAnimationEnd(Animation animation) { // 动画结束时执行此方法
}
};