无聊刷帖看到一个求助,试着写了一下。
一个自定义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时经常用到的方法:
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();
}
}
}前面在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
这样,一个自定义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>这种方式,不知道只是模拟器的问题还是真机也会。
如果不要位移动画的话实现起来会更简单一点,对自定义控件不熟悉的可以去试着写一下。
感觉写得问题挺多的,有好的建议跪求指点评论啊。
原文地址:http://blog.csdn.net/aqswde35025/article/details/42461039