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日

相关文章

  • javascript实现十秒钟后注册按钮可点击的方法

    Sure! Here’s a step-by-step guide on how to implement a method in JavaScript that enables a registration button to become clickable after ten seconds: HTML Markup: Start by creatin…

    other 2023年7月29日
    00
  • 详解C语言中的常量指针和指针常量

    详解C语言中的常量指针和指针常量 常量指针 常量指针是指一个指针指向的内存地址不能被修改,但是它指向的内存地址所存储的值可以被修改。可以通过 const 关键字来指示这个指针是常量,例如: int a = 10; const int *p = &a; 上面的代码中,p 是一个常量指针,它指向一个整型数 a,但是不能通过 p 修改 a 的值,例如: *…

    other 2023年6月27日
    00
  • 新手快速上手webpack4打包工具的使用详解

    新手快速上手webpack4打包工具的使用详解 介绍 Webpack是一个非常流行的JavaScript模块打包工具。 它可以将你的JavaScript代码和其他资源,如样式表、图片等打包到一个或多个bundle中,在你的项目中进行使用。 Webpack 4提供了很多新的特性和改进,这些特性和改进包括更好的性能、更简单的配置和更好的插件系统。本文将介绍如何使…

    other 2023年6月27日
    00
  • win7下配置GO语言环境 + eclipse配置GO开发

    1. 配置GO语言环境 1.1 下载GO语言安装包 去https://golang.google.cn/dl/ ,根据自己的操作系统版本下载对应的安装包。 示例:下载Windows 64位的安装包。 1.2 安装GO语言 双击安装包,按照提示一步一步安装即可。安装完成后,检查系统环境变量中是否已经配置好了GOPATH。 示例:在安装过程中,按照默认设置来安装…

    other 2023年6月27日
    00
  • css布局中的百分比布局

    CSS布局中的百分比布局 在CSS布局中,百分比布局是一种常用的布局方式,它可以根据父元素的大小自适应地调整子元素的大小和位置。本攻略将详细介绍CSS布局中的百分比布局,包括基本概念、使用方法和示例说明。 基本概念 百分比布局是一种基于百分比的布局方式,它可以根据父元素的大小自适应地调整子元素的大小和位置。在百分比布局中,我们可以使用百分比来设置元素的宽度、…

    other 2023年5月6日
    00
  • win7系统重装搜狗输入法提示请您先重启电脑再进行操作的原因及解决方法

    原因解释 在 Windows 7 系统中,搜狗输入法作为一款第三方输入法软件,需要依赖于操作系统本身的一些模块和服务来运行。因此,在进行系统重装或者修改系统相关配置时,可能会影响到搜狗输入法的正常工作,导致出现提示“请您先重启电脑再进行操作”的情况。 具体来说,当操作系统或者其他应用程序对搜狗输入法所依赖的模块或服务进行更新、升级、安装或者卸载等操作时,搜狗…

    other 2023年6月27日
    00
  • Python面向对象编程之继承与多态详解

    Python面向对象编程之继承与多态详解 1. 继承 继承是面向对象编程中的一个重要概念,它允许子类继承父类的属性和方法。在 Python 中,我们可以通过以下方式来实现继承: class Person: def __init__(self, name, age): self.name = name self.age = age def introduce(…

    other 2023年6月26日
    00
  • c++学习(八)(c语言部分)之图形库

    下面是关于 C++ 学习(八)(C 语言部分)之图形库的完整攻略,包含两个示例说明。 图形库 在 C 语言中,你可以使用图形库来创建基本的图形界面。图形库是一个软件库,它提供了一组函数,可以用来绘制图形、文本和其他图形元素。在 Windows 系统中,你可以使用 WinAPI 来创建图形界面,在 Linux 系统中,你可以使用 X Window System…

    other 2023年5月8日
    00
合作推广
合作推广
分享本页
返回顶部