Android自定义控件之仿优酷菜单

Android自定义控件之仿优酷菜单

简介

本文将介绍如何通过自定义ViewGroup实现仿优酷菜单的效果,主要涉及以下几个方面:

  1. 自定义ViewGroup的基本概念
  2. 仿优酷菜单的实现过程
  3. 示例展示说明

自定义ViewGroup

ViewGroup是View的子类,可以包含多个子View,是Android App中布局最常用的容器之一。自定义ViewGroup可以让我们更好的控制App的布局效果,并实现一些自定义的交互效果。

自定义ViewGroup需要重写ViewGroup的三个方法:

  • onMeasure()
  • onLayout()
  • onDraw()

  • onMeasure():是ViewGroup中最重要的方法,用于测量ViewGroup的大小。

  • onLayout():用于确定ViewGroup中子View的位置。
  • onDraw():用于绘制ViewGroup的背景。

仿优酷菜单的实现

准备工作

在布局文件中引入自定义的ViewGroup:

<com.example.menu.MyMenu
    android:id="@+id/my_menu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

自定义ViewGroup

首先,我们要继承ViewGroup类,并且实现其构造方法和测量子View的方法onMeasure()。

public class MyMenu extends ViewGroup {
    public MyMenu(Context context) {
        super(context);
        init();
    }

    public MyMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        ...
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
    }
}

在init()方法中,我们可以配置ViewGroup的默认属性,比如菜单的宽度和高度,以及子View的颜色。

private void init() {
    setBackgroundColor(Color.WHITE);
    setClickable(true);
    setWillNotDraw(false);
    setOrientation(VERTICAL);

    mMenuWidth = dip2px(getContext(), 200);
    mMenuHeight = dip2px(getContext(), 50);
    mMenuItemWidth = dip2px(getContext(), 50);
    mMenuItemHeight = dip2px(getContext(), 50);
    mMenuItemCount = 4;
    mMenuItemColor = new int[] {Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN};
}

接下来,我们重写onMeasure()方法,通过测量子View的宽度和高度,计算出ViewGroup的宽度和高度。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    //获取到ViewGroup的宽度和高度
    //分别获取其宽度测量模式,以及宽度数值
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    //先测量菜单项的宽高,用于确定菜单的大小
    int itemWidthSpec = MeasureSpec.makeMeasureSpec(mMenuItemWidth, MeasureSpec.EXACTLY);
    int itemHeightSpec = MeasureSpec.makeMeasureSpec(mMenuItemHeight, MeasureSpec.EXACTLY);
    for (int i = 0; i < mMenuItemCount; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }
        child.measure(itemWidthSpec, itemHeightSpec);
    }

    //菜单的宽度设置为所有菜单项的宽度之和
    int menuWidth = mMenuWidth;
    int menuHeight = mMenuHeight * mMenuItemCount;

    //根据ViewGroup的宽度和高度,设置宽度和高度的测量模式
    //宽度和高度的模式必须为MeasureSpec、AT_MOST
    setMeasuredDimension(width, Math.min(menuHeight, height));
}

接下来,我们在onLayout()方法中,确定菜单的位置和每个菜单项的位置。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    int childTop = getHeight() - mMenuHeight;
    //动画结束时,子菜单项显示的行为,即未展开时,其他子菜单项收起
    if (!mExpanded) {
        for (int i = 1; i < mMenuItemCount; i++) {
            View child = getChildAt(i);
            child.setVisibility(GONE);
        }
    }

    for (int i = 0; i < mMenuItemCount; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }

        int childLeft = getWidth() - mMenuItemWidth - getPaddingRight();
        childTop -= mMenuItemHeight;
        child.layout(childLeft, childTop, childLeft + mMenuItemWidth, childTop + mMenuItemHeight);
    }
}

在onDraw() 方法中,我们可以绘制ViewGroup的背景,这里可以通过Paint和Canvas绘制一些特定效果,比如渐变色等。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Rect rect = new Rect(0, getHeight() - mMenuHeight * mMenuItemCount, getWidth(), getHeight());
    canvas.drawBitmap(getBackgroundBitmap(getWidth(), getHeight()), 0, 0, null);
    canvas.drawRect(rect, mShadowPaint);
}

实现菜单缩放动画

在自定义ViewGroup的基础上,我们还需要实现菜单的展开和收起动画。

首先,我们可以通过OnClickListener监听菜单的点击事件,并在事件处理中添加动画效果。

setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        if (mAnimating) {
            return;
        }
        if (!mExpanded) {
            expandMenu();
        } else {
            collapseMenu();
        }
    }
});

接下来,我们分别实现菜单的展开和收起动画。

private void expandMenu() {
    //展开MenuItem时为了制造一些阻尼效果,先移动一段距离,然后再移动过去
    int distance = ((getHeight() - mMenuHeight) / (mMenuItemCount - 1)) + mMenuItemHeight / 2;
    for (int i = 0; i < mMenuItemCount - 1; i++) {
        final View child = getChildAt(i + 1);
        final int x = i + 1;
        final int y = mMenuItemCount - x;
        child.setVisibility(VISIBLE);
        AnimationSet animationSet = new AnimationSet(true);
        animationSet.setInterpolator(new OvershootInterpolator());
        Animation animation = null;
        animation = new TranslateAnimation(getPaddingLeft(), getPaddingLeft(), distance * y, 0);
        animation.setDuration(mAnimationDuration);

        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                mAnimating = true;
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                child.clearAnimation();
                mAnimating = false;
                mExpanded = true;
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        animationSet.addAnimation(animation);
        child.startAnimation(animationSet);
    }
}

private void collapseMenu() {
    //收缩右侧的MenuItem,只留中心点
    int distance = ((getHeight() - mMenuHeight) / (mMenuItemCount - 1)) + mMenuItemHeight / 2;
    for (int i = 0; i < mMenuItemCount - 1; i++) {
        final View child = getChildAt(i + 1);
        final int x = i + 1;
        final int y = mMenuItemCount - x;
        AnimationSet animationSet = new AnimationSet(true);
        animationSet.setInterpolator(new OvershootInterpolator());
        Animation animation = null;
        animation = new TranslateAnimation(getPaddingLeft(), getPaddingLeft(), 0, distance * y);
        animation.setDuration(mAnimationDuration);
        animation.setFillAfter(false);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                mAnimating = true;
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                int childCount = getChildCount();
                for (int i = 1; i < childCount; i++) {
                    View child = getChildAt(i);
                    child.clearAnimation();
                    child.setVisibility(GONE);
                }
                mAnimating = false;
                mExpanded = false;
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        animationSet.addAnimation(animation);
        child.startAnimation(animationSet);
    }
}

示例展示说明

我们现在来演示一下,如何在自定义控件中实现菜单缩放动画。

示例1 仿优酷菜单

首先,我们在菜单中添加一个菜单项,用于展示菜单展开和收起动画。

<com.example.menu.MyMenu
    android:id="@+id/my_menu"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_menu"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/ic_menu" />
</com.example.menu.MyMenu>

接着,我们通过代码实现菜单的展开和收起动画。

final MyMenu menu = findViewById(R.id.my_menu);
final ImageView ivMenu = findViewById(R.id.iv_menu);
ivMenu.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (menu.isAnimating()) {
            return;
        }
        if (!menu.isExpanded()) {
            ivMenu.animate().rotation(135).setDuration(300).start();
            menu.expandMenu();
        } else {
            ivMenu.animate().rotation(0).setDuration(300).start();
            menu.collapseMenu();
        }
    }
});

示例2 仿开心网播放器

接下来,我们来演示一下仿开心网播放器的例子。

<com.example.menu.MyMenu
    android:id="@+id/my_menu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:menu_type="player">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_my_menu_item_color="@color/colorPrimary"
        app:layout_my_menu_item_count="3">

        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginStart="10dp"
            android:layout_marginEnd="10dp"
            android:src="@drawable/ic_player_album_list"
            app:layout_my_menu_item_width="50dp"
            app:layout_my_menu_item_height="50dp"/>

        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginStart="10dp"
            android:layout_marginEnd="10dp"
            android:src="@drawable/ic_player_equalizer"
            app:layout_my_menu_item_width="50dp"
            app:layout_my_menu_item_height="50dp"/>

        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginStart="10dp"
            android:layout_marginEnd="10dp"
            android:src="@drawable/ic_player_sleep_off"
            app:layout_my_menu_item_width="50dp"
            app:layout_my_menu_item_height="50dp"/>
    </LinearLayout>
</com.example.menu.MyMenu>

我们可以在menu_type中设置菜单的显示类型,比如"player"表示播放器中的菜单。

menu.setType(TypedArrayUtils.getString(attr, R.styleable.MyMenu_layout_my_menu_type, ""));

结语

自定义ViewGroup能够让我们更加灵活地控制Android App中的布局效果,提升App的用户体验。在这里,我们通过自定义ViewGroup的方式实现了一个仿优酷菜单的效果,并实现了菜单的缩放动画。通过不断学习和实践,我们可以更加熟练地掌握Android自定义控件的技巧和方法。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Android自定义控件之仿优酷菜单 - Python技术站

(0)
上一篇 2023年6月25日
下一篇 2023年6月25日

相关文章

  • macbook外接显示器设置方法(新手入门贴)

    Macbook外接显示器设置方法(新手入门贴) 现在的Macbook已经越来越薄,而且一般没有提供常规HDMI接口,这也让很多人在外接显示器时感到困惑。实际上,连接Macbook外接显示器相对而言还是比较简单的,本篇将向大家详细介绍Macbook外接显示器的设置方法。 步骤一:准备工作 在进行Macbook外接显示器的设置之前,需要先准备好一些必要的硬件设备…

    其他 2023年3月28日
    00
  • 3dmax默认路径怎么自定义设置?

    当你在使用 3D Max 进行建模、渲染等操作时,它默认会保存和保留文件在一些特定的文件夹路径中。但是,如果你想要保存到自己的文件夹路径中却找不到合适的设置选项该怎么办呢? 下面就为大家详细讲解“3dmax默认路径怎么自定义设置”的完整攻略: 第一步:打开菜单栏 首先,我们需要打开 3D Max 的菜单栏。在菜单栏中,选择“自定义”选项卡,然后选择“首选项(…

    other 2023年6月25日
    00
  • Android实现Service重启的方法

    下面是详细讲解 Android 实现 Service 重启的方法的完整攻略。 什么是 Service 重启? Service 是 Android 中的一种组件,它可以在后台运行长时间的任务,即使应用退出或者被杀掉也能够继续运行。但是有时候,由于各种原因,Service 会被系统或者其他应用杀掉,这时候我们需要实现 Service 重启,让 Service 能…

    other 2023年6月27日
    00
  • 微软 Win11 全新现代任务管理器更多曝光功能体验:支持 App 健康、电池健康、新启动项

    微软 Win11 全新现代任务管理器功能体验攻略 微软近日将 Win11 改版任务管理器的一些新功能曝光了出来。这些新功能增强了 Win11 任务管理器的任务管理能力,包括支持 App 健康、电池健康、新启动项等,更精准了解系统状态和开机启动项。下面是这些新功能的详细说明: 支持 App 健康 Win11 任务管理器支持了 App 健康统计数据。在进程列表中…

    other 2023年6月25日
    00
  • uniapp微信小程序自定义导航栏的全过程

    下面是“uniapp微信小程序自定义导航栏的全过程”的完整攻略。 1. 添加自定义导航栏组件 在uni-app项目的 /components 目录下,新建一个名为 custom-nav 的自定义组件,在 custom-nav 组件的目录下新建一个名为 custom-nav.vue 的组件模板文件。在 custom-nav.vue 文件中,我们需要定义自定义导…

    other 2023年6月25日
    00
  • 戴尔笔记本开不了机怎么办 戴尔笔记本开机黑屏的解决方法

    完整攻略:戴尔笔记本开不了机怎么办 戴尔笔记本开机黑屏的解决方法 硬件问题 如果笔记本电脑一直处于黑屏状态,有可能是硬件出现了故障。以下是几种可能的硬件故障及解决方法。 1. 内存故障 如果你的笔记本电脑在启动时出现蓝屏或无法进入系统的情况,这可能是内存失败致使的。尝试以下解决方案: 查看内存是否牢固地安装在内存插槽中,如果情况不对请尝试重新插拔内存。 如果…

    other 2023年6月27日
    00
  • mysql存数组的实例代码和方法

    要在 MySQL 中存储数组,可以使用 JSON 格式来存储。下面是一些示例代码和方法: 方法1:使用JSON字段存储数组 可以创建一个名为 items 的 JSON 字段来存储数组。例如,我们有一个名为 order 的表格,希望存储每个订单的商品列表。可以创建一个名为 items 的 JSON 字段来存储商品列表,并使用以下代码插入一行新记录: INSER…

    other 2023年6月25日
    00
  • 什么是ssrssr有什么用如何使用使用ssr

    什么是 SSR, SSR 有什么用,如何使用 SSR? 什么是 SSR? SSR (ShadowsocksR) 是一种基于 Socks5 代理技术的网络加速工具。它通过对网络流量进行加密和伪装,可以有效地隐藏数据传输过程中的敏感信息,提高安全性和隐私保护。同时,SSR 还能够绕过国家级别的网络封锁和限制,帮助用户快速高效地访问被屏蔽的网站和服务。 SSR 有…

    其他 2023年3月29日
    00
合作推广
合作推广
分享本页
返回顶部