自定义控件实现三种方式:
1.继承view或viewgroup
2.继承已有的自定义控件,对已有的功能进行增强
3.组合方式,把控件组合到一起
自定义控件常用的三个方法:
1.onMeasure:测量view的大小
2.onLayout :对子view进行排版
3.onDraw :画出控件
1、布局
这里有3个菜单,每个菜单中有一些按钮,可以用3个容器(RelativeLayout)来表示3个菜单,往里面添加按钮即可。(注:每个菜单都是一个半圆,菜单的宽是直径,高是半径,所以宽是高的两倍)
注意:3个菜单布局的顺序,先放men3,然后menu2,然后menu1,顺序不能反,因为越往后放的菜单越在界面的上方,这样面积最小的菜单1会在最上方,菜单1不会遮挡到下面的菜单2和菜单3。
2、控件的初始化
public class MainActivity extends Activity implements OnClickListener {
private RelativeLayout rl_menu3;
private RelativeLayout rl_menu2;
private RelativeLayout rl_menu1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rl_menu3 = (RelativeLayout) findViewById(R.id.rl_menu3);
rl_menu2 = (RelativeLayout) findViewById(R.id.rl_menu2);
rl_menu1 = (RelativeLayout) findViewById(R.id.rl_menu1);
findViewById(R.id.btn_menu2).setOnClickListener(this);
findViewById(R.id.btn_menu1).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_menu2: // 单击菜单2按钮
break;
case R.id.btn_menu1: // 单击菜单1按钮
break;
}
}
}
3、整体思路
写代码的时候可以先写整体思路,再写具体实现,如下为整体思路:
private boolean menu3Showing = true;
private boolean menu2Showing = true;
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_menu2: // 单击菜单2按钮
if (menu3Showing) {
// 隐藏菜单3
} else {
// 显示菜单3
}
menu3Showing = !menu3Showing;
break;
case R.id.btn_menu1: // 单击菜单1按钮
if (menu3Showing) {
// 隐藏菜单3
menu3Showing = false;
// 隐藏菜单2(延迟)
} else if (menu2Showing) {
// 隐藏菜单2
} else {
// 显示菜单2
}
menu2Showing = !menu2Showing;
break;
}
}
4、实现菜单旋转
上面已经整体把握了思路,就差细节了,细节就是怎么让菜单旋转起来,通过观察会发现3个菜单的显示和隐藏效果都是一样的,所以显示和隐藏菜单的功能应该写成方法,方便复用,使用”先使用后创建”的技巧创建如下方法:
public class Utils {
/** 隐藏菜单 */
public static void hideMenu(View view) {
float fromDegrees = 0f; // 旋转的起始角度
float toDegrees = -180f; // 旋转的结束角度
long startOffset = 0L; // 动画开启的延迟时间
int pivotXType = RotateAnimation.RELATIVE_TO_SELF; // 指定旋转点x方向参照物
int pivotYType = RotateAnimation.RELATIVE_TO_SELF; // 指定旋转点y方向参照物
float pivotXValue = 0.5f; // 指定旋转点在x轴的位置
float pivotYValue = 1.0f; // 指定旋转点在y轴的位置
RotateAnimation ra = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);
ra.setDuration(500); // 指定动画的执行时间
ra.setFillAfter(true); // 让动画保持在结束状态
ra.setStartOffset(startOffset); // 指定动画延迟启动的时间
view.startAnimation(ra); // 开启动画
}
}
运行查看效果。
实现了隐藏之后,显示的就很简单了,跟隐藏是差不多的,只不过是旋转的角度不一样,隐藏是从0 转到-180,显示是从-180转回0,使用“方法抽取”的方式对上面的代码进行抽取,修改后如下:
public class Utils {
/** 隐藏菜单 */
public static void hideMenu(View view) {
rotateView(view, 0f, -180f, 0L);
}
/** 隐藏菜单 */
public static void hideMenu(View view, long startOffset) {
rotateView(view, 0f, -180f, startOffset);
}
/** 显示菜单 */
public static void showMenu(View view) {
rotateView(view, -180f, 0f, 0L);
}
/**
* 旋转View
* @param view 要旋转的View
* @param fromDegrees 旋转的起始角度
* @param toDegrees 旋转的结束角度
* @param startOffset 动画启动的延迟时间
*/
private static void rotateView(View view, float fromDegrees, float toDegrees, long startOffset) {
int pivotXType = RotateAnimation.RELATIVE_TO_SELF; // 指定旋转点x方向参照物
int pivotYType = RotateAnimation.RELATIVE_TO_SELF; // 指定旋转点y方向参照物
float pivotXValue = 0.5f; // 指定旋转点在x轴的位置
float pivotYValue = 1.0f; // 指定旋转点在y轴的位置
RotateAnimation ra = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);
ra.setDuration(500); // 指定动画的执行时间
ra.setFillAfter(true); // 让动画保持在结束状态
ra.setStartOffset(startOffset); // 指定动画延迟启动的时间
view.startAnimation(ra); // 开启动画
}
}
在单击菜单的时候调用相应的工具类方法即可,如下:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_menu2: // 单击菜单2按钮
if (menu3Showing) {
// 隐藏菜单3
Utils.hideMenu(rl_menu3);
} else {
// 显示菜单3
Utils.showMenu(rl_menu3);
}
menu3Showing = !menu3Showing;
break;
case R.id.btn_menu1: // 单击菜单1按钮
if (menu3Showing) {
// 隐藏菜单3
Utils.hideMenu(rl_menu3);
menu3Showing = false;
// 隐藏菜单2(延迟)
Utils.hideMenu(rl_menu2, 300);
} else if (menu2Showing) {
// 隐藏菜单2
Utils.hideMenu(rl_menu2);
} else {
// 显示菜单2
Utils.showMenu(rl_menu2);
}
menu2Showing = !menu2Showing;
break;
}
}
5、菜单隐藏后,点击原来位置问题
菜单隐藏后在原来按钮位置点击还是可以响应点击事件的
解决方案:遍历菜单中所有的控件,并设置控件的clickable属性。修改Utils类,如下,红色代码为新增代码:
/**
* 设置View是否可以点击
* @param view
* @param clickable 是否可以点击
*/
private static void setViewClickable(View view, boolean clickable) {
view.setClickable(clickable);
if (view instanceof ViewGroup) { // 如果是一个容器,则遍历这个容器中的所有子View
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
child.setClickable(clickable);
}
}
}
/** 隐藏菜单 */
public static void hideMenu(View view) {
rotateView(view, 0f, -180f, 0L);
setViewClickable(view, false);
}
/** 隐藏菜单 */
public static void hideMenu(View view, long startOffset) {
rotateView(view, 0f, -180f, startOffset);
setViewClickable(view, false);
}
/** 显示菜单 */
public static void showMenu(View view) {
rotateView(view, -180f, 0f, 0L);
setViewClickable(view, true);
}
6、快速点击按钮问题
快速点击按钮时会发现菜单还没隐藏完就开始显示了,或者还没显示完又隐藏了,解决方案:监听动画的开启次数来判断是否需要执行画
给动画类添加一个监听器,修改Utils类,如下,红色代码为新增代码:
private static void rotateView(View view, float fromDegrees, float toDegrees, long startOffset) {
。。。之前的代码
ra.setAnimationListener(listener); // 设置动画监听器
view.startAnimation(ra); // 开启动画
}
/** 动画开启的次数 */
private static int startCount = 0;
static AnimationListener listener = new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
startCount++;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
startCount--;
}
};
/**
* 是否还有动画在执行
* @return
*/
public static boolean hasAnimExecuting() {
return startCount > 0;
}
在处理按钮的点击事件之前先判断一下是否还有动画在执行,如果有则不再执行,修改MainActivity的按钮点击事件,如下:
if (Utils.hasAnimExecuting()) {
return;
}