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

无聊刷帖看到一个求助,试着写了一下。

一个自定义Switch控件,附带动画效果。

说是控件,其实是一个布局容器,先上效果图:

先讲原理,再看高清源码。

原理:

好像没啥原理,汗...

跟其它自定义容器控件一样,一般要注意:

(1)计算好大小,宽度和高度

(2)计算好子View的布局位置

不是一般要注意的:

(3)动画是用的nineoldandroids

(4)遮挡效果是通过控制子View的绘制顺序

高清源码:

(1)计算大小:

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// 初始化,只调用一次
		if (!isInit) {
			childLeft = getChildAt(0);
			childRight = getChildAt(1);
			childLeft.setOnClickListener(leftClickListener);
			childRight.setOnClickListener(rightClickListener);
			measureChild(childLeft, widthMeasureSpec, heightMeasureSpec);
			measureChild(childRight, widthMeasureSpec, heightMeasureSpec);
			// 记录childLeft和childRight的宽高
			leftChildW = childLeft.getMeasuredWidth();
			leftChildH = childLeft.getMeasuredHeight();
			rightChildW = childRight.getMeasuredWidth();
			rightChildH = childRight.getMeasuredHeight();
			// 初始化动画
			smallToBig = ObjectAnimator.ofFloat(null, "scaleY", new float[] { 0.8f, 1 });
			bigToSmall = ObjectAnimator.ofFloat(null, "scaleY", new float[] { 1, 0.8f });
			leftToRightL = ObjectAnimator.ofFloat(childLeft, "translationX", new float[] { 0, leftChildW / 2 });
			rightToLeftL = ObjectAnimator.ofFloat(childLeft, "translationX", new float[] { 0 });
			leftToRightR = ObjectAnimator.ofFloat(childRight, "translationX", new float[] { 0, rightChildW / 2 });
			rightToLeftR = ObjectAnimator.ofFloat(childRight, "translationX", new float[] { 0 });
			animatorSet = new AnimatorSet();
			animatorSet.addListener(mAnimatorListener);
			isInit = true;
		}
		// 宽度为两个child宽度相加
		widthMeasureSpec = MeasureSpec.makeMeasureSpec(leftChildW + rightChildW, MeasureSpec.EXACTLY);
		// 高度为两个child中较高的那个
		heightMeasureSpec = MeasureSpec.makeMeasureSpec(leftChildH > rightChildH ? leftChildH : rightChildH,
				MeasureSpec.EXACTLY);
		setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
	}

看起来有点长,其实真正计算大小的就几句,其它的时其它东西的初始化,因为有些东西(比如动画)初始化需要子View的宽高,所以也放在了这里,我也不知道要放哪里好,加一个boolean变量isInit来控制动画之类的东西只会进行一次初始化,onMeasure和onLayout的调用频率非常高,应该避免在这两个方法内进行大量重复new之类的操作。

计算大小没什么好说的,主要注意一个自定义View时经常用到的方法:

			measureChild(childLeft, widthMeasureSpec, heightMeasureSpec);
			measureChild(childRight, widthMeasureSpec, heightMeasureSpec);

measureChild方法可以让你手动去测量一下child,得到child的测量宽高。

注意是测量宽高,不是最终的真实宽高,getMeasuredWidth()和getWidth()是不同的。

(2)布局子View

	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		switch (currentState) {
		// 两个child的移动是通过动画,并不需要在这里进行特别处理,注意动画的参数就行了
		case STATE_LEFT_ON_TOP:
		case STATE_RIGHT_ON_TOP:
			childLeft.layout(0, 0, leftChildW, leftChildH);
			childRight.layout(leftChildW - rightChildW / 2, 0, leftChildW + rightChildW / 2, rightChildH);
			break;
		}
		// 这里是初始时的状态判断并初始化显示
		if (!isInit2) {
			if (currentState == STATE_LEFT_ON_TOP) {
				// 只需要把childRight变小
				bigToSmall.setTarget(childRight);
				bigToSmall.start();
			} else {
				// 把childLeft变小并且两个都向右移
				bigToSmall.setTarget(childLeft);
				animatorSet = null;
				animatorSet = new AnimatorSet();
				animatorSet.addListener(mAnimatorListener);
				animatorSet.playTogether(bigToSmall, leftToRightL, leftToRightR);
				animatorSet.start();
			}
			isInit2 = true;
		}
	}

