效果图
项目概述
首先,我们学习如何自定义一个组合控件,其中,优酷菜单是一个典型的自定义组合控件,它的效果图如图1-1 所示:
图中由中间往外,分别是一级菜单、二级菜单、三级菜单。其基本用法是:点击一级菜单后加载二级菜单,再点击二级菜单加载三级菜单,如图1-2(c)—(d)—(e)—(f),再点击一级菜单分别隐藏三级、二级菜单
1-2(a)—(b)。并且点击手机菜单键,让菜单根据状态来显示和隐藏,演示效果图如图1-2 所示。
优酷菜单UI
优酷菜单的整体布局采用RelativeLayout,每一级菜单都是一个RelativeLayout。优酷菜单的布局文件activity_main.xml,具体的代码如文件【1-1】所示:
【文件1-1】activity_main.xml
运行程序,效果图如图1-3 所示
优酷菜单业务逻辑实现
布局UI 实现之后,我们需要实现优酷菜单的业务逻辑代码,具体代码如文件【1-2】所示:【文件1-2】com.itheima.youku.MainActivity
package com.github.rotatemenu;
import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
/**
* ============================================================
* Copyright:${TODO}有限公司版权所有 (c) 2017
* Author: AllenIverson
* Email: [email protected]
* GitHub: https://github.com/JackChen1999
* 博客: http://blog.csdn.net/axi295309066
* 微博: AndroidDeveloper
*
* Project_Name:RotateMenu
* Package_Name:com.github.rotatemenu
* Version:1.0
* time:2016/2/28 21:47
* des :三级旋转菜单
* gitVersion:$Rev$
* updateAuthor:$Author$
* updateDate:$Date$
* updateDes:${TODO}
* ============================================================
*/
public class MainActivity extends Activity implements View.OnClickListener {
private RelativeLayout rlLevel1, rlLevel2, rlLevel3;
private boolean isLevel1Show = true;
private boolean isLevel2Show = true;
private boolean isLevel3Show = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView ivHome = (ImageView) findViewById(R.id.iv_home);
ImageView ivMenu = (ImageView) findViewById(R.id.iv_menu);
rlLevel1 = (RelativeLayout) findViewById(R.id.rl_level1);
rlLevel2 = (RelativeLayout) findViewById(R.id.rl_level2);
rlLevel3 = (RelativeLayout) findViewById(R.id.rl_level3);
ivHome.setOnClickListener(this);
ivMenu.setOnClickListener(this);
// 为了避免第三层布局将一二层事件拦截掉, 需要在布局文件中最先注册第三层, 最后注册第一层
rlLevel3.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_home:
System.out.println("home clicked!");
if (Tools.mAnimtionNum != 0) {
break;
}
if (isLevel2Show) {
Tools.hideView(rlLevel2);// 隐藏第二层布局
isLevel2Show = false;
if (isLevel3Show) {// 如果发现第三次也展现, 也需要隐藏
Tools.hideView(rlLevel3, 200);// 动画延时200 毫秒再运行
isLevel3Show = false;
}
} else {
Tools.showView(rlLevel2);
isLevel2Show = true;
}
break;
case R.id.iv_menu:
if (Tools.mAnimtionNum != 0) {
break;
}
System.out.println("menu clicked!");
if (isLevel3Show) {
Tools.hideView(rlLevel3);
isLevel3Show = false;
} else {
Tools.showView(rlLevel3);
isLevel3Show = true;
}
break;
default:
break;
}
}
//监听用户的物理按键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
if (Tools.mAnimtionNum != 0) {
return true;
}
if (isLevel1Show) {
Tools.hideView(rlLevel1);
isLevel1Show = false;
if (isLevel2Show) {
Tools.hideView(rlLevel2, 200);
isLevel2Show = false;
}
if (isLevel3Show) {
Tools.hideView(rlLevel3, 300);
isLevel3Show = false;
}
} else {
Tools.showView(rlLevel1);
isLevel1Show = true;
Tools.showView(rlLevel2, 200);
isLevel2Show = true;
}
return true;
}
return super.onKeyDown(keyCode, event);
}
}
Tools 工具类的逻辑实现
为了隐藏View 和显示View,在工具类Tools.java 中定义了两个方法,具体代码如文件【】所示:【文件1-3】com.itheima.youku.Tools
package com.github.rotatemenu;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
/**
* ============================================================
* Copyright:${TODO}有限公司版权所有 (c) 2017
* Author: AllenIverson
* Email: [email protected]
* GitHub: https://github.com/JackChen1999
* 博客: http://blog.csdn.net/axi295309066
* 微博: AndroidDeveloper
*
* Project_Name:RotateMenu
* Package_Name:com.github.rotatemenu
* Version:1.0
* time:2016/2/28 21:47
* des :三级旋转菜单
* gitVersion:$Rev$
* updateAuthor:$Author$
* updateDate:$Date$
* updateDes:${TODO}
* ============================================================
*/
public class Tools {
public static void hideView(ViewGroup view) {
hideView(view, 0);
}
public static void showView(ViewGroup view) {
showView(view, 0);
}
public static int mAnimtionNum = 0; //用于记录当前正在执行的动画个数
/**
* 隐藏动画
*
* @param view 将要执行动画的视图
* @param delay 动画要延迟执行的时间
*/
public static void hideView(ViewGroup view, long delay) {
/**
* 第一个参数: fromDegrees 起始角度,这里我们设置为0
* 第二个参数: toDegrees 目标角度,这里设置为180 度
* 第三个参数: pivotXType 相对于X 坐标类型,这里是相对于自己
* 第四个参数: pivotXValue 相对于X 坐标类型的值,这里是0.5f,也就是X 轴的一半
* 第五个参数: pivotYType 相对于Y 坐标类型,这里是相对于自己
* 第六个参数: pivotYValue 相对于Y 坐标类型的值,这里是1.f,也就是Y 坐标最大处
* RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType,
pivotYValue)
*/
RotateAnimation anim = new RotateAnimation(0, 180,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 1f);
anim.setDuration(500); //动画执行时间
anim.setFillAfter(true); // 保持动画后的状态
anim.setStartOffset(delay); // 延迟多长时间后才运行动画
anim.setAnimationListener(new MyAnimationListener());
view.startAnimation(anim);
// 禁用所有孩子的点击事件
int childCount = view.getChildCount();
for (int i = 0; i < childCount; i++) {
view.getChildAt(i).setEnabled(false); // 禁用点击事件
}
}
/**
* 显示动画
*
* @param view 将要执行动画的视图
* @param delay 动画要延迟执行的时间
*/
public static void showView(ViewGroup view, long delay) {
RotateAnimation anim = new RotateAnimation(180, 360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 1f);
anim.setDuration(500);
anim.setFillAfter(true); // 保持动画后的状态
anim.setStartOffset(delay); // 延迟多长时间后才运行动画
anim.setAnimationListener(new MyAnimationListener());
view.startAnimation(anim);
// 开启所有孩子的点击事件
int childCount = view.getChildCount();
for (int i = 0; i < childCount; i++) {
view.getChildAt(i).setEnabled(true);// 开启点击事件
}
}
public static class MyAnimationListener implements Animation.AnimationListener {
@Override
public void onAnimationStart(Animation animation) {
mAnimtionNum++;
}
@Override
public void onAnimationEnd(Animation animation) {
mAnimtionNum--;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
}
}
知识点总结
1.补间动画不能改变控件的实际位置,控件还是能够响应原先的事件。在菜单隐藏后还会响应点击事件,因此在Tools.java 的第32 到36 行在隐藏菜单时,通过遍历相对布局的子控件,设置其为不可用来解决此bug,
在显示菜单时,第51 到55 行通过遍历相对布局的子控件,设置为可用。
2.连续点击菜单时,优酷菜单动画会直接执行,产生一个隐藏动画还没执行完,就执行显示动画的bug,因此在Tools.java 的隐藏和显示动画中都设置了动画监听MyAnimationListener,在点击菜单时,先判断Tools
的动画数量mAnimtionNum(Tools.java 第8 行)是否为0,再执行下一个动画,来解决bug。
布局文件
实现代码
RotateMenuActivity.java
public class RotateMenuActivity extends AppCompatActivity implements View.OnClickListener{
@Bind(R.id.iv_home)
public ImageView mIv_home;
@Bind(R.id.iv_menu)
public ImageView mIv_menu;
@Bind(R.id.rl_level1)
public RelativeLayout mLevel1;
@Bind(R.id.rl_level2)
public RelativeLayout mLevel2;
@Bind(R.id.rl_level3)
public RelativeLayout mLevel3;
@Bind(R.id.btn_menu)
public Button btn_menu;
private boolean isShowlevel2 = true;
private boolean isShowlevel3 = true;
private boolean isShowmenu = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initView();
initListener();
}
private void initView() {
setContentView(R.layout.activity_rotate_menu);
ButterKnife.bind(this);
SpannableString title = new SpannableString("三级旋转菜单");
title.setSpan(new ForegroundColorSpan(Color.WHITE),0,title.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(title);
}
private void initListener() {
mIv_home.setOnClickListener(this);
mIv_menu.setOnClickListener(this);
btn_menu.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.iv_home:
if (AnimUtil.animCount != 0){
return;
}
if (isShowlevel2){
int startOffset = 0;
if (isShowlevel3){
AnimUtil.closeMenu(mLevel3,startOffset);
startOffset += 200;
isShowlevel3 = false;
}
AnimUtil.closeMenu(mLevel2,startOffset);
}else {
AnimUtil.openMenu(mLevel2,0);
}
isShowlevel2 = !isShowlevel2;
break;
case R.id.iv_menu:
if (AnimUtil.animCount != 0){
return;
}
if (isShowlevel3){
AnimUtil.closeMenu(mLevel3,0);
}else {
AnimUtil.openMenu(mLevel3,0);
}
isShowlevel3 = !isShowlevel3;
break;
case R.id.btn_menu:
showMenu();
break;
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU){
showMenu();
return true;
}
return super.onKeyDown(keyCode, event);
}
private void showMenu(){
if (isShowmenu){
int startOffset = 0;
if (isShowlevel3){
AnimUtil.closeMenu(mLevel3,startOffset);
isShowlevel3 = false;
startOffset += 200;
}
if (isShowlevel2){
AnimUtil.closeMenu(mLevel2,startOffset);
isShowlevel2 = false;
startOffset += 200;
}
AnimUtil.closeMenu(mLevel1,startOffset);
}else {
AnimUtil.openMenu(mLevel1,0);
AnimUtil.openMenu(mLevel2,200);
isShowlevel2 = true;
AnimUtil.openMenu(mLevel3,400);
isShowlevel3 = true;
}
isShowmenu = !isShowmenu;
}
}
AnimUtil.java
public class AnimUtil {
public static int animCount = 0;//记录当前执行的动画数量
public static void closeMenu(View view, int startOffset) {
view.setPivotX(view.getWidth()/2);
view.setPivotY(view.getHeight());
//view.invalidate();
view.animate().rotation(-180).setDuration(500).setListener(mListener).setStartDelay
(startOffset).start();
}
public static void openMenu(View view, int startOffset) {
view.setPivotX(view.getWidth()/2);
view.setPivotY(view.getHeight());
//view.invalidate();
view.animate().rotation(0).setDuration(500).setListener(mListener).setStartDelay
(startOffset).start();
}
static AnimatorListener mListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
animCount++;
}
@Override
public void onAnimationEnd(Animator animation) {
animCount--;
}
};
}
代码:https://github.com/JackChen1999/RotateMenu