转载请注明出处:http://blog.youkuaiyun.com/sctu_vroy/article/details/46947723
最近由于项目需要实现类似手机QQ左右侧滑菜单的页面,在网上找到很多资料都是通过fragment来实现的,比较麻烦。偶然机会,在慕课网看到鸿洋大神的QQ5.0侧滑菜单实现,受到启发,将其实现单边侧滑的自定义控件重构成双向侧滑菜单控件。
双向侧滑菜单实现的整体思路:
1)继承HorizontalScrollView
2)将左右菜单以及正文内容布局在SlidingMenu布局文件中
<com.example.double_slidingmenu.SlidingMenu
android:id="@+id/id_menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/img_frame_background"
android:scrollbars="none" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" >
<!-- 左菜单 -->
<include layout="@layout/left_menu" />
<!-- 正文内容 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/qq" >
</LinearLayout>
<!-- 右菜单 -->
<span style="white-space:pre"> </span><include layout="@layout/left_menu"/>
<span style="white-space: pre;"> </span></LinearLayout>
</com.example.double_slidingmenu.SlidingMenu>
3)SlidingMenu构造函数
<span style="white-space:pre"> </span>public SlidingMenu(Context context) {
super(context, null);
}
public SlidingMenu(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
mScreenWidth = metrics.widthPixels;
//dp转px
mMenuPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, metrics);
}
布局文件中使用自定义控件时,会自动调用带属性attrs的构造函数(非自定义属性)。构造函数首先获取手机屏幕宽度mScreenWidth,并将菜单侧滑后保留正文内容区域宽度(50dp)转化为px单位mMenuPadding。
4)重写view的onMeasure()和onLayout()方法
<span style="white-space:pre"> </span>@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(!once) {
mWapper = (LinearLayout) getChildAt(0);
mLeftMenu = (ViewGroup) mWapper.getChildAt(0);
mContent = (ViewGroup) mWapper.getChildAt(1);
mRightMenu = (ViewGroup) mWapper.getChildAt(2);
mMenuWidth = mLeftMenu.getLayoutParams().width = mRightMenu.getLayoutParams().width
= mScreenWidth - mMenuPadding;
half = mMenuWidth / 2;
mContent.getLayoutParams().width = mScreenWidth;
once = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed) {
this.scrollTo(mMenuWidth, 0);
}
}
onMeasure()方法在自定义控件中必须被重写,用于
测量view及其内容来确定view的宽度和高度。方法中实现对左右菜单以及正文内容部分的宽度设置。对于onLayout()方法,调用场景:在view给其孩子设置尺寸和位置时被调用;参数changed表示view尺寸发生变化,一旦changed为true,按一般习惯,应该把画面切回正文区域,此处调用ScrollView中的方法scrollTo(x,y)方法,一旦view有新的尺寸或位置时,隐藏左右菜单,只显示正文区域部分。
5)重写onTouchEvent()方法,实现隐藏、显示菜单功能
<span style="white-space:pre"> </span>@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_UP) {
int left = getScrollX();
if(left <= half) {
//显示左菜单
this.smoothScrollTo(0, 0);
}
else if(left > mMenuWidth + mScreenWidth / 2) {
//显示右菜单
this.smoothScrollTo(mScreenWidth + mMenuWidth - mMenuPadding, 0);
}
else {
//隐藏左右菜单
this.smoothScrollTo(mMenuWidth, 0);
}
return true;
}
return super.onTouchEvent(ev);
}
根据安卓源码,getScrollX()获取到的是HorizontalScrollView隐藏在左边部分的宽度值。获取手指按压抬起时的隐藏在view左边的宽度值,根据数值做判断,实现左右菜单显示和隐藏的效果。参数说明:half(菜单宽度的一半)。判断条件说明:以当前显示页面宽度一半为滑动阈值,显示左菜单较好理解,显示右菜单的条件是当getScrollX()大于已经隐藏的左菜单宽度+当前显示页面(正文)宽度一半。
6)重写ScrollView的onScrollChanged()方法,实现滑动动画和最终的隐藏效果
<span style="white-space:pre"> </span>/* 滚动发生时属性动画*/
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
//l = getScrollX()
/**
* 区别1:内容区域1.0~0.7 缩放的效果 scale : 1.0~0.0 0.7 + 0.3 * scale
*
* 区别2:菜单的偏移量需要修改
*
* 区别3:菜单的显示时有缩放以及透明度变化 缩放:0.7 ~1.0 1.0 - scale * 0.3 透明度 0.6 ~ 1.0
* 0.6+ 0.4 * (1- scale) ;
*
*/
if(l <= mMenuWidth ) {
isLeft = true;
}
else if(l > mMenuWidth) {
isLeft = false;
}
float lScale = l * 1.0f / mMenuWidth; //1.0 ~ 0.0
float rScale = (mScreenWidth + mMenuWidth - mMenuPadding - l) * 1.0f / mMenuWidth;
float rightScale = 0.7f + 0.3f * lScale;
float leftScale = 1.0f - lScale * 0.3f;
float leftAlpha = 0.6f + 0.4f * (1 - lScale);
float rightScale_ = 0.7f + 0.3f * rScale;
float leftScale_ = 1.0f - rScale * 0.3f;
float leftAlpha_ = 0.6f + 0.4f * (1 - rScale);
// 调用属性动画,设置TranslationX
if(isLeft) {
ViewHelper.setTranslationX(mLeftMenu, mMenuWidth * lScale * 0.7f);
ViewHelper.setScaleX(mLeftMenu, leftScale);
ViewHelper.setScaleY(mLeftMenu, leftScale);
ViewHelper.setAlpha(mLeftMenu, leftAlpha);
// 设置content的缩放的中心点
ViewHelper.setPivotX(mContent, 0);
ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);
ViewHelper.setScaleX(mContent, rightScale);
ViewHelper.setScaleY(mContent, rightScale);
}
else {
ViewHelper.setTranslationX(mRightMenu, mMenuWidth * rScale * 0.7f);
ViewHelper.setScaleX(mRightMenu, leftScale_);
ViewHelper.setScaleY(mRightMenu, leftScale_);
ViewHelper.setAlpha(mRightMenu, leftAlpha_);
// 设置content的缩放的中心点
ViewHelper.setPivotX(mContent, mContent.getWidth());
ViewHelper.setPivotY(mContent, mContent.getHeight() / 2);
ViewHelper.setScaleX(mContent, rightScale_);
ViewHelper.setScaleY(mContent, rightScale_);
}
}
为了兼容Android3.0以下版本,此处属性动画调用nineoldandroids-2.4.0的jar包,实现动画效果与Android3.0以上一样。此处通过对比getSrollX()和mMenuWidth的大小,判断当前滑动操作是向哪个方向。需要注意的是:
(1)左滑和右滑的缩放比例的实现不一致,lScale逻辑较简单,rScale的逻辑(由于mMenuPadding+mMenuWidth=mScreenWidth,一开始getScrollX(),也即l=mMenuWidth,则mScreenWidth-mMenuPadding+mMenuWidth=2个mMenuWidth=2个l,所以初值为1.0;随着手指向左滑动,getScrollX()越来越大,则差值越来越接近于0);
(2)设置content的缩放中心点需要注意,显示左菜单时中心点须设置在content的左纵边中心点;显示右菜单时则必须设置在content的右纵边中心点,否则默认缩放以content区域中心为中心点,达不到我们需要的效果。
最终效果:
最后感谢鸿洋大神视频的启发!!!