接着上一篇入门篇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侧滑菜单。有学习精神的一定要多多支持啊。
拜了个拜( ^_^ )/~~