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 、在三个参数的构造方法中获取

四、属性动画 缩放、透明度 等。

0 1
原创粉丝点击