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下载

时间: 2024-10-12 08:01:43

Android好奇宝宝_番外篇_看脸的世界_07的相关文章

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

上一篇番外篇讲了一个炒鸡炒鸡简单的自定义ProgressBar,这一篇基于上一篇的基础扩展为SeekBar,没看过上一篇的,请先看一遍:传送门 先上效果图(2G内存的机子运行模拟器,所以有点卡): 这个效果之前不知道在哪里看到过,我也忘了. 下面进入正题: 测量大小和绘制部分沿用上一篇ProgressBar的,不清楚的请走上面的传送门. 对比上一篇的扩展: (1)SeekBar能通过触摸改变刻度 (2)SeekBar上方添加一个显示当前刻度的浮动View(后面用FloatView表示) (1)通

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

废话少说,先上效果图: (左侧的图片是我用window画图软件1分钟画的,所以就不要嫌丑了,You can you up no bb.) 这是我发过最挫的效果图了,不过这是由于没有图片素材导致的,就不要在意这些细节了,知道实现原理后完全可以发挥你的想象去实现更美观的效果. 这个效果也是有开源库的,不过我又把名字给忘了,不过我记得原理,于是就试着自己写了一下. 其实原理很简单,我在另一篇博客(一个有吃豆人删除动画的ListView)也说过了,这一篇当做兑换那些年少轻狂不更事时许下的诺言(是不是瞬间

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

简单实现波纹效果 其实这一篇的效果实现很简单,写这篇博客重点是为了说另一件事,剧透一下:有关内存泄露的. 先说下效果的实现: 原理: 原理只有一个,就是Shader的使用.Shader我看别人翻译成着色器,其实它的作用就是为画笔增加颜色的渐变,画笔默认是一个颜色画到底,但是使用Shader可以实现从一个颜色渐变到另一个颜色. 想了解更多关于Shader的姿势,推荐博客:传送门 有了Shader,就能很简单的画出波纹的效果了,至于动画效果,只是动态改变画的大小而已. 高清源码: (1)初始化 重写

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

一个有吃豆人删除动画的ListView 这是一个无聊的效果,由一个无聊的程序猿,在无聊的情况下写的. 虽然这效果不中看中用,不过就当学习了. 先上图 效果一目了然,主要是: (1)移除item时执行吃豆人动画 (2)滚动时吃豆人也相应移动 (3)应对可见与不可见状态间的切换 简单原理分析: (1)吃豆人.豆.和左边的白色矩形(当然所有颜色都是可以改的,你想换成图片也行)都是用canvas画出来的. (2)问:canvas那里来的?答:ListView的canvas.具体是重写ListView的这

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

无聊刷帖看到一个求助,试着写了一下. 一个自定义Switch控件,附带动画效果. 说是控件,其实是一个布局容器,先上效果图: 先讲原理,再看高清源码. 原理: 好像没啥原理,汗... 跟其它自定义容器控件一样,一般要注意: (1)计算好大小,宽度和高度 (2)计算好子View的布局位置 不是一般要注意的: (3)动画是用的nineoldandroids (4)遮挡效果是通过控制子View的绘制顺序 高清源码: (1)计算大小: protected void onMeasure(int width

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

这一篇是记录一下本猿自定义View的一般思路,通过一个炒鸡简单的自定义ProgressBar讲解一些自定义View的基础知识.适合新手,高手勿喷,有好的指点和想法的欢迎评论. (1)确认需求 写一个自定义View,首先你要确定需求是什么.一般包括外观,事件处理,动画效果. 外观需求:ProgressBar的外观需求非常简单,就是两个矩形(当然也可以是其它形状,这里我们只实现基础的矩形)重叠显示.其中一个固定大小的当背景,一个可变宽度的来显示刻度. 事件处理:ProgressBar不是SeekBa

步步为营_Android开发课_番外篇[5]_软件的安装与卸载源码

Focus on technology, enjoy life!-- 杨焕州 QQ:804212028 原文链接:http://blog.csdn.net/y18334702058/article/details/44624305 本文可能存在参考或借助部分外界资源,如有任何侵权行为,请与我联系! 主题:软件的安装与卸载源码 从SDcard安装软件: String fileName = Environment.getExternalStorageDirectory() + "/myApp.apk

老子道德经全文_番外篇

道德经 <道德经>全文 [第一章]道可道,非常道:名可名,非常名.无名天地之始,有名万物之母.故常无欲,以观其妙:常 有欲,以观其徼(jiào).此两者同出而异名,同谓之玄,玄之又玄,众妙之门.[译文] [第二章]天下皆知美之为美,斯恶(è)已:皆知善之为善,斯不善已.故有无相生,难易相成,长短相 较,高下相倾,音声相和(hè),前后相随.是以圣人处无为之事,行不言之教,万物作焉而不辞,生而 不有,为而不恃,功成而弗居.夫(fú)唯弗居,是以不去. [译文] [第三章]不尚贤,使民不争:不贵难

H5学习_番外篇_PHP数据库操作

1. 文件操作 1.1 打开关闭文件 fopen() resource fopen ( string filename, string mode [, bool use_include_path [, resource zcontext]] )? fopen()函数将resource绑定到一个流或句柄.绑定之后.脚本就能够通过句柄与此资源交互; 例1:以仅仅读方式打开一个位于本地server的文本文件 $fh = fopen("test.txt", "r"); 例2