09-17 自定义控件之组合控件

Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,今天先大致了解一下自定义控件的要求和实现的基本原理。

分类

Android开发中用到的自定义控件可以分为有三种:

  1. 组合控件
    把几个常用控件组合起来,形成一个新的控件,相当于一个组团的意思。
  2. 扩展控件
    对现有控件进行功能或者样式上的扩展,相当于给原先的控件添加装备。
  3. 完全自定义控件
    功能样式都自行定义设计,可以实现想要的功能。

光说不练到底是不行的,我打算每个文章里面加上一个小例子来写,理解也更方便。

开始

我当初入门的时候,就是以早期的优酷菜单的作为练手的,现在就还拿这个来说吧!老规矩,先放一张效果图:


优酷菜单

该菜单由内而外分别叫做“1级菜单”,“2级菜单”和“3级菜单”,1级菜单和2级菜单的中心位置的ImageButton用来控制整个菜单的动态效果。点击1级菜单时,若2级或者3级菜单处于显示状态,则隐藏2级和3级菜单,如果没有显示,则只显示出2级菜单。点击2级菜单的时候,只控制3级菜单的显示和隐藏。

布局

首先,让我们来开始愉快的画布局吧!

<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"  
    tools:context=".MainActivity" >  
  
    <RelativeLayout  
        android:id="@+id/rl_level1"  
        android:layout_width="100dip"  
        android:layout_height="50dip"  
        android:layout_alignParentBottom="true"  
        android:layout_centerHorizontal="true"  
        android:background="@drawable/level1" >  
  
        <ImageButton  
            android:id="@+id/ib_home"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_centerInParent="true"  
            android:background="@drawable/icon_home" />  
    </RelativeLayout>  
  
    <RelativeLayout  
        android:id="@+id/rl_level2"  
        android:layout_width="200dip"  
        android:layout_height="100dip"  
        android:layout_alignParentBottom="true"  
        android:layout_centerHorizontal="true"  
        android:background="@drawable/level2" >  
  
        <ImageButton  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_alignParentBottom="true"  
            android:layout_marginBottom="5dip"  
            android:layout_marginLeft="10dip"  
            android:background="@drawable/icon_search" />  
  
        <ImageButton  
            android:id="@+id/ib_menu"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_centerHorizontal="true"  
            android:layout_marginTop="10dip"  
            android:background="@drawable/icon_menu" />  
  
        <ImageButton  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_alignParentBottom="true"  
            android:layout_alignParentRight="true"  
            android:layout_marginBottom="5dip"  
            android:layout_marginRight="10dip"  
            android:background="@drawable/icon_myyouku" />  
    </RelativeLayout>  
  
    <RelativeLayout  
        android:id="@+id/rl_level3"  
        android:layout_width="320dip"  
        android:layout_height="160dip"  
        android:layout_alignParentBottom="true"  
        android:layout_centerHorizontal="true"  
        android:background="@drawable/level3" >  
  
        <ImageButton  
            android:id="@+id/ib_channel1"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_alignParentBottom="true"  
            android:layout_marginBottom="10dip"  
            android:layout_marginLeft="15dip"  
            android:background="@drawable/channel1" />  
  
        <ImageButton  
            android:id="@+id/ib_channel2"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_above="@id/ib_channel1"  
            android:layout_marginBottom="20dip"  
            android:layout_marginLeft="40dip"  
            android:background="@drawable/channel2" />  
  
        <ImageButton  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_above="@id/ib_channel2"  
            android:layout_marginBottom="15dip"  
            android:layout_marginLeft="10dip"  
            android:layout_toRightOf="@id/ib_channel2"  
            android:background="@drawable/channel3" />  
  
        <ImageButton  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_centerHorizontal="true"  
            android:layout_marginTop="10dip"  
            android:background="@drawable/channel4" />  
  
        <ImageButton  
            android:id="@+id/ib_channel7"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_alignParentBottom="true"  
            android:layout_alignParentRight="true"  
            android:layout_marginBottom="10dip"  
            android:layout_marginRight="15dip"  
            android:background="@drawable/channel7" />  
  
        <ImageButton  
            android:id="@+id/ib_channel6"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_above="@id/ib_channel7"  
            android:layout_alignParentRight="true"  
            android:layout_marginBottom="20dip"  
            android:layout_marginRight="40dip"  
            android:background="@drawable/channel6" />  
  
        <ImageButton  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_above="@id/ib_channel6"  
            android:layout_marginBottom="15dip"  
            android:layout_marginRight="10dip"  
            android:layout_toLeftOf="@id/ib_channel6"  
            android:background="@drawable/channel5" />  
    </RelativeLayout>  
  
