QQ5.0侧滑菜单实现
来源:互联网 发布:服务器软件架构 编辑:程序博客网 时间:2024/05/24 04:57
本文所用到的所有图片资源均存放在“我的相册—>QQ5.0侧滑资源”中
概览
本文将实现Android端 QQ 5.0 App的主界面,在实现的过程中,还会顺带着实现两个侧滑效果的主界面。最终的主界面预览效果如下所示:
分析如下:
主界面包含两部分内容,左边是菜单部分,右边是内容部分,并呈现水平摆放;
界面具有侧滑效果,需要将这两部分内容放在一个具有水平滚动功能的控件中;
若将左侧的菜单只滑出一小部分,则在松开手指时菜单依然平滑隐藏;反之,则将菜单项平滑显示出来,内容区域缩小至右侧。
分析
为了上述实现效果,我们需要一个容器控件将菜单和内容两部分装入,自然想到了自定义ViewGroup来实现,但是继承ViewGroup自定义水平滚动控件需要不断在onTouchEvent的MOVE动作中根据手指当前的滑动位置改变ViewGroup的marginLeft属性、并需要处理滑动冲突问题、同时还要处理手指抬起时菜单回弹或显示的动画效果,这一般使用Scroller辅助类。看得出来,实现起来比较麻烦。
搜遍API,发现Android中提供了一个HorizontalScrollView这个控件,它自带水平滚动属性 —— MOVE事件不再需要自己处理;同时也不再需要处理滑动冲突 —— 该控件中完全可以放一个ListView。我们只需要在onTouchEvent的UP事件中判断滑动的位置就能完美实现上图的效果。
一、实现水平滑动界面
在实现最终效果之前,我们先来实现一个水平滑动的效果。它的效果如下:
首先是菜单的布局XML代码,它包含五个item,同时还有一个背景图,代码如下:
<!-- left_menu.xml --><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/img_frame_background"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_img_1" android:layout_width="50dp" android:layout_height="50dp" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:background="@drawable/img_one" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_toRightOf="@id/iv_img_1" android:text="第一个item" android:textColor="#FFFFFF" android:textSize="20sp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_img_2" android:layout_width="50dp" android:layout_height="50dp" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:background="@drawable/img_two" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_toRightOf="@id/iv_img_2" android:text="第二个item" android:textColor="#FFFFFF" android:textSize="20sp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_img_3" android:layout_width="50dp" android:layout_height="50dp" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:background="@drawable/img_three" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_toRightOf="@id/iv_img_3" android:text="第三个item" android:textColor="#FFFFFF" android:textSize="20sp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_img_4" android:layout_width="50dp" android:layout_height="50dp" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:background="@drawable/img_four" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_toRightOf="@id/iv_img_4" android:text="第四个item" android:textColor="#FFFFFF" android:textSize="20sp" /> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_img_5" android:layout_width="50dp" android:layout_height="50dp" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_marginTop="20dp" android:background="@drawable/img_five" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="20dp" android:layout_toRightOf="@id/iv_img_5" android:text="第五个item" android:textColor="#FFFFFF" android:textSize="20sp" /> </RelativeLayout> </LinearLayout></RelativeLayout>
接着是主界面XML,HorizontalScrollView中包含菜单和内容布局(内容布局就是一张图片),代码如下:
<!-- activity_main_xml --><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <HorizontalScrollView android:id="@+id/smv_custom_sliding_menu" android:layout_width="match_parent" android:layout_height="match_parent" vanpersie:menuRightPadding="100dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal"> <include layout="@layout/left_menu" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/qq" android:orientation="horizontal"> </LinearLayout> </LinearLayout> </HorizontalScrollView></RelativeLayout>
需要注意的是,HorizontalScrollView只能包含一个直接子控件。
继续,自定义一个SlidingMenuView ,它于继承HorizontalScrollView:
public class SlidingMenuView extends HorizontalScrollView { public static final String TAG = "SlidingMenuView"; //HorizontalScrollView包裹的唯一一个子View private LinearLayout mWrapper; //菜单栏 private ViewGroup mMenu; //内容区域 private ViewGroup mContent; //屏幕宽度 单位px private int mScreenWidth; //Menu与屏幕右侧的距离 单位 dp,默认为50dp private int mMenuRightPadding = 50; //只让onMeasure调用一次 private boolean once = false; //menu的宽度 private int mMenuWidth; //切换菜单隐藏及显示 private boolean isOpen; /** * 当未自定义属性时 系统将调用该构造方法 * * @param context * @param attrs */ public SlidingMenuView(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * 自定义且使用了自定义属性 * * @param context * @param attrs * @param defStyleAttr */ public SlidingMenuView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //获取自定义的属性 TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SlidingMenuView, defStyleAttr, 0); int n = ta.getIndexCount(); for (int i = 0; i < n; ++i) { int attr = ta.getIndex(i); switch (attr) { case R.styleable.SlidingMenuView_menuRightPadding: //将默认的50dp转化为px mMenuRightPadding = (int) ta.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) 50, context.getResources().getDisplayMetrics())); break; } } ta.recycle(); //获取屏幕宽度 WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; } public SlidingMenuView(Context context) { this(context, null); } /** * 设置子View的宽高、设置自己的宽高 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (!once) { //获取HorizontalScrollView中的为一个元素 mWrapper = (LinearLayout) getChildAt(0); //获取菜单 mMenu = (ViewGroup) mWrapper.getChildAt(0); //获取内容 mContent = (ViewGroup) mWrapper.getChildAt(1); //设置菜单的宽度为屏幕的宽度-右边距 mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding; mContent.getLayoutParams().width = mScreenWidth; once = true; } } /** * 设置SlidingMenuView的位置 * 应当将Content内容显示,将Menu隐藏在左侧 需设置偏移量实现 * * @param changed * @param l * @param t * @param r * @param b */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed) { //x为正值时滚动条向右移动 内容向左移动 this.scrollTo(mMenuWidth, 0); } }}
分析:
1、首先需要在activity_main.xml布局文件中的HorizontalScrollView标签修改为com.demo.lenovo.qqslidingmenu.SlidingMenuView;
2、还需要为SlidingMenuView自定义一个属性,这个属性表示当显示菜单项时,菜单项与右侧屏幕的距离,也就是内容区域显示的剩余区域的宽度大小:
在res/values/attr.xml中自定义属性,format为dimension,表示该属性可以设置的单位为 dp、sp、px 等:
<resources> <attr name="menuRightPadding" format="dimension" /> <declare-styleable name="SlidingMenuView"> <attr name="menuRightPadding" /> </declare-styleable></resources>
在com.demo.lenovo.qqslidingmenu.SlidingMenuView标签下声明自定义属性的命名空间,格式有两种:
第一种是:
xmlns:任意名字=”http://schemas.android.com/apk/res-auto”
第二种是:
xmlns:任意名字=”http://schemas.android.com/apk/res/包名”
即:
xmlns:vanpersie=”http://schemas.android.com/apk/res/com.demo.lenovo.qqslidingmenu”
并设置属性值为100dp:
vanpersie:menuRightPadding="100dp"
最后在代码在三个参数的构造方法中获取该属性值,我们将该属性值的默认值设为50dp,并将单位转化为px:
3、在构造方法中通过DisplayMetrics 类获取屏幕宽度:
//获取屏幕宽度 WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels;
4、重写onMeasure()方法,在该方法中,需要测量ViewGroup中子控件的宽和高以及自己的宽和高;由于只需要测量一次,故设置一个标志位once,控制onMeasure()方法只回调一次;
5、重写onLayout()方法,该方法用于设置SlidingMenuView的位置,初始时,我们将菜单隐藏;故需要调用scrollTo(mMenuWidth, 0);,将菜单滑出屏幕;
6、最后,重写onTouchEvent()方法,在UP事件中,监听水平滚动的位置,当getScrollX() 大于菜单宽度的一半时,隐藏菜单;反之,显示菜单:
case MotionEvent.ACTION_UP: int scrollX = getScrollX(); //这时应将菜单隐藏 if (scrollX >= (mMenuWidth / 2)) { this.smoothScrollTo(mMenuWidth, 0); } else { smoothScrollTo(0, 0); }
到此,水平滑动效果实现完成。
二、实现抽屉效果界面
抽屉式效果入下:
为了实现抽屉效果,需要重写onScrollChanged()方法,该方法的第一参数就是scrollX的值,该值表示菜单项的左侧与屏幕左侧的距离,而属性动画的setTranslationX(float translationX) 方法中参数translationX正好表示控件的水平偏移量,故只需加入这几行代码,抽屉效果即可实现:
@Override protected void onScrollChanged(final int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); mMenu.setTranslationX((float) (l));}
三、带有缩放、透明度渐变的效果最终界面
最终效果:
从最终效果界面看出:
1、菜单区域在横向和纵向有一个大概0.7 ~ 1.0 的缩放效果;
2、菜单区域透明度有一个大概0.6 ~ 1.0 的变化;
3、内容区域有在横向和纵向有一个大概 1.0 ~ 0.7的缩放效果;
4、内容区域的缩放中心默认为View的中心,而此时的缩放中心应该在View的左边缘的中心。
5、当菜单显示时,点击内容区域的参与部分,内容区域应显示,菜单隐藏。
我们需要一个梯度变化值scale,该值根据onScrollChanged的第一个参数l(即getScrollX())而来:
float scale = l * 1.0f / mMenuWidth; // 1.0 ~ 0.0
接着,根据不同的该值计算不同的梯度:
float rightScale = 0.7f + 0.3f * scale; // 1.0 ~ 0.7 float leftScale = 1.0f - scale * 0.3f; //0.7 ~ 1.0 float leftAlpha = 0.6f + 0.4f * (1 - scale); // 0.6 ~ 1.0
并利用属性动画进行设置:
mMenu.setScaleX(leftScale); mMenu.setScaleY(leftScale); mMenu.setAlpha(leftAlpha); mContent.setPivotX(0); mContent.setPivotY(mContent.getHeight() / 2); mContent.setScaleX(rightScale); mContent.setScaleY(rightScale);
最后为内容区域设置监听:
mContent.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (l == 0) { SlidingMenuView.this.smoothScrollTo(mMenuWidth, 0); } } });
注意:为了保证滑动时背景不发生改变,我们应将最初设置在left_menu.xml的background背景图设置到activity_main.xml中。
至此,所有效果已经完全实现
总结
要点:
一、自定义ViewGroup的方法:
1、选择合适的构造方法,获得一些需要用到的值;
2、onMeasure 计算子View的宽和高,以及设置自己的宽和高;
3、onLayout 决定子View的布局的位置;
4、需要监听ViewGroup的滑动事件时,应重写onTouchEvent()方法;否则无需重写该方法。
二、构造方法
1、有三个构造方法:一般情况为 使用一个参数的调用两个的, 两个的调用三个的;
2、一个参数的构造方法用于在代码中动态new时初始化(如Button btn = new Button(context));
3、两个参数的构造方法用于没有设置自定义属性时的初始化;
4、三个参数的构造方法用于设置自定义属性、并在代码中使用该属性时的初始化。
三、自定义属性
1、在attr.xml中自定义属性并指明其单位;
2、布局文件中正确书写命名空间的值 xmlns;
3 、在三个参数的构造方法中获取
四、属性动画 缩放、透明度 等。
- 仿QQ5.0侧滑菜单实现
- QQ5.0侧滑菜单实现
- QQ5.0 新版 侧滑菜单效果实现!
- android 实现仿QQ5.0 侧滑菜单
- SlidingPaneLayout实现QQ5.0侧滑菜单(perfect)
- 高仿QQ5.0的侧滑菜单的实现
- qq5.0侧滑抽屉式菜单的实现
- 自定义View以及QQ5.0侧滑菜单实现
- 仿QQ5.0侧滑菜单【AndroidResideMenu】
- 仿QQ5.0侧滑菜单ResideMenu
- Android QQ5.0侧滑菜单
- 仿QQ5.0的侧滑菜单
- 仿QQ5.0侧滑菜单ResideMenu
- 仿QQ5.0侧滑菜单ResideMenu
- 仿QQ5.0侧滑菜单——抽屉式菜单(简单地实现)
- 抽屉式菜单-仿QQ5.0侧滑菜单
- 使用DrawerLayout实现QQ5.0侧拉菜单效果
- Android 自定义SlidingMenu 实现QQ5.0侧滑菜单动画效果
- 提取股票收益率和日期&特别是日期转化
- css3动画的使用(@keyframes)
- XE开发酒店手机APP
- 上传jpg图片,莫名报org.apache.struts2.dispatcher.Dispatcher
- 如何用消息系统避免分布式事务?
- QQ5.0侧滑菜单实现
- Spring Boot 常规错误一览及解决方案
- RabbitMQ用户角色及权限控制
- 初识linux,下载linux
- VC++ lib和dll的区别与使用
- scala关键字
- 算法:Reverse Bits
- javascript原型&闭包
- hdu 2426 Interesting Housing Problem【KM】