自定义View——旋转小球Loadding

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_16674697/article/details/51965411

先说说这篇博客的由来,最近群里有个朋友问了一下这种效果怎么做?然后群里炸开了锅,有个很厉害的小伙子甚至做到了凌晨一点,不过最后效果实现的也很赞,废话不多说,先上目标效果图以及本博客实现效果:



总的来说,看到这样一个效果,作为一个安卓开发者而言,并不陌生,因为在当今APP中这种效果已经烂大街了,具体实现思路是什么呢?至少我第一眼看到,会认为是加载了一连串图片,即帧动画,想了几秒钟,决定使用帧动画看一下效果,效果和demo中的差不多,只不过因为图片分辨率的原因,显示较为模糊(哈哈哈,我直接扣得gif图),不过这种方法的优点和缺点也很明显,首先,效果肯定不会很流畅(加载了50张图),其次,gif会重复绘制,在这里不多说这种方案,不过有些不会的朋友,可以自行下载demo~

好了,废话不多说,下面来看一下自定义View的实现吧:


实现步骤主要包括:


  • 继承View并初始化参数
  • 绘制三个小球
  • 计算小球运动轨迹并更改半径
  • 运动状态初始化


继承View并初始化参数


首先,我们的LoaddingView继承View并重写onMearsure测量宽高,那么在测量宽高前,我们需要哪些参数呢?


minRadius:左圆小圆半径

ballPadding:三个小球之间的间距

maxRadius:中间大圆的半径,这个可以根据小圆半径来设,比如minRadius * 1.5f小圆的一点五倍

minMeasureHeight、minMeasureWidth:控件最小默认宽高


好了,初始化这些参数后,我们来计算要显示一个完整的控件所需要的宽高,想想最小宽是多少?

最小宽=小球直径*2+大球直径+小球间距*2;

最小高=大球直径;

即:

minMeasureWidth=minRadius*2*2+maxRadius*2+ballPadding*2;

minMeasureWidth=maxRadius*2;


好了,知道了公式再来看代码,就容易了很多:

<span style="white-space:pre">	</span>/**
	 * 根据最小圆半径初始化相应数值
	 */
	public void calculateSize() {
		ballPadding = (int) minRadius * 2;// 球间距设为小圆两倍
		maxRadius = minRadius * 1.5f;// 大球半径为小球半径1.5倍
		minMeasureWidth = (int) (4 * minRadius + 2 * ballPadding + 2 * maxRadius);
		minMeasureHeight = (int) (maxRadius * 2);
	}


这个函数在初始化中调用,接下来,我们来看onMearsure的代码:

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		width = this.getMeasuredSize(widthMeasureSpec, true);
		height = this.getMeasuredSize(heightMeasureSpec, false);
		setMeasuredDimension(width, height);
	}

	/**
	 * 计算控件的实际大小
	 * 
	 * @param length
	 *            onMeasure方法的参数,widthMeasureSpec或者heightMeasureSpec
	 * @param isWidth
	 *            是宽度还是高度
	 * @return int 计算后的实际大小
	 */
	private int getMeasuredSize(int length, boolean isWidth) {
		// 模式
		int specMode = MeasureSpec.getMode(length);
		// 尺寸
		int specSize = MeasureSpec.getSize(length);
		// 计算所得的实际尺寸,要被返回
		int retSize = 0;
		// 对不同的指定模式进行判断
		if (specMode == MeasureSpec.EXACTLY) {
			// 如果制定大小,则半径也随之变小,宽高重新测量
			if (isWidth) {
				minRadius = specSize / 11;
				calculateSize();
			}
			retSize = specSize;
		} else { // 如使用wrap_content
			retSize = (int) (isWidth ? minMeasureWidth : minMeasureHeight);
		}

		return retSize;
	}

注释很详细,如果用户在xml中设置了固定宽高,则根据所设的宽值,动态改变小球半径,从而实现适配,如果设置了wrap_content,则按照我们默认的小球半径来返回宽高,其中

minRadius=specSize/11;

这里为什么是11呢?其实这里的11是这样得来的:

大球半径=小球半径*1.5;

球间距=小球半径*两倍;

那么:

minMeasureWidth=minRadius*2*2+maxRadius*2+ballPadding*2;

可以换算为:

minMeasureWidth=(minRadius*4)+minRadius*3+4*minRadius;

即:

minMeasureWidth=minRadius*11;

那么:

minRadius=minMeasureWidth/11;


其实这里我使用11并不规范,如果用户想自己设置球间距或者大球和小球比例的话,就很容易出问题,所以这里按照规范应该是:

minRadius=(minMeasureWidth-maxRadius*2-ballPadding*2)/4;

但是代码里如果把/11换成这个公式的话你会发现,咦~宽度怎么不对,怎么变形了,是的,起初我找这个问题也花了一点点时间,后来想通后,一拍大脑(博主比较笨别太介意),maxRadius是跟随minRadius变化的,我们改变minRadius而maxRadius不改变,所以计算出来的值会完全不对,所以这里按道理的解决方案是,把minRadius和maxRadius(ballPadding也一样)的关系比例设为可设值,或者直接把maxRadius(ballPadding也一样)提出让用户自己设值大小,这样就可以完美了,在这里,博主就省点事就没改了,相信使用者如果有这类需求的话肯定会比我更快的发现问题吧哈哈~


绘制三个小球以及运动变化


在绘制前我们得需要一些绘制的必要参数:

leftPaint、centerPaint、rightPaint:不多说,肯定需要三个球的画笔

mInitX:小球距离左边X轴零点的偏移量,用于重绘轨迹

offset:小球从左边到中间运动的offset值,即[0,1];有了这些,咱们看代码实现:

 
 

是不是看起来很复杂,没关系,咱们慢慢的理顺思路:

首先,得弄懂mInitX,mInitX是一个变化值,offset的变化全部依仗于它,说白了,就是左圆向中间圆的圆心偏移量。

mInitX∈[0,(minMeasureWidth/2-maxRadius)];

即小球从左边到中间的偏移量

offset:当小球从左边运动到中间的时候,应该用已有的路径/总路径

也就是说,如果我的总路径是总宽-一个小球的直径,那么它的偏移量的两倍/这个总量,不就是每移动一次的偏移量么

知道了这个再看代码:

offset = (mInitX * 2) / (minMeasureWidth - 2 * minRadius);
canvas.drawCircle(mInitX + minRadius, maxRadius,
					(maxRadius - minRadius) * offset + minRadius, leftPaint);
			canvas.drawCircle(mInitX + minMeasureWidth / 2, maxRadius,
					maxRadius - (maxRadius - minRadius) * offset, centerPaint);
			canvas.drawCircle((minMeasureWidth - 2 * minRadius) * (1 - offset),
					maxRadius, minRadius, rightPaint);
 
 

就一目了然了。左圆和中间大圆的运动轨迹很好计算:

左圆圆心坐标应该是=mInitX + minRadius, maxRadius);

左圆半径=(maxRadius - minRadius) * offset + minRadius;大圆减去小圆的差值乘以变化率+原来的半径,就是增大的半径

大圆圆心坐标应该是=(mInitX + minMeasureWidth / 2 maxRadius);

大圆半径=maxRadius - (maxRadius - minRadius) * offset;和左圆相反,为相减

右圆的运动轨迹相对于复杂些:

首先分析一下,右圆要移动到左圆位置,半径大小不变,但是移动轨迹增加,根据offset的计算可以得到要移动的距离,即

x=(minMeasureWidth - 2 * minRadius) * (1 - offset);

看完公式,其实思路也就清晰了,就是所有轨迹*offset的相反偏移量分析完三个小球的运动轨迹,我们只要改变mInitX 就可以动态的更新视图,达到运动效果,怎么样,是不是很有意思~

小球运动结束后初始化

那么如何让小球重新开始呢?读者在看到这里时,应该有个疑问,因为单独的初始化操作,肯定小球的颜色又归位了,我上下效果图:

很显然,这个效果离我们的的目标效果相差还有点,毕竟重新归位后,颜色也跟着变化了,所以我们还得做一个操作,修改paint的颜色,每次把下一个paint的颜色转化到下下个,这样就可以实现循环效果了,但是想想代码,好像颜色的转化并不是那么容易(也许博主没有想到),能不能在paint上做手脚,恩~然后就产生了如下代码:

if (lock) {
			int def = (int) (minMeasureWidth / 2 - maxRadius);
			mInitX = mInitX + duration;
			if (mInitX > def) {
				Paint dPaint = new Paint();
				dPaint = leftPaint;
				leftPaint = rightPaint;
				rightPaint = centerPaint;
				centerPaint = dPaint;
				mInitX = 0;
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

			invalidate();
		}

擦~这么暴力,直接paint转化,对,就是这么暴力这么任性~~介绍一下def,自然是左圆到中间圆运动的全部轨迹。对了,还有暂停,逆时针什么的,其实逆时针更简单,只要调换一下代码顺序,就可以达到不同的覆盖效果,当然,Paint中也有对应的方法,不过既然有简单的,就别整太复杂了哈哈,到此~整个效果已经全部实现!


总结:


虽说功能很小,但是运用到的运动逻辑思维很重要,本篇博客和上一篇网易颜色渐变效果指示器中都运用到了同一个思路,叫做水平偏移量,是的,很多重绘的位置转换都是通过偏移量重绘来的,所以拥有这个思维,几乎很多复杂的运动体都可以简单的换算成偏移亮转化,好了,到此,本篇博客正式结束,感谢读者支持,谢谢~~大笑




下载地址:http://download.csdn.net/detail/qq_16674697/9579827

作者:yangpeixing

QQ群:251664830 欢迎大神小白们加入~

转载请注明出处~谢谢~








猜你喜欢

转载自blog.csdn.net/qq_16674697/article/details/51965411