</RelativeLayout>

动画

因为动画都是靠自定义的旋转动画来实现的,而且用的地方也比较多,所以对代码进行了抽取,封装了一个工具类,暴露出转入以及转出两个方法,来供调用……

import android.view.animation.Animation;  
import android.view.animation.RotateAnimation;  
import android.widget.RelativeLayout;  
  
public class AnimationUtils {  
  
    public static boolean isRunningAnimation = false; // 记录动画是否在执行  
  
    /** 
     * 旋转出去的动画 
     *  
     * @param layout 
     *            执行动画的对象 
     * @param startOffset 
     *            延迟时间 
     */  
    public static void outRotateAnimation(RelativeLayout layout, long startOffset) {  
        // 防止父控件中的子控件抢焦点能力强,而将子控件设置为不可用  
        for (int i = 0; i < layout.getChildCount(); i++) {  
            layout.getChildAt(i).setEnabled(false);  
        }  
  
        RotateAnimation ra = new RotateAnimation( //  
                0.0f, // 旋转开始的角度  
                -180.0f, // 旋转结束的角度  
                RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标X轴的参照物  
                0.5f, // 相对于参照物X轴的百分比  
                RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标Y轴的参照物  
                1.0f // 相对于参照物Y轴的百分比  
        );  
        ra.setDuration(500);  
        ra.setStartOffset(startOffset); // 动画延迟时间  
        ra.setFillAfter(true);  
        ra.setAnimationListener(new MyAnimationListener());  
        layout.startAnimation(ra);  
    }  
  
    /** 
     * 旋转进来的动画 
     *  
     * @param layout 
     *            执行动画的对象 
     */  
    public static void inRotateAnimation(RelativeLayout layout) {  
        // 进来的时候,将所有的子控件设置为可用  
        for (int i = 0; i < layout.getChildCount(); i++) {  
            layout.getChildAt(i).setEnabled(false);  
        }  
  
        RotateAnimation ra = new RotateAnimation( //  
                -180.0f, // 旋转开始的角度  
                0.0f, // 旋转结束的角度  
                RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标X轴的参照物  
                0.5f, // 相对于参照物X轴的百分比  
                RotateAnimation.RELATIVE_TO_SELF, // 旋转坐标Y轴的参照物  
                1.0f // 相对于参照物Y轴的百分比  
        );  
        ra.setDuration(500);  
        ra.setFillAfter(true);  
        layout.startAnimation(ra);  
    }  
  
    static class MyAnimationListener implements Animation.AnimationListener {  
  
        /** 
         * 动画开始的时候执行 
         */  
        @Override  
        public void onAnimationStart(Animation animation) {  
            // TODO Auto-generated method stub  
            isRunningAnimation = true;  
        }  
  
        /** 
         * 动画结束的时候执行 
         */  
        @Override  
        public void onAnimationEnd(Animation animation) {  
            // TODO Auto-generated method stub  
            isRunningAnimation = false;  
        }  
  
        /** 
         * 动画重复执行的时候 
         */  
        @Override  
        public void onAnimationRepeat(Animation animation) {  
            // TODO Auto-generated method stub  
  
        }  
  
    }  
  
}  

代码控制

然后就是在代码中进行控制。当点击的时候判断当前菜单的出现状态。然后根据菜单状态的不同执行相应操作。

import android.os.Bundle;  
import android.app.Activity;  
import android.view.KeyEvent;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.RelativeLayout;  
  
public class MainActivity extends Activity implements OnClickListener {  
  
    private RelativeLayout rlLevel1;  
    private RelativeLayout rlLevel2;  
    private RelativeLayout rlLevel3;  
    /** 记录3级菜单是否展示 */  
    private boolean isDisplayLevel3 = true;  
    /** 记录2级菜单是否展示 */  
    private boolean isDisplayLevel2 = true;  
    /** 记录1级菜单是否展示 */  
    private boolean isDisplayLevel1 = true;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        rlLevel1 = (RelativeLayout) findViewById(R.id.rl_level1);  
        rlLevel2 = (RelativeLayout) findViewById(R.id.rl_level2);  
        rlLevel3 = (RelativeLayout) findViewById(R.id.rl_level3);  
  
        findViewById(R.id.ib_home).setOnClickListener(this);  
        findViewById(R.id.ib_menu).setOnClickListener(this);  
  
    }  
  
    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.ib_home:  
            if (AnimationUtils.isRunningAnimation) // 当前动画正在执行的时候,不执行动画  
                return;  
            if (isDisplayLevel2) {  
                // 2级菜单正在展示  
                long startOffset = 0; // 旋转延时时间  
                if (isDisplayLevel3) {  
                    // 3级菜单也在展示,先旋转出去3级菜单,再旋转出去2级菜单  
                    AnimationUtils.outRotateAnimation(rlLevel3, startOffset);  
                    startOffset += 200;  
                    isDisplayLevel3 = !isDisplayLevel3;  
                }  
                AnimationUtils.outRotateAnimation(rlLevel2, startOffset);  
            } else {  
                // 2级菜单没有展示,需要旋转进来  
                AnimationUtils.inRotateAnimation(rlLevel2);  
            }  
            isDisplayLevel2 = !isDisplayLevel2;  
            break;  
        case R.id.ib_menu:  
            if (AnimationUtils.isRunningAnimation)  
                return;  
            if (isDisplayLevel3) {  
                // 3级菜单正在展示,需要旋转出去  
                AnimationUtils.outRotateAnimation(rlLevel3, 0);  
            } else {  
                // 3级菜单没有展示,需要旋转进来  
                AnimationUtils.inRotateAnimation(rlLevel3);  
            }  
            isDisplayLevel3 = !isDisplayLevel3;  
            break;  
        default:  
            break;  
        }  
    }  
  
    /** 
     * 菜单按钮的处理 
     */  
    @Override  
    public boolean onKeyDown(int keyCode, KeyEvent event) {  
        // TODO Auto-generated method stub  
        if (keyCode == KeyEvent.KEYCODE_MENU) {  
            if (AnimationUtils.isRunningAnimation)  
                return super.onKeyDown(keyCode, event);  
            if (isDisplayLevel1) {  
                // 1级菜单旋转出去  
                long startOffset = 0; // 记录延迟时间  
                if (isDisplayLevel2) {  
                    // 2级菜单旋转出去  
                    if (isDisplayLevel3) {  
                        // 3级菜单先旋转出去  
                        AnimationUtils.outRotateAnimation(rlLevel3, startOffset);  
                        startOffset += 200;  
                        isDisplayLevel3 = !isDisplayLevel3;  
                    }  
                    AnimationUtils.outRotateAnimation(rlLevel2, startOffset);  
                    startOffset += 200; // 延迟200ms  
                    isDisplayLevel2 = !isDisplayLevel2;  
                }  
                AnimationUtils.outRotateAnimation(rlLevel1, startOffset);  
            } else {  
                // 1级菜单旋转进来  
                AnimationUtils.inRotateAnimation(rlLevel1);  
            }  
            isDisplayLevel1 = !isDisplayLevel1;  
        }  
        return super.onKeyDown(keyCode, event);  
    }  
}  

至此,大功告成!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容