布局也没什么好说的,注释写得也清楚。

像注释说得,两种状态下并不用分情况进行布局,始终是靠左进行布局就行了,移动是动画做的事情,多试几遍动画的参数,看看那个行就OK了。

这里isInit2同理isInit,防止重复。

if(isInit2)里的内容是修改初始时的显示,如果没有这个,不管什么状态,初始时都是这样的:

(3)动画实现

	public void changeState() {
		if (!isAniming) {
			if (currentState == STATE_RIGHT_ON_TOP) {
				currentState = STATE_LEFT_ON_TOP;
				if (mStateChangeListener != null) {
					mStateChangeListener.onStateChange(currentState);
				}
				smallToBig.setTarget(childLeft);
				bigToSmall.setTarget(childRight);
				animatorSet.playTogether(smallToBig, bigToSmall, rightToLeftL, rightToLeftR);
				animatorSet.start();
			} else {
				currentState = STATE_RIGHT_ON_TOP;
				if (mStateChangeListener != null) {
					mStateChangeListener.onStateChange(currentState);
				}
				smallToBig.setTarget(childRight);
				bigToSmall.setTarget(childLeft);
				animatorSet.playTogether(smallToBig, bigToSmall, leftToRightL, leftToRightR);
				animatorSet.start();
			}
		}
	}

这里动画用了nineoldandroids来实现。

前面在onMeasure方法里已经进行了动画的初始化,这里只要判断一下要执行那些动画,然后start()就行了。

比较难的是动画初始化时的参数,我的经验就是“试”,多试几次就行了。

(4)遮挡效果

	@Override
	protected int getChildDrawingOrder(int childCount, int i) {
		switch (currentState) {
		case STATE_LEFT_ON_TOP:
			// childLeft在上,需要先draw childRight再draw childLeft
			// 后draw的会覆盖先draw的,这样childLeft才会在上层
			if (i == 0) {
				return 1;
			} else {
				return 0;
			}
		default:
			return i;
		}
	}

这个方法是重写父类ViewGroup的,重写这个方法可以控制child的绘制顺序,记住后绘制的会遮挡住先绘制的。

注意这个方法要生效要先调用另一个方法:

setChildrenDrawingOrderEnabled(true);// 开启有序绘制child

利用ViewGroup这个特性可以实现挺多有趣效果的,比如多个child相互遮挡,点击那个就顶层显示,也可以不断切换绘制顺序来实现类似轮播的效果。

这样,一个自定义Switch控件就完成了。

前面说过,其实这个是个布局容器。最前面的效果图就是我放了两个TextView在里面形成的效果,放其它View也是可以的。

    <view
        android:id="@+id/switchView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        class="jjj.demo.switchviewdemo.SwitchView" >

        <TextView
            android:layout_width="60dp"
            android:layout_height="40dp"
            android:background="@drawable/shape_corner_red"
            android:gravity="center"
            android:text="ON"
            android:textColor="#ffffff"
            android:textSize="16sp" />

        <TextView
            android:layout_width="60dp"
            android:layout_height="40dp"
            android:background="@drawable/shape_corner_blue"
            android:gravity="center"
            android:text="OFF"
            android:textColor="#ffffff"
            android:textSize="16sp" />
    </view>

不知道为什么自定义控件在低版本系统上用下面这种方式经常不能正常显示:

<jjj.demo.switchviewdemo.SwitchView ></jjj.demo.switchviewdemo.SwitchView>

所以只能用上面<view ></view>这种方式,不知道只是模拟器的问题还是真机也会。

如果不要位移动画的话实现起来会更简单一点,对自定义控件不熟悉的可以去试着写一下。

感觉写得问题挺多的,有好的建议跪求指点评论啊。

Demo下载

时间: 2024-10-16 04:39:06

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

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