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

简单实现波纹效果

其实这一篇的效果实现很简单,写这篇博客重点是为了说另一件事,剧透一下:有关内存泄露的。

先说下效果的实现:

原理:

原理只有一个,就是Shader的使用。Shader我看别人翻译成着色器,其实它的作用就是为画笔增加颜色的渐变,画笔默认是一个颜色画到底,但是使用Shader可以实现从一个颜色渐变到另一个颜色。

想了解更多关于Shader的姿势,推荐博客:传送门

有了Shader,就能很简单的画出波纹的效果了,至于动画效果,只是动态改变画的大小而已。

高清源码:

(1)初始化

重写构造方法:

	public BoWenView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

进行必要的初始化:

	private void init() {
		// 画笔初始化
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		// 发送消息,开始动画循环
		mHandler.sendEmptyMessage(0);
	}

下面是一些需要View大小作为参数的初始化,我是重写了onSizeChanged方法,该方法顾名思义就是View的大小被改变时会被调用,第一次加入View树时也会被调用,只是此时的旧值都为0而已:

	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		// 计算半径=较长边的1/12加1
		radius = w > h ? w / 12 + 1 : h / 12 + 1;
		// 计算中心坐标
		cX = w / 2;
		cY = h / 2;
		// 初始化环形着色器
		radialGradient = new RadialGradient(w / 2, h / 2, radius, 0X88FFFFFF, 0XAAFFFFFF, TileMode.REPEAT);
		mPaint.setShader(radialGradient);
		// 初始化中心图像的位置
		bitmapRectF = new RectF(cX - radius, cY - radius, cX + radius, cY + radius);
	}

要实现我们的效果需要用的是环形着色器,另外还有线性的和圆形的,更多内容可以查看上面推荐的博客。

简单说下RadialGradient的构造参数:

RadialGradient(x, y, h, color0, color1, tile)

x和y为圆心坐标,h为渐变的半径,这里设置为我们计算出来的小半径,配合我们设置了TileMode.REPEAT模式,才会有上面一圈一圈的效果,如果h设置成了View的宽或者高,那么显示出来的只有一圈。至于TileMode有3种模式,有兴趣的可以试下不同模式下效果的区别。color0是开始颜色,会渐变到终止颜色color1。这里都是设成白色,不过透明度不同。

(2)中心图像

提供一个方法给外部设置要显示的中心图像,然后再onDraw方法里画出来就行了,至于位置已经在前面初始化时计算出来了:

	private Bitmap centerBitmap;

	public void setCenterBitmap(Bitmap bitmap) {
		centerBitmap = bitmap;
		invalidate();
	}

(3)动画实现

前面说过动画只是动态改变画的圆大小而已,这里用一个Handler来循环发送消息:

	Handler mHandler = new Handler(new Handler.Callback() {
		@Override
		public boolean handleMessage(Message msg) {
			Log.e("Bowen", "handleMessage");
			if (scale >= 6) {
				scale = 0;
			}
			scale++;
			invalidate();
			mHandler.sendEmptyMessageDelayed(0, 500);
			return false;
		}
	});

通过scale的值来控制要画的圆的大小。

(4)开始画

	protected void onDraw(Canvas canvas) {
		//画圆,根据sacle来控制大小
		canvas.drawCircle(cX, cY, cX > cY ? cX * scale / 6 : cY * scale / 6, mPaint);
		//有设置图像的话就在中间画出来
		if (centerBitmap != null) {
			canvas.drawBitmap(centerBitmap, null, bitmapRectF, null);
		}
	}

好了,写完了,放到Activity实验一下:

	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		//设置中心图像
		((BoWenView) findViewById(R.id.bowen)).setCenterBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.dun));
	}

效果出来了,万事大吉,撸两盘然后睡觉去了。

手机还显示着Demo的Activity,点击Home键返回桌面。就在这时,恐怖的事情发生了,Logcat还一直打印着handleMessage方法里的打印信息。这意味着Handler没有被回收,Handler是BowenView的成员变量,Handler无法被回收意味着BowenView也无法被回收,而BowenView又拥有Activity的引用,意味着整个Activity在应用被真正关闭之前(即进程被销毁之前)都无法回收,这TM的不就是内存泄露嘛,我居然造了一颗炸弹。

一个常犯的错误:

其实在Activity中使用Handler一样有这个问题,发生这个问题的原因是:

先理解下面两句话再继续往下看:

(1)基于垃圾回收规则:只回收没有引用的对象。

(2)在Java中:非静态(匿名)内部类会引用外部类对象。

当你发送一个延时消息时,这个消息会被添加到一个全局的消息队列中,当时间到达要延时的时间时,这个消息会被发送会给我们的Handler。(对该Handler机制不熟悉的,请参考我另一篇博客:传送门

如果在中间过程中,我们退出了Activity,那么当垃圾收集器准备回收这个Activity时,发现在全局消息队列中还有一个消息里有着一个指向该Handler的引用,于是该Handler无法被回收。

如果是像上面那种情况,Handler是View的成员变量,即Handler引用了View,而View引用了Activity,Activity引用了一大把东西,所以有一大把东西会占着内存,而且我们还访问不到,垃圾收集器也回收不了。

如果是在Activity中使用Handler也是同样的道理,即Handler为Activity的非静态内部类,即Handler引用了Activity,即Handler的无法回收同样将导致Activity的无法回收。

如果这个Activity很占内存,那么内存泄露堆积,就很容易造成OOM,而且原因也很难排查得到。

(从破坏的角度看,我做的还是挺不错的)

解决方法:

其实我的发现是基于这篇博客:传送门

这篇博客也提供了一种解决方法,原理是将Handler写为静态类,并且在Handler内部保存了一个Activity的软引用。

下面是我的解决方法:

(1)在View中使用Handler:

可以在View类中重写onWindowVisibilityChanged方法,当Activity所属的窗口变为不可见时,从消息队列中移除我们发送的消息,代码修改如下:

	protected void onWindowVisibilityChanged(int visibility) {
		super.onWindowVisibilityChanged(visibility);
		if (visibility == View.GONE) {
			Log.e("Bowen", "Window-GONE");
			mHandler.removeMessages(0);
		} else if (visibility == View.VISIBLE) {
			Log.e("Bowen", "Window-VISIBLE");
			mHandler.sendEmptyMessage(0);
		}
	}

记得把前面在init方法中发送消息的语句去掉:

	private void init() {
		// 画笔初始化
		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		// 发送消息,开始动画循环
		//mHandler.sendEmptyMessage(0);
	}

(2)在Activity中使用Handler:

与上面类似,重写onStop方法,在onStop方法中移除消息。

//我就是传说中的
//略...

Demo下载

时间: 2024-10-13 22:23:58

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

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好奇宝宝_番外篇_看脸的世界_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好奇宝宝_番外篇_看脸的世界_07

废话少说,先上图: (请看底部的4个点) 忘记是在那个APP上看到ViewPager底部的圆点指示器可以随着滚动而滚动的效果,便开始思考要怎么实现,最终发现效果实现很简单,拿来练手自定义View挺不错的. 写码之前: 写代码之前必须至少先有大概的思路,而且不要想到一点就开始写,必须对整体都大概心里有数再开始写.比如在实现这个效果时,刚开始我是想着重写线性布局,然后动态添加圆点,通过margin控制间隔.但是我发现这种办法在滚动时的处理逻辑编写起来比较复杂,既然只是几个圆点而已,直接继承View用

步步为营_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