效果圖
項目概述
首先供常,我們學習如何自定義一個組合控件悼凑,其中偿枕,優(yōu)酷菜單是一個典型的自定義組合控件捐迫,它的效果圖如圖1-1 所示:
圖中由中間往外,分別是一級菜單断国、二級菜單源祈、三級菜單。其基本用法是:點擊一級菜單后加載二級菜單墓塌,再點擊二級菜單加載三級菜單瘟忱,如圖1-2(c)—(d)—(e)—(f),再點擊一級菜單分別隱藏三級苫幢、二級菜單
1-2(a)—(b)访诱。并且點擊手機菜單鍵,讓菜單根據(jù)狀態(tài)來顯示和隱藏韩肝,演示效果圖如圖1-2 所示触菜。
優(yōu)酷菜單UI
優(yōu)酷菜單的整體布局采用RelativeLayout,每一級菜單都是一個RelativeLayout哀峻。優(yōu)酷菜單的布局文件activity_main.xml涡相,具體的代碼如文件【1-1】所示:
【文件1-1】activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--三級菜單-->
<RelativeLayout
android:id="@+id/rl_level3"
android:layout_width="280dp"
android:layout_height="140dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level3">
<ImageView
android:id="@+id/iv_channel1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:src="@drawable/channel1"/>
<ImageView
android:id="@+id/iv_channel2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/iv_channel1"
android:layout_alignLeft="@id/iv_channel1"
android:layout_marginBottom="5dp"
android:layout_marginLeft="25dp"
android:src="@drawable/channel2"/>
<ImageView
android:id="@+id/iv_channel3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/iv_channel2"
android:layout_alignLeft="@id/iv_channel2"
android:layout_marginBottom="5dp"
android:layout_marginLeft="35dp"
android:src="@drawable/channel3"/>
<ImageView
android:id="@+id/iv_channel4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="5dp"
android:src="@drawable/channel4"/>
<ImageView
android:id="@+id/iv_channel7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="10dp"
android:src="@drawable/channel7"/>
<ImageView
android:id="@+id/iv_channel6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/iv_channel7"
android:layout_alignRight="@id/iv_channel7"
android:layout_marginBottom="5dp"
android:layout_marginRight="25dp"
android:src="@drawable/channel6"/>
<ImageView
android:id="@+id/iv_channel5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/iv_channel6"
android:layout_alignRight="@id/iv_channel6"
android:layout_marginBottom="5dp"
android:layout_marginRight="35dp"
android:src="@drawable/channel5"/>
</RelativeLayout>
<!--二級菜單-->
<RelativeLayout
android:id="@+id/rl_level2"
android:layout_width="180dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level2">
<ImageView
android:id="@+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:src="@drawable/icon_search"/>
<ImageView
android:id="@+id/iv_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="5dp"
android:src="@drawable/icon_menu"/>
<ImageView
android:id="@+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="10dp"
android:src="@drawable/icon_myyouku"/>
</RelativeLayout>
<!--二級菜單-->
<RelativeLayout
android:id="@+id/rl_level1"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@drawable/level1">
<ImageView
android:id="@+id/iv_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@drawable/icon_home"/>
</RelativeLayout>
</RelativeLayout>
運行程序,效果圖如圖1-3 所示
優(yōu)酷菜單業(yè)務邏輯實現(xiàn)
布局UI 實現(xiàn)之后剩蟀,我們需要實現(xiàn)優(yōu)酷菜單的業(yè)務邏輯代碼催蝗,具體代碼如文件【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}有限公司版權(quán)所有 (c) 2017
* Author: AllenIverson
* Email: 815712739@qq.com
* GitHub: https://github.com/JackChen1999
* 博客: http://blog.csdn.net/axi295309066
* 微博: AndroidDeveloper
* <p>
* Project_Name:RotateMenu
* Package_Name:com.github.rotatemenu
* Version:1.0
* time:2016/2/28 21:47
* des :三級旋轉(zhuǎn)菜單
* 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) {// 如果發(fā)現(xiàn)第三次也展現(xiàn), 也需要隱藏
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;
}
}
//監(jiān)聽用戶的物理按鍵
@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 工具類的邏輯實現(xiàn)
為了隱藏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}有限公司版權(quán)所有 (c) 2017
* Author: AllenIverson
* Email: 815712739@qq.com
* GitHub: https://github.com/JackChen1999
* 博客: http://blog.csdn.net/axi295309066
* 微博: AndroidDeveloper
* <p>
* Project_Name:RotateMenu
* Package_Name:com.github.rotatemenu
* Version:1.0
* time:2016/2/28 21:47
* des :三級旋轉(zhuǎn)菜單
* 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; //用于記錄當前正在執(zhí)行的動畫個數(shù)
/**
* 隱藏動畫
*
* @param view 將要執(zhí)行動畫的視圖
* @param delay 動畫要延遲執(zhí)行的時間
*/
public static void hideView(ViewGroup view, long delay) {
/**
* 第一個參數(shù): fromDegrees 起始角度丙号,這里我們設置為0
* 第二個參數(shù): toDegrees 目標角度,這里設置為180 度
* 第三個參數(shù): pivotXType 相對于X 坐標類型缰冤,這里是相對于自己
* 第四個參數(shù): pivotXValue 相對于X 坐標類型的值槽袄,這里是0.5f,也就是X 軸的一半
* 第五個參數(shù): pivotYType 相對于Y 坐標類型,這里是相對于自己
* 第六個參數(shù): 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); //動畫執(zhí)行時間
anim.setFillAfter(true); // 保持動畫后的狀態(tài)
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 將要執(zhí)行動畫的視圖
* @param delay 動畫要延遲執(zhí)行的時間
*/
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); // 保持動畫后的狀態(tài)
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) {
}
}
}
知識點總結(jié)
1.補間動畫不能改變控件的實際位置遍尺,控件還是能夠響應原先的事件。在菜單隱藏后還會響應點擊事件涮拗,因此在Tools.java 的第32 到36 行在隱藏菜單時乾戏,通過遍歷相對布局的子控件,設置其為不可用來解決此bug三热,
在顯示菜單時鼓择,第51 到55 行通過遍歷相對布局的子控件,設置為可用就漾。
2.連續(xù)點擊菜單時呐能,優(yōu)酷菜單動畫會直接執(zhí)行,產(chǎn)生一個隱藏動畫還沒執(zhí)行完,就執(zhí)行顯示動畫的bug摆出,因此在Tools.java 的隱藏和顯示動畫中都設置了動畫監(jiān)聽MyAnimationListener朗徊,在點擊菜單時,先判斷Tools
的動畫數(shù)量mAnimtionNum(Tools.java 第8 行)是否為0偎漫,再執(zhí)行下一個動畫爷恳,來解決bug。
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@id/btn_menu"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:text="Menu鍵"
android:textColor="#fff"
android:background="@drawable/progress_normal"
android:textAllCaps="false"/>
<!--一級菜單-->
<RelativeLayout
android:id="@+id/rl_level1"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@mipmap/level1">
<ImageView
android:id="@+id/iv_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@mipmap/icon_home"/>
</RelativeLayout>
<!--二級菜單-->
<RelativeLayout
android:id="@+id/rl_level2"
android:layout_width="180dp"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@mipmap/level2">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:src="@mipmap/icon_search"/>
<ImageView
android:id="@+id/iv_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="5dp"
android:src="@mipmap/icon_menu"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="10dp"
android:src="@mipmap/icon_myyouku"/>
</RelativeLayout>
<!--三級菜單-->
<include
layout="@layout/menu_level3"
/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_level3"
android:layout_width="280dp"
android:layout_height="140dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="@mipmap/level3">
<ImageView
android:id="@+id/channel1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:src="@mipmap/channel1"/>
<ImageView
android:id="@+id/channel2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/channel1"
android:layout_alignLeft="@+id/channel1"
android:layout_marginLeft="25dp"
android:layout_marginBottom="5dp"
android:src="@mipmap/channel2"/>
<ImageView
android:id="@+id/channel3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/channel2"
android:layout_alignLeft="@+id/channel2"
android:layout_marginLeft="35dp"
android:layout_marginBottom="5dp"
android:src="@mipmap/channel3"/>
<ImageView
android:id="@+id/channel4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="5dp"
android:src="@mipmap/channel4"/>
<ImageView
android:id="@+id/channel5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/channel6"
android:layout_alignRight="@+id/channel6"
android:layout_marginBottom="5dp"
android:layout_marginRight="35dp"
android:src="@mipmap/channel5"/>
<ImageView
android:id="@+id/channel6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/channel7"
android:layout_alignRight="@+id/channel7"
android:layout_marginBottom="5dp"
android:layout_marginRight="25dp"
android:src="@mipmap/channel6"/>
<ImageView
android:id="@+id/channel7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:src="@mipmap/channel7"/>
</RelativeLayout>
實現(xiàn)代碼
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("三級旋轉(zhuǎn)菜單");
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;//記錄當前執(zhí)行的動畫數(shù)量
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--;
}
};
}