当前位置: 代码迷 >> Android >> Android猎奇宝宝_番外篇_看脸的世界_07
  详细解决方案

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

热度:63   发布时间:2016-04-28 02:19:22.0
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下载

  相关解决方案