Android 一个绚丽的loading动效分析与实现!

时间:2023-03-08 16:32:04

http://blog.****.net/tianjian4592/article/details/44538605

前两天我们这边的头儿给我说,有个 gif 动效很不错,可以考虑用来做项目里的loading,问我能不能实现,看了下效果确实不错,也还比较有新意,复杂度也不是非常高,所以就花时间给做了,我们先一起看下原gif图效果:

Android 一个绚丽的loading动效分析与实现!

从效果上看,我们需要考虑以下几个问题:

1.叶子的随机产生;

2.叶子随着一条正余弦曲线移动;

3.叶子在移动的时候旋转,旋转方向随机,正时针或逆时针;

4.叶子遇到进度条,似乎是融合进入;

5.叶子不能超出最左边的弧角;

7.叶子飘出时的角度不是一致,走的曲线的振幅也有差别,否则太有规律性,缺乏美感;

总的看起来,需要注意和麻烦的地方主要是以上几点,当然还有一些细节问题,比如最左边是圆弧等等;

那接下来我们将效果进行分解,然后逐个击破:

整个效果来说,我们需要的图主要是飞动的小叶子和右边旋转的风扇,其他的部分都可以用色值进行绘制,当然我们为了方便,就连底部框一起切了;

先从gif 图里把飞动的小叶子和右边旋转的风扇、底部框抠出来,小叶子图如下:

Android 一个绚丽的loading动效分析与实现!

我们需要处理的主要有两个部分:

1. 随着进度往前绘制的进度条;

2. 不断飞出来的小叶片;

我们先处理第一部分 - 随着进度往前绘制的进度条:

进度条的位置根据外层传入的 progress 进行计算,可以分为图中 1、2、3 三个阶段:

Android 一个绚丽的loading动效分析与实现!

1. 当progress 较小,算出的当前距离还在弧形以内时,需要绘制如图所示 1 区域的弧形,其余部分用白色填充;

2. 当 progress 算出的距离到2时,需要绘制棕色半圆弧形,其余部分用白色矩形填充;

3. 当 progress 算出的距离到3 时,需要绘制棕色半圆弧形,棕色矩形,白色矩形;

4. 当 progress 算出的距离到头时,需要绘制棕色半圆弧形,棕色矩形;(可以合并到3中)

首先根据进度条的宽度和当前进度、总进度算出当前的位置:

  1. //mProgressWidth为进度条的宽度,根据当前进度算出进度条的位置
  2. mCurrentProgressPosition = mProgressWidth * mProgress / TOTAL_PROGRESS;

然后按照上面的逻辑进行绘制,其中需要计算上图中的红色弧角角度,计算方法如下:

  1. // 单边角度
  2. int angle = (int) Math.toDegrees(Math.acos((mArcRadius - mCurrentProgressPosition)/ (float) mArcRadius));

Math.acos()  -反余弦函数;

Math.toDegrees() - 弧度转化为角度,Math.toRadians 角度转化为弧度

所以圆弧的起始点为:

  1. int startAngle = 180 - angle;

圆弧划过的角度为:

  1. 2 * angle

这一块的代码如下:

  1. // mProgressWidth为进度条的宽度,根据当前进度算出进度条的位置
  2. mCurrentProgressPosition = mProgressWidth * mProgress / TOTAL_PROGRESS;
  3. // 即当前位置在图中所示1范围内
  4. if (mCurrentProgressPosition < mArcRadius) {
  5. Log.i(TAG, "mProgress = " + mProgress + "---mCurrentProgressPosition = "
  6. + mCurrentProgressPosition
  7. + "--mArcProgressWidth" + mArcRadius);
  8. // 1.绘制白色ARC,绘制orange ARC
  9. // 2.绘制白色矩形
  10. // 1.绘制白色ARC
  11. canvas.drawArc(mArcRectF, 90, 180, false, mWhitePaint);
  12. // 2.绘制白色矩形
  13. mWhiteRectF.left = mArcRightLocation;
  14. canvas.drawRect(mWhiteRectF, mWhitePaint);
  15. // 3.绘制棕色 ARC
  16. // 单边角度
  17. int angle = (int) Math.toDegrees(Math.acos((mArcRadius - mCurrentProgressPosition)
  18. / (float) mArcRadius));
  19. // 起始的位置
  20. int startAngle = 180 - angle;
  21. // 扫过的角度
  22. int sweepAngle = 2 * angle;
  23. Log.i(TAG, "startAngle = " + startAngle);
  24. canvas.drawArc(mArcRectF, startAngle, sweepAngle, false, mOrangePaint);
  25. } else {
  26. Log.i(TAG, "mProgress = " + mProgress + "---transfer-----mCurrentProgressPosition = "
  27. + mCurrentProgressPosition
  28. + "--mArcProgressWidth" + mArcRadius);
  29. // 1.绘制white RECT
  30. // 2.绘制Orange ARC
  31. // 3.绘制orange RECT
  32. // 1.绘制white RECT
  33. mWhiteRectF.left = mCurrentProgressPosition;
  34. canvas.drawRect(mWhiteRectF, mWhitePaint);
  35. // 2.绘制Orange ARC
  36. canvas.drawArc(mArcRectF, 90, 180, false, mOrangePaint);
  37. // 3.绘制orange RECT
  38. mOrangeRectF.left = mArcRightLocation;
  39. mOrangeRectF.right = mCurrentProgressPosition;
  40. canvas.drawRect(mOrangeRectF, mOrangePaint);
  41. }

接下来再来看叶子部分:

首先根据效果情况基本确定出 曲线函数,标准函数方程为:y = A(wx+Q)+h,其中w影响周期,A影响振幅 ,周期T= 2 * Math.PI/w;

根据效果可以看出,周期大致为总进度长度,所以确定w=(float) ((float) 2 * Math.PI /mProgressWidth);

仔细观察效果,我们可以发现,叶子飘动的过程中振幅不是完全一致的,产生一种错落的效果,既然如此,我们给叶子定义一个Type,根据Type 确定不同的振幅;

我们创建一个叶子对象:

  1. private class Leaf {
  2. // 在绘制部分的位置
  3. float x, y;
  4. // 控制叶子飘动的幅度
  5. StartType type;
  6. // 旋转角度
  7. int rotateAngle;
  8. // 旋转方向--0代表顺时针,1代表逆时针
  9. int rotateDirection;
  10. // 起始时间(ms)
  11. long startTime;
  12. }

类型采用枚举进行定义,其实就是用来区分不同的振幅:

  1. private enum StartType {
  2. LITTLE, MIDDLE, BIG
  3. }

创建一个LeafFactory类用于创建一个或多个叶子信息:

  1. private class LeafFactory {
  2. private static final int MAX_LEAFS = 6;
  3. Random random = new Random();
  4. // 生成一个叶子信息
  5. public Leaf generateLeaf() {
  6. Leaf leaf = new Leaf();
  7. int randomType = random.nextInt(3);
  8. // 随时类型- 随机振幅
  9. StartType type = StartType.MIDDLE;
  10. switch (randomType) {
  11. case 0:
  12. break;
  13. case 1:
  14. type = StartType.LITTLE;
  15. break;
  16. case 2:
  17. type = StartType.BIG;
  18. break;
  19. default:
  20. break;
  21. }
  22. leaf.type = type;
  23. // 随机起始的旋转角度
  24. leaf.rotateAngle = random.nextInt(360);
  25. // 随机旋转方向(顺时针或逆时针)
  26. leaf.rotateDirection = random.nextInt(2);
  27. // 为了产生交错的感觉,让开始的时间有一定的随机性
  28. mAddTime += random.nextInt((int) (LEAF_FLOAT_TIME * 1.5));
  29. leaf.startTime = System.currentTimeMillis() + mAddTime;
  30. return leaf;
  31. }
  32. // 根据最大叶子数产生叶子信息
  33. public List<Leaf> generateLeafs() {
  34. return generateLeafs(MAX_LEAFS);
  35. }
  36. // 根据传入的叶子数量产生叶子信息
  37. public List<Leaf> generateLeafs(int leafSize) {
  38. List<Leaf> leafs = new LinkedList<Leaf>();
  39. for (int i = 0; i < leafSize; i++) {
  40. leafs.add(generateLeaf());
  41. }
  42. return leafs;
  43. }
  44. }

定义两个常亮分别记录中等振幅和之间的振幅差:

  1. // 中等振幅大小
  2. private static final int MIDDLE_AMPLITUDE = 13;
  3. // 不同类型之间的振幅差距
  4. private static final int AMPLITUDE_DISPARITY = 5;
  1. // 中等振幅大小
  2. private int mMiddleAmplitude = MIDDLE_AMPLITUDE;
  3. // 振幅差
  4. private int mAmplitudeDisparity = AMPLITUDE_DISPARITY;

有了以上信息,我们则可以获取到叶子的Y值:

  1. // 通过叶子信息获取当前叶子的Y值
  2. private int getLocationY(Leaf leaf) {
  3. // y = A(wx+Q)+h
  4. float w = (float) ((float) 2 * Math.PI / mProgressWidth);
  5. float a = mMiddleAmplitude;
  6. switch (leaf.type) {
  7. case LITTLE:
  8. // 小振幅 = 中等振幅 - 振幅差
  9. a = mMiddleAmplitude - mAmplitudeDisparity;
  10. break;
  11. case MIDDLE:
  12. a = mMiddleAmplitude;
  13. break;
  14. case BIG:
  15. // 小振幅 = 中等振幅 + 振幅差
  16. a = mMiddleAmplitude + mAmplitudeDisparity;
  17. break;
  18. default:
  19. break;
  20. }
  21. Log.i(TAG, "---a = " + a + "---w = " + w + "--leaf.x = " + leaf.x);
  22. return (int) (a * Math.sin(w * leaf.x)) + mArcRadius * 2 / 3;
  23. }

接下来,我们开始绘制叶子:

  1. /**
  2. * 绘制叶子
  3. *
  4. * @param canvas
  5. */
  6. private void drawLeafs(Canvas canvas) {
  7. long currentTime = System.currentTimeMillis();
  8. for (int i = 0; i < mLeafInfos.size(); i++) {
  9. Leaf leaf = mLeafInfos.get(i);
  10. if (currentTime > leaf.startTime && leaf.startTime != 0) {
  11. // 绘制叶子--根据叶子的类型和当前时间得出叶子的(x,y)
  12. getLeafLocation(leaf, currentTime);
  13. // 根据时间计算旋转角度
  14. canvas.save();
  15. // 通过Matrix控制叶子旋转
  16. Matrix matrix = new Matrix();
  17. float transX = mLeftMargin + leaf.x;
  18. float transY = mLeftMargin + leaf.y;
  19. Log.i(TAG, "left.x = " + leaf.x + "--leaf.y=" + leaf.y);
  20. matrix.postTranslate(transX, transY);
  21. // 通过时间关联旋转角度,则可以直接通过修改LEAF_ROTATE_TIME调节叶子旋转快慢
  22. float rotateFraction = ((currentTime - leaf.startTime) % LEAF_ROTATE_TIME)
  23. / (float) LEAF_ROTATE_TIME;
  24. int angle = (int) (rotateFraction * 360);
  25. // 根据叶子旋转方向确定叶子旋转角度
  26. int rotate = leaf.rotateDirection == 0 ? angle + leaf.rotateAngle : -angle
  27. + leaf.rotateAngle;
  28. matrix.postRotate(rotate, transX
  29. + mLeafWidth / 2, transY + mLeafHeight / 2);
  30. canvas.drawBitmap(mLeafBitmap, matrix, mBitmapPaint);
  31. canvas.restore();
  32. } else {
  33. continue;
  34. }
  35. }
  36. }

最后,向外层暴露几个接口:

  1. /**
  2. * 设置中等振幅
  3. *
  4. * @param amplitude
  5. */
  6. public void setMiddleAmplitude(int amplitude) {
  7. this.mMiddleAmplitude = amplitude;
  8. }
  9. /**
  10. * 设置振幅差
  11. *
  12. * @param disparity
  13. */
  14. public void setMplitudeDisparity(int disparity) {
  15. this.mAmplitudeDisparity = disparity;
  16. }
  17. /**
  18. * 获取中等振幅
  19. *
  20. * @param amplitude
  21. */
  22. public int getMiddleAmplitude() {
  23. return mMiddleAmplitude;
  24. }
  25. /**
  26. * 获取振幅差
  27. *
  28. * @param disparity
  29. */
  30. public int getMplitudeDisparity() {
  31. return mAmplitudeDisparity;
  32. }
  33. /**
  34. * 设置进度
  35. *
  36. * @param progress
  37. */
  38. public void setProgress(int progress) {
  39. this.mProgress = progress;
  40. postInvalidate();
  41. }
  42. /**
  43. * 设置叶子飘完一个周期所花的时间
  44. *
  45. * @param time
  46. */
  47. public void setLeafFloatTime(long time) {
  48. this.mLeafFloatTime = time;
  49. }
  50. /**
  51. * 设置叶子旋转一周所花的时间
  52. *
  53. * @param time
  54. */
  55. public void setLeafRotateTime(long time) {
  56. this.mLeafRotateTime = time;

这些接口用来干嘛呢?用于把我们的动效做成完全可手动调节的,这样做有什么好处呢?

1. 更加便于产品、射鸡湿查看效果,避免YY,自己手动调节,不会出现要你一遍遍的改参数安装、查看、再改、再查看... ... N遍之后说 “这好像不是我想要的” -- 瞬间天崩地裂,天昏地暗,感觉被全世界抛弃;

2. 便于体现你是一个考虑全面,思维缜密,会编程、会设计的艺术家,当然这纯属YY,主要还是方便大家;

如此一来,射鸡湿们只需要不断的调节即可实时的看到展现的效果,最后只需要把最终的参数反馈过来即可,万事大吉,一了百了;

当然,如果对方是个漂亮的妹子,而你又苦于没有机会搭讪,以上内容就当我没说,尽情的不按要求写吧,她肯定会主动找你的,说不定连饭都反过来请了... ...

好啦,言归正传,完成收尾部分,我们让所有的参数都可调节起来:

把剩下的layout 和activity贴出来:

activity:

  1. public class LeafLoadingActivity extends Activity implements OnSeekBarChangeListener,
  2. OnClickListener {
  3. Handler mHandler = new Handler() {
  4. public void handleMessage(Message msg) {
  5. switch (msg.what) {
  6. case REFRESH_PROGRESS:
  7. if (mProgress < 40) {
  8. mProgress += 1;
  9. // 随机800ms以内刷新一次
  10. mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS,
  11. new Random().nextInt(800));
  12. mLeafLoadingView.setProgress(mProgress);
  13. } else {
  14. mProgress += 1;
  15. // 随机1200ms以内刷新一次
  16. mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS,
  17. new Random().nextInt(1200));
  18. mLeafLoadingView.setProgress(mProgress);
  19. }
  20. break;
  21. default:
  22. break;
  23. }
  24. };
  25. };
  26. private static final int REFRESH_PROGRESS = 0x10;
  27. private LeafLoadingView mLeafLoadingView;
  28. private SeekBar mAmpireSeekBar;
  29. private SeekBar mDistanceSeekBar;
  30. private TextView mMplitudeText;
  31. private TextView mDisparityText;
  32. private View mFanView;
  33. private Button mClearButton;
  34. private int mProgress = 0;
  35. private TextView mProgressText;
  36. private View mAddProgress;
  37. private SeekBar mFloatTimeSeekBar;
  38. private SeekBar mRotateTimeSeekBar;
  39. private TextView mFloatTimeText;
  40. private TextView mRotateTimeText;
  41. @Override
  42. protected void onCreate(Bundle savedInstanceState) {
  43. super.onCreate(savedInstanceState);
  44. setContentView(R.layout.leaf_loading_layout);
  45. initViews();
  46. mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS, 3000);
  47. }
  48. private void initViews() {
  49. mFanView = findViewById(R.id.fan_pic);
  50. RotateAnimation rotateAnimation = DXAnimationUtils.initRotateAnimation(false, 1500, true,
  51. Animation.INFINITE);
  52. mFanView.startAnimation(rotateAnimation);
  53. mClearButton = (Button) findViewById(R.id.clear_progress);
  54. mClearButton.setOnClickListener(this);
  55. mLeafLoadingView = (LeafLoadingView) findViewById(R.id.leaf_loading);
  56. mMplitudeText = (TextView) findViewById(R.id.text_ampair);
  57. mMplitudeText.setText(getString(R.string.current_mplitude,
  58. mLeafLoadingView.getMiddleAmplitude()));
  59. mDisparityText = (TextView) findViewById(R.id.text_disparity);
  60. mDisparityText.setText(getString(R.string.current_Disparity,
  61. mLeafLoadingView.getMplitudeDisparity()));
  62. mAmpireSeekBar = (SeekBar) findViewById(R.id.seekBar_ampair);
  63. mAmpireSeekBar.setOnSeekBarChangeListener(this);
  64. mAmpireSeekBar.setProgress(mLeafLoadingView.getMiddleAmplitude());
  65. mAmpireSeekBar.setMax(50);
  66. mDistanceSeekBar = (SeekBar) findViewById(R.id.seekBar_distance);
  67. mDistanceSeekBar.setOnSeekBarChangeListener(this);
  68. mDistanceSeekBar.setProgress(mLeafLoadingView.getMplitudeDisparity());
  69. mDistanceSeekBar.setMax(20);
  70. mAddProgress = findViewById(R.id.add_progress);
  71. mAddProgress.setOnClickListener(this);
  72. mProgressText = (TextView) findViewById(R.id.text_progress);
  73. mFloatTimeText = (TextView) findViewById(R.id.text_float_time);
  74. mFloatTimeSeekBar = (SeekBar) findViewById(R.id.seekBar_float_time);
  75. mFloatTimeSeekBar.setOnSeekBarChangeListener(this);
  76. mFloatTimeSeekBar.setMax(5000);
  77. mFloatTimeSeekBar.setProgress((int) mLeafLoadingView.getLeafFloatTime());
  78. mFloatTimeText.setText(getResources().getString(R.string.current_float_time,
  79. mLeafLoadingView.getLeafFloatTime()));
  80. mRotateTimeText = (TextView) findViewById(R.id.text_rotate_time);
  81. mRotateTimeSeekBar = (SeekBar) findViewById(R.id.seekBar_rotate_time);
  82. mRotateTimeSeekBar.setOnSeekBarChangeListener(this);
  83. mRotateTimeSeekBar.setMax(5000);
  84. mRotateTimeSeekBar.setProgress((int) mLeafLoadingView.getLeafRotateTime());
  85. mRotateTimeText.setText(getResources().getString(R.string.current_float_time,
  86. mLeafLoadingView.getLeafRotateTime()));
  87. }
  88. @Override
  89. public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
  90. if (seekBar == mAmpireSeekBar) {
  91. mLeafLoadingView.setMiddleAmplitude(progress);
  92. mMplitudeText.setText(getString(R.string.current_mplitude,
  93. progress));
  94. } else if (seekBar == mDistanceSeekBar) {
  95. mLeafLoadingView.setMplitudeDisparity(progress);
  96. mDisparityText.setText(getString(R.string.current_Disparity,
  97. progress));
  98. } else if (seekBar == mFloatTimeSeekBar) {
  99. mLeafLoadingView.setLeafFloatTime(progress);
  100. mFloatTimeText.setText(getResources().getString(R.string.current_float_time,
  101. progress));
  102. }
  103. else if (seekBar == mRotateTimeSeekBar) {
  104. mLeafLoadingView.setLeafRotateTime(progress);
  105. mRotateTimeText.setText(getResources().getString(R.string.current_rotate_time,
  106. progress));
  107. }
  108. }
  109. @Override
  110. public void onStartTrackingTouch(SeekBar seekBar) {
  111. }
  112. @Override
  113. public void onStopTrackingTouch(SeekBar seekBar) {
  114. }
  115. @Override
  116. public void onClick(View v) {
  117. if (v == mClearButton) {
  118. mLeafLoadingView.setProgress(0);
  119. mHandler.removeCallbacksAndMessages(null);
  120. mProgress = 0;
  121. } else if (v == mAddProgress) {
  122. mProgress++;
  123. mLeafLoadingView.setProgress(mProgress);
  124. mProgressText.setText(String.valueOf(mProgress));
  125. }
  126. }
  127. }

layout:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:background="#fed255"
  6. android:orientation="vertical" >
  7. <TextView
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:layout_gravity="center_horizontal"
  11. android:layout_marginTop="100dp"
  12. android:text="loading ..."
  13. android:textColor="#FFA800"
  14. android:textSize=" 30dp" />
  15. <RelativeLayout
  16. android:id="@+id/leaf_content"
  17. android:layout_width="match_parent"
  18. android:layout_height="wrap_content"
  19. android:layout_marginTop="50dp" >
  20. <com.baidu.batterysaverDemo.ui.LeafLoadingView
  21. android:id="@+id/leaf_loading"
  22. android:layout_width="302dp"
  23. android:layout_height="61dp"
  24. android:layout_centerHorizontal="true" />
  25. <ImageView
  26. android:id="@+id/fan_pic"
  27. android:layout_width="wrap_content"
  28. android:layout_height="wrap_content"
  29. android:layout_alignParentRight="true"
  30. android:layout_centerVertical="true"
  31. android:layout_marginRight="35dp"
  32. android:src="@drawable/fengshan" />
  33. </RelativeLayout>
  34. <ScrollView
  35. android:layout_width="match_parent"
  36. android:layout_height="match_parent" >
  37. <LinearLayout
  38. android:layout_width="match_parent"
  39. android:layout_height="match_parent"
  40. android:orientation="vertical" >
  41. <LinearLayout
  42. android:id="@+id/seek_content_one"
  43. android:layout_width="match_parent"
  44. android:layout_height="wrap_content"
  45. android:layout_marginLeft="15dp"
  46. android:layout_marginRight="15dp"
  47. android:layout_marginTop="15dp" >
  48. <TextView
  49. android:id="@+id/text_ampair"
  50. android:layout_width="wrap_content"
  51. android:layout_height="wrap_content"
  52. android:layout_gravity="center_vertical"
  53. android:textColor="#ffffa800"
  54. android:textSize="15dp" />
  55. <SeekBar
  56. android:id="@+id/seekBar_ampair"
  57. android:layout_width="0dp"
  58. android:layout_height="wrap_content"
  59. android:layout_marginLeft="5dp"
  60. android:layout_weight="1" />
  61. </LinearLayout>
  62. <LinearLayout
  63. android:layout_width="match_parent"
  64. android:layout_height="wrap_content"
  65. android:layout_marginLeft="15dp"
  66. android:layout_marginRight="15dp"
  67. android:layout_marginTop="15dp"
  68. android:orientation="horizontal" >
  69. <TextView
  70. android:id="@+id/text_disparity"
  71. android:layout_width="wrap_content"
  72. android:layout_height="wrap_content"
  73. android:layout_gravity="center_vertical"
  74. android:textColor="#ffffa800"
  75. android:textSize="15dp" />
  76. <SeekBar
  77. android:id="@+id/seekBar_distance"
  78. android:layout_width="0dp"
  79. android:layout_height="wrap_content"
  80. android:layout_marginLeft="5dp"
  81. android:layout_weight="1" />
  82. </LinearLayout>
  83. <LinearLayout
  84. android:layout_width="match_parent"
  85. android:layout_height="wrap_content"
  86. android:layout_marginLeft="15dp"
  87. android:layout_marginRight="15dp"
  88. android:layout_marginTop="15dp"
  89. android:orientation="horizontal" >
  90. <TextView
  91. android:id="@+id/text_float_time"
  92. android:layout_width="wrap_content"
  93. android:layout_height="wrap_content"
  94. android:layout_gravity="center_vertical"
  95. android:textColor="#ffffa800"
  96. android:textSize="15dp" />
  97. <SeekBar
  98. android:id="@+id/seekBar_float_time"
  99. android:layout_width="0dp"
  100. android:layout_height="wrap_content"
  101. android:layout_marginLeft="5dp"
  102. android:layout_weight="1" />
  103. </LinearLayout>
  104. <LinearLayout
  105. android:layout_width="match_parent"
  106. android:layout_height="wrap_content"
  107. android:layout_marginLeft="15dp"
  108. android:layout_marginRight="15dp"
  109. android:layout_marginTop="15dp"
  110. android:orientation="horizontal" >
  111. <TextView
  112. android:id="@+id/text_rotate_time"
  113. android:layout_width="wrap_content"
  114. android:layout_height="wrap_content"
  115. android:layout_gravity="center_vertical"
  116. android:textColor="#ffffa800"
  117. android:textSize="15dp" />
  118. <SeekBar
  119. android:id="@+id/seekBar_rotate_time"
  120. android:layout_width="0dp"
  121. android:layout_height="wrap_content"
  122. android:layout_marginLeft="5dp"
  123. android:layout_weight="1" />
  124. </LinearLayout>
  125. <Button
  126. android:id="@+id/clear_progress"
  127. android:layout_width="match_parent"
  128. android:layout_height="wrap_content"
  129. android:layout_marginTop="15dp"
  130. android:text="去除进度条,玩转弧线"
  131. android:textSize="18dp" />
  132. <LinearLayout
  133. android:layout_width="match_parent"
  134. android:layout_height="wrap_content"
  135. android:layout_marginLeft="15dp"
  136. android:layout_marginRight="15dp"
  137. android:layout_marginTop="15dp"
  138. android:orientation="horizontal" >
  139. <Button
  140. android:id="@+id/add_progress"
  141. android:layout_width="wrap_content"
  142. android:layout_height="wrap_content"
  143. android:text="增加进度: "
  144. android:textSize="18dp" />
  145. <TextView
  146. android:id="@+id/text_progress"
  147. android:layout_width="wrap_content"
  148. android:layout_height="wrap_content"
  149. android:layout_gravity="center_vertical"
  150. android:textColor="#ffffa800"
  151. android:textSize="15dp" />
  152. </LinearLayout>
  153. </LinearLayout>
  154. </ScrollView>
  155. </LinearLayout>

最终效果如下,本来录了20+s,但是PS只能转5s,所以有兴趣的大家自己运行的玩吧:

Android 一个绚丽的loading动效分析与实现!

源码****下载地址:http://download.****.net/detail/tianjian4592/8524539