Android好奇宝宝_番外篇_看脸的世界_07

废话少说,先上图:

技术分享

(请看底部的4个点)


忘记是在那个APP上看到ViewPager底部的圆点指示器可以随着滚动而滚动的效果,便开始思考要怎么实现,最终发现效果实现很简单,拿来练手自定义View挺不错的。


写码之前:

写代码之前必须至少先有大概的思路,而且不要想到一点就开始写,必须对整体都大概心里有数再开始写。比如在实现这个效果时,刚开始我是想着重写线性布局,然后动态添加圆点,通过margin控制间隔。但是我发现这种办法在滚动时的处理逻辑编写起来比较复杂,既然只是几个圆点而已,直接继承View用画的方式画出来更简单。最终写出来的类加上一大把自动生成的代码,也才一百多行。

写代码并不难,想到正确的思路才难。


高清源码:


(1)初始化

	public AnimDian(Context context, AttributeSet attrs) {
		super(context, attrs);
		TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.AnimDian);
		dianCount = array.getInteger(R.styleable.AnimDian_dian_count, 5);
		dianColor = array.getColor(R.styleable.AnimDian_dian_color, 0XFFFF0000);
		dianBgColor = array.getColor(R.styleable.AnimDian_dian_bg_color, 0X88FFFFFF);
		margin = array.getInteger(R.styleable.AnimDian_dian_margin, 20);
		dianSize = array.getInteger(R.styleable.AnimDian_dian_size, 20);
		array.recycle();
		init();
	}


一些自定义属性可以在xml中设置,关于自定义属性的教学网上一大把,我就不赘述了。

	private void init() {
		// 初始化两支画笔
		dianPaint = new Paint();
		dianPaint.setAntiAlias(true);
		dianPaint.setColor(dianColor);
		bgPaint = new Paint();
		bgPaint.setAntiAlias(true);
		bgPaint.setColor(dianBgColor);
	}


初始化两支画笔,一支画显示选中的前景点,一支画背景点。


(2)测量大小

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// 测量自身大小
		// 宽=点的宽度(直径)*点的个数+点之间的距离*(点的个数-1)
		int width = dianSize * dianCount + margin * (dianCount - 1);
		// 高=点的高度(直径)
		int height = dianSize;
		int wMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
		int hMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
		// 设置计算结果
		setMeasuredDimension(wMeasureSpec, hMeasureSpec);
	}


这个View的大小测量很简单,我也不浪费口水了。


(3)画

写一个自定义View是一个试验的过程,很少有一次性过把整个效果写完的,这里我们先考虑静态的,即没有滚动效果的该怎么写,然后再考虑怎么加上滚动效果。

先是静态的实现:

	public void draw(Canvas canvas) {
		// 画背景点
		for (int i = 0; i < dianCount; i++) {
			drawBgDian(canvas, i);
		}
		//画选中状态的点
		if (selectPosition > -1) {
			drawDian(canvas);
		}
	}


静态的实现很简单,就是把几个背景的点画出来,再在选中位置的地方再画一个不同颜色的点遮盖住背景点。

注意:后画的东西会遮挡住先画的。


这里稍微复杂一点的就是位置的计算,不过只要认真思考现在已有的数据,以及是否有什么规律,画点草图,一般都不难解决。


画背景点:

	private void drawBgDian(Canvas canvas, int i) {
		canvas.drawCircle((dianSize + margin) * i + dianSize / 2, dianSize / 2, dianSize / 2, bgPaint);
	}

圆心的Y坐标容易算,但X坐标需要画点草图想一下,让你的大脑动一下吧。


画选中状态的点跟背景点一样,只是只画一个,且用了不用颜色的画笔而已:

	private void drawDian(Canvas canvas) {
		canvas.drawCircle((dianSize + margin) * selectPosition + dianSize / 2, dianSize / 2, dianSize / 2, dianPaint);
	}

就这样,静态效果已经完成了。现在开始思考怎么实现滚动效果:

首先,要让红点滚动,必须有滚动的数据,比如滚动的方向,滚动的距离。于是得先得到滚动的数据来源。

因为我们是用在ViewPager上的,所以很容易想到给ViewPager设置OnPageChangeListener,再把数据传给我们的AnimDian:

		public void onPageScrolled(int arg0, float arg1, int arg2) {
			animDian.onPageScrolled(arg0, arg1, arg2);
		}

接下来就得搞清楚3个参数的含义,打印一下数值,得出结果是:

技术分享

技术分享

小结:

这里的返回参数并不受滑动方向的影响,一直以左边作为基准,在滑动时会有两个page显示在屏幕上:

arg0:左边page的index

arg1:左边page没有显示出来的部分的百分比,或者理解为右边page显示出来的部分的百分比

arg2:同arg1,不过是具体的像素值

我们这里只需要arg0和arg1,像素值我们用不着。

有了滚动的状态数据,就可以计算滚动点的位置了:

	public void onPageScrolled(int arg0, float arg1, int arg2) {
		if (arg1 > 0 && arg1 < 1) {
			scrollState = STATE_SCROLLING;
			// 滚动时计算前景点距离左边的距离
			scrollDianCX = (dianSize + margin) * (float) (arg0 + arg1) + dianSize / 2;
			invalidate();
		}
	}

知道参数含义后怎么计算位置,还是需要我们再动下脑。


得到位置后就可以画出滚动点了:

	private void drawScrollDian(Canvas canvas) {
		canvas.drawCircle(scrollDianCX, dianSize / 2, dianSize / 2, dianPaint);
	}

当然在滚动时,不应该画出选中位置的点,所以修改draw方法逻辑:

	public void draw(Canvas canvas) {
		// 画背景点
		for (int i = 0; i < dianCount; i++) {
			drawBgDian(canvas, i);
		}
		// 如果不是在滚动状态,画选中位置的前景点
		if (selectPosition > -1 && scrollState != STATE_SCROLLING) {
			drawDian(canvas);
		}
		// 在滚动状态,画滚动点
		if (scrollState == STATE_SCROLLING) {
			drawScrollDian(canvas);
		}
	}

然后在滚动结束和选中位置发生改变时,同样要通知我们的AnimDian:

		public void onPageScrollStateChanged(int arg0) {
			//arg0==0表示滚动状态为结束
			if (arg0 == 0) {
				animDian.onPageScrollEnd();
			}
		}
		public void onPageSelected(int arg0) {
			animDian.setSelectPosition(arg0);
		}

在滚动结束时要修改AnimDian的滚动状态:

	public void onPageScrollEnd() {
		scrollState = STATE_READY;
		invalidate();
	}

同样,在选中位置改变时也要进行处理:

	public void setSelectPosition(int selectPosition) {
		this.selectPosition = selectPosition;
		//外部可以设置监听选中位置的改变,一般用不着,直接监听ViewPager的就行了
		if (mListener != null)
			mListener.onSelectChange(selectPosition);
		//如果当前正在滚动,就没必要请求重绘了,等到滚动结束后会去请求重绘的
		if (scrollState == STATE_SCROLLING)
			return;
		invalidate();
	}

至此,这个自定义View就完成了。虽然有点简单,但是作为新手刚开始还是不要挑战太复杂的自定义View,先写点简单的找点成就感和自信,多思考和理解下原理。


准备回家过年了!!!


DEMO下载

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。