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

上一篇番外篇讲了一个炒鸡炒鸡简单的自定义ProgressBar,这一篇基于上一篇的基础扩展为SeekBar,没看过上一篇的,请先看一遍:传送门

先上效果图(2G内存的机子运行模拟器,所以有点卡):

这个效果之前不知道在哪里看到过,我也忘了。

下面进入正题:

测量大小和绘制部分沿用上一篇ProgressBar的,不清楚的请走上面的传送门。

对比上一篇的扩展:

(1)SeekBar能通过触摸改变刻度

(2)SeekBar上方添加一个显示当前刻度的浮动View(后面用FloatView表示)

(1)通过触摸改变刻度:

这个很容易实现,只要处理触摸事件,然后根据触摸坐标修改刻度并请求重绘就行了。

需要注意的一点就是要处理好临界状态,不然可能出现刻度为负值或者最大只能为99的情况。

因为这个很简单,就不再说了,代码会在等一下跟FloatView的实现一起贴出来,因为FloatView也需要触摸事件。

(2)浮动View的实现:

之前在网上看到这个效果时就在思考怎么实现。

刚开始时我想的是通过监听触摸事件,可以计算出FloatView的位置,然后在onDraw把它给画出来。

但是还没开始写我就发现问题了,就是这样的话FloatView就作为SeekBar的一部分,并且它的位置位于SeekBar的范围之外,这样的话FloatView是显示不出来的。

既然有问题,那么就思考解决的办法,我想到三个方法:

(1)动态改变SeekBar的高度,让FloatView可以显示出来。但会引发另一个问题,相邻的View会被挤压,所以该方法不可行。

(2)我记得ViewGroup有个属性可以让子view超过自身的显示范围,我可以在代码中类似这样设置:

((ViewGroup) getParent()).setClipChildren(true);

但这个方法问题更多,首先,你让你的直接父View允许你超过显示范围,但可能你的FloatView显示在你的直接父View的范围外了,这样你必须循环父View的父View,设置所有父View的clipChildren属性,这样可能会影响到页面内其它的View。所以这个方法也不建议采用,我们应该只显示FloatView并且避免在任何布局中对其它View造成影响。

(3)不在onDraw里画,用WindowManager来添加FloatView:

Bingo,就是这个了。用这个方法FloatView不属于SeekBar,甚至不属于这个页面,可以说是属于这个屏幕的,所以不会对页面内的任何View造成影响。更妙的是我们可以显示任何的View,不像在onDraw方法里能画的东西有限(其实在onDraw方法里也是可以画其它的View的,不过处理起来比较麻烦)。

其实我们常用的PopupWindow和现在很多应用都有的桌面悬浮窗都是这种方法。

好了,下面开始讲这种方法的实现,不清楚如何用WindowManager添加FloatView的参考我另一篇博文:传送门

(1)创建一个FloatView:

		// 创建FloatView
		floatView = new TextView(getContext());
		floatView.setGravity(Gravity.CENTER);
		floatView.setBackgroundResource(R.drawable.shape_circle_blue);
		floatView.setTextColor(0XFFFFFFFF);
		floatViewWidth = (int) dp2px(40);
		floatViewHeight = floatViewWidth;

(2)初始化FloatView的LayoutParams:

		// FloatView添加到Window的参数初始化
		floatLP = new LayoutParams();
		floatLP.width = floatViewWidth;
		floatLP.height = floatViewHeight;
		floatLP.gravity = Gravity.LEFT | Gravity.TOP;
		floatLP.format = PixelFormat.RGBA_8888;
		floatLP.windowAnimations = R.style.pppanim;

这里注意看windowAnimations属性,就是为FloatView添加入场和出场的动画效果的,添加的方式跟PopupWindow是一样的(它们都是通过WindowManager添加的),可以看下动画样式:

    <style name="pppanim">
        <item name="android:windowEnterAnimation">@anim/ppp</item>
        <item name="android:windowExitAnimation">@anim/pppout</item>
    </style>

具体的动画可以自己随便定义,Enter代表添加时的动画,Exit代表移除。

(3)开始显示FloatView:

FloatView的显示跟触摸事件挂钩。我们应该在Down事件时往Window中添加FloatView,Move事件时更新FloatView的位置和显示的刻度,在Up事件时从Window中移除FloatView。

注意:往Window中添加两次相同的View和试图移除未添加进Window的View都会产生异常。

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 获得状态栏高度
			statusHeight = getStatusHeight(mContext);
			// 修改刻度
			fontRectF.right = event.getX();
			changeProgress();
			// 修改FloatView显示文字
			floatView.setText(mProgress + "");
			// 修改FloatView的X和Y坐标
			// X坐标=当前触摸的X-FloatView的宽度/2+该ProgressBar的左边坐标
			floatLP.x = (int) event.getRawX() - floatViewWidth / 2;
			// Y坐标=相对屏幕触摸X坐标-前面根据屏幕密度计算出来的垂直间隔-状态栏高度-FloatView的高度
			floatLP.y = (int) event.getRawY() - mFloatVerticalSpacing - statusHeight - floatViewHeight;
			// 将FloatView添加进Window
			wm.addView(floatView, floatLP);
			break;
		case MotionEvent.ACTION_MOVE:
			float newX = event.getX();
			// 临界处理
			if (newX < 0) {
				newX = 0;
			} else if (newX > mWidth) {
				newX = mWidth;
			}
			// 修改刻度
			fontRectF.right = newX;
			changeProgress();
			// 修改FloatView显示文字
			floatView.setText(mProgress + "");
			// 修改FloatView的X坐标
			// 临界处理,只有在触摸在Bar范围内才去更新
			if (event.getRawX() >= getLeft() && event.getRawX() <= getRight()) {
				floatLP.x = (int) event.getRawX() - floatViewWidth / 2;
				// 更新FloatView在window中的位置
				wm.updateViewLayout(floatView, floatLP);
			}
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			// 从window中移除FloatView
			// 在2.3版本模拟器中报IllegalArgumentException,尚未查明原因
			wm.removeView(floatView);
			break;
		}
		// 我们想处理触摸事件,所以这里要返回true,对触摸事件不清楚的,找我另一篇博文
		return true;
	}

具体看注释吧,我写得很详细。这里比较复杂的是计算FloatView的位置和临界处理。

这里要注意getX()和getRawX()的区别:

getX()是相对于这个SeekBar的坐标,getRawX()是相对于屏幕的坐标。

所以当我们计算刻度时,应该用getX()。而计算FloatView的位置时,我们的FloatView是添加进屏幕的而不是添加到SeekBar的,应该使用getRawX(),基本上用WindowManager添加View时,大多数情况下都应该用getRawX()。

发现也没什么好解释的,不懂的话下载Demo自己再研究研究,有点基础的建议直接自己试下实现,原理也很简单。

当然这个控件还有很多地方可以优化,比如:很多属性可以写成通过外部动态设置的,像画笔颜色,FloatView的外观,FloatView的动画,FloatView的垂直间隔。还有2.3模拟器在移除FloatView时不知道为什么报错了,又找不到真机测试,有知道原因的跪求评论指点。

Demo下载

时间: 2024-10-01 06:24:04

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

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