Android自定义控件(二):提高篇

接着上一篇入门篇Android自定义控件(一):入门篇,相信对于Android自定义这一块已经有一个初步的理解了,但是想要实现更为高大上的功能也许还是“心有余而力不足”,上篇是自定义的View,那么这次就来详细地跟着例子自定义ViewGroup,时不我待,即刻动身。


提高

今天我们最要实现的一个烂大街的侧滑菜单,那么既然是自定义,我们可以自定义侧滑的方式,还有菜单完全显示的程度以及各种你能想到的好玩的。先看看最终实现的效果:

这里写图片描述 这里写图片描述 这里写图片描述

哈哈,个人感觉还是很不错的,那么有了目标,就按部就班的开始工作吧。

入门篇中开头就有提到自定义控件的步骤,中间有几个步骤是用于自定义ViewGroup的,那么现在就来各个详细说明吧。

首先在values文件下新建一个attrs.xml文件,用于申明我们需要编写的自定义控件的属性,这里当然就是侧滑菜单所需要的属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MySlidingMenu">
        <attr name="menu_right_padding" format="dimension"/>
        <attr name="sliding_mode" format="enum">
            <enum name="normal" value="0" />
            <enum name="drawer" value="1" />
            <enum name="qq" value="2" />
        </attr>
    </declare-styleable>
</resources>

这里既然是一个横向侧滑的菜单,那么就采用继承HorizontalScrollView这个类,可能有些人不是很了解这个类,但是ScrollView都很熟悉吧,前者就是一个纵向的ScrollView。这样视图滚动都已经不需要我们写了,节省了大量代码,只需要在滚动作出一定处理就行。

为了博客的质量,这里就不贴出完整代码了,需要的可以在文末的github地址中下载完整项目。创建MySlidingMenu类继承自HorizontalScrollView,然后在带有defStyleAttr参数的构造方法中获取到xml中编写的自定义属性:

 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySlidingMenu);
        for (int i = 0; i < a.getIndexCount(); i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.MySlidingMenu_menu_right_padding:
                    // 获取菜单right_padding,默认为50dp
                    mMenuRightPadding = a.getDimensionPixelSize(attr,
                            (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources().getDisplayMetrics()));
                    break;
                case R.styleable.MySlidingMenu_sliding_mode:
                    // 获取侧滑模式,默认是普通侧滑模式
                    slidingMode = a.getInteger(attr, NORMAL);
                    break;
            }
        }
        a.recycle();

然后重写onMeasure方法,这个里面主要是测量子view的宽高同时设置自己的宽高

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!once) {
            LinearLayout mSlidingLayout = (LinearLayout) getChildAt(0);
            leftMenu = (ViewGroup) mSlidingLayout.getChildAt(0);
            rightContent = (ViewGroup) mSlidingLayout.getChildAt(1);
            leftMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;
            rightContent.getLayoutParams().width = mScreenWidth;
            once = true;
        }
    }

注:为了简化代码,方便实现最终效果采用的上述写法,这样并不符合规范。感兴趣的可以通过文末的github地址,来获取完整代码,自行研究。

有了子view和自身的宽高之后,接下来就是手动布局了,重写onlayout,这个方法主要是用于按照自己的想法,放置当前viewgroup下的子view

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            scrollTo(leftMenu.getLayoutParams().width, 0);
        }
    }

大家可以看到这个里面只写了一个scrollTo的方法,这个就是将当前的viewgroup视图滚动到一定的位置,传入的两个参数分别对应x、y轴,只不过要注意正负的方向,貌似与正常的四象限是相反的,懵- -。。。

这样初始化的步骤已经完成了,现在要完成交互这一块了。把事情先简单化,若手指抬起的时候菜单展现的宽度超过了菜单宽度的1/2,那么就展示菜单,否则隐藏。基于android事件交互的基础上,我们重写onTouchEvent方法:

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_UP:
                if (getScrollX() >= leftMenu.getLayoutParams().width / 2) {
                    smoothScrollTo(leftMenu.getLayoutParams().width, 0);
                } else {
                    smoothScrollTo(0, 0);
                }
                return true;
        }
        return super.onTouchEvent(ev);
    }

smoothScrollTo闻其名知其意,这个就相当于一个视图滚动的动画,这样又节省了一部分代码,真的是舒服啊啊啊~~~

做到这里,就可以跑跑程序试试了,对于满怀期待的小白,效果还是很不错的,一个普通的侧滑已经完成了。但是文章的最开头都忘了么,自定义了一些属性,这可不能浪费,我们自定义了三种侧滑动画:普通,抽屉,仿QQ。再想想,视图滑动的时候会不停触发onScrollChanged方法,那么我们重写这个方法:

@Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (slidingMode == NORMOAL) {
            return;
        } else if (slidingMode == DRAWER) {
            //抽屉动画
            ObjectAnimator menuTranlationAnimator = ObjectAnimator.ofFloat(leftMenu, "translationX", l);
            menuTranlationAnimator.setDuration(0);
            menuTranlationAnimator.start();
            return;
        }
        float scale = l * 1.0f / leftMenu.getWidth();
        // 菜单动画
        ObjectAnimator leftMenuScaleAnimatorX = ObjectAnimator.ofFloat(leftMenu, "scaleX", 1f, 0.7f + 0.3f * (1 - scale));
        ObjectAnimator leftMenuScaleAnimatorY = ObjectAnimator.ofFloat(leftMenu, "scaleY", 1f, 0.7f + 0.3f * (1 - scale));
        ObjectAnimator menuAlphaAnimator = ObjectAnimator.ofFloat(leftMenu, "alpha", leftMenu.getAlpha(), 0.7f + 0.3f * (1 - scale));
        leftMenu.setPivotX(0);
        leftMenu.setPivotY(leftMenu.getHeight() / 2);
        ObjectAnimator menuTranlationAnimator = ObjectAnimator.ofFloat(leftMenu, "translationX", (int) ((0.3 * scale + 0.7) * l));
        AnimatorSet menuSet = new AnimatorSet();
        menuSet.setDuration(0);
        menuSet.play(leftMenuScaleAnimatorY).with(menuAlphaAnimator).with(menuTranlationAnimator).with(leftMenuScaleAnimatorX);
        menuSet.start();
        // 内容动画
        ObjectAnimator contentScaleAnimatorY = ObjectAnimator.ofFloat(rightContent, "scaleY", 1f, 0.7f + 0.3f * scale);
        ObjectAnimator contentScaleAnimatorX = ObjectAnimator.ofFloat(rightContent, "scaleX", 1f, 0.7f + 0.3f * scale);
        rightContent.setPivotX(0);
        rightContent.setPivotY(rightContent.getHeight() / 2);
        AnimatorSet contentSet = new AnimatorSet();
        contentSet.play(contentScaleAnimatorY).with(contentScaleAnimatorX);
        contentSet.setDuration(0);
        contentSet.start();
    }

通过获取的自定义属性slidingMode,来编写对应的动画代码,动画这块就不多说,主要是这个重写的思想。看看怎么在页面布局xml代码中引用我们自己的控件咧:

<com.cjt_pc.qq_slidingmenu.MySlidingMenu
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimaryDark"
        menu:menu_right_padding="80dp"
        menu:sliding_mode="qq">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <LinearLayout
                // 菜单
            </LinearLayout>

            <LinearLayout
               // 内容
            </LinearLayout>
        </LinearLayout>
    </com.cjt_pc.qq_slidingmenu.MySlidingMenu>

引用自定义控件的时候,千万不要忘了在开头申明xmlns:menu="http://schemas.android.com/apk/res-auto"。然后继承的HorizontalScrollView类似ScrollView,只能接受一个子View,所以在菜单和内容的外面包裹了一层LinearLayout,注意orientation是horizontal的。

good,做到这里就基本上做完了啊,当初我也是十分的兴奋,感觉牛气冲天- -。但是真正到应用上面,还是存在些距离的。例如手指滑动速率的判断,菜单展示的时候屏蔽内容布局的事件,onMeasure的规范化,等等,这些都是很重要的。嘿嘿,我已经做完了,贴上对应github的地址了:仿QQ侧滑菜单。有学习精神的一定要多多支持啊。

拜了个拜( ^_^ )/~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值