仿QQ侧滑菜单

效果图

这里写图片描述

这里写图片描述

自定义控件


/**
 * 自定义侧滑控件
 * Created by xiaoyehai on 2016/11/29.
 */
public class SlidingMenu extends FrameLayout {

    private View menuView; //菜单view

    private View mainView; //主界面view

    /**
     * ViewDragHelper:它主要用于处理ViewGroup中对子View的拖拽处理,
     * 本质是对触摸事件的解析类;
     */
    private ViewDragHelper viewDragHelper;

    private int width;

    private float dragRange; //拖拽范围

    //    private FloatEvaluator floatEvaluator; //float计算器
    //    private IntEvaluator intEvaluator; //int计算器

    //定义状态常量:枚举
    enum DragState {
        Open, Close;
    }

    //当前SlidingMenu的状态,默认关闭
    private DragState currentState = DragState.Close;

    public SlidingMenu(Context context) {
        this(context, null);
    }

    public SlidingMenu(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    /**
     * 获取SlidingMenu的状态
     *
     * @return
     */
    public DragState getCurrentState() {
        return currentState;
    }

    private void init() {
        viewDragHelper = ViewDragHelper.create(this, callback);
        //        floatEvaluator = new FloatEvaluator();
        //        intEvaluator = new IntEvaluator();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //简单的异常处理,该控件只能有2个布局
        if (getChildCount() != 2) {
            throw new IllegalArgumentException("SlidingMenu only have 2 children!");
        }
        menuView = getChildAt(0);
        mainView = getChildAt(1);
    }

    /**
     * 该方法在onMeasure()执行完后执行,可以在该方法初始化自己和子View的宽高
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = getMeasuredWidth();
        dragRange = width * 0.6f;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 让ViewDragHelper帮我们判断是否应该拦截
        return viewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 将触摸事件交给ViewDragHelper来解析处理
        viewDragHelper.processTouchEvent(event);
        return true;
    }

    /**
     * 回调类
     */
    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         * 用于判断是否捕获当前child的触摸事件
         * @param child 当前触摸的子View
         * @param pointerId
         * @return true:捕获并解析  false:不处理
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == menuView || child == mainView;
        }

        /**
         * 获取view水平方向的拖拽范围
         * @param child
         * @return
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return (int) dragRange;
        }

        /**
         * 控制child在水平方向的移动
         * @param child
         * @param left 当前child的即将移动到的位置,left=chile.getLeft()+dx
         * @param dx 本次child水平方向移动的距离
         * @return
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (child == mainView) {
                if (left < 0) { //限制mainView左边界
                    left = 0;
                }
                if (left > dragRange) { //限制mainView右边界
                    left = (int) dragRange;
                }
            } else if (child == menuView) { //menuView不可以滑动,设置left = left - dx;
                //滑动menuView的时候让menuView不要移动,如果left = left - dx,这样做的话menuView无法移动,
                //就不能让maiView伴随menuView移动
                //left = left - dx;
            }
            return left;
        }

        /**
         * 当child的位置改变的时候执行,一般用来做其他子View跟随该view移动
         * @param changedView
         * @param left
         * @param top
         * @param dx
         * @param dy
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            if (changedView == menuView) {
                //固定menuView,不让其移动
                menuView.layout(0, 0, menuView.getMeasuredWidth(), menuView.getMeasuredHeight());

                int newLeft = mainView.getLeft() + dx;
                if (newLeft < 0) {
                    newLeft = 0;
                }
                if (newLeft > dragRange) { //限制mainView右边界
                    newLeft = (int) dragRange;
                }
                //滑动menuView的时候让mainView跟着移动
                mainView.layout(newLeft, mainView.getTop() + dy,
                        newLeft + mainView.getMeasuredWidth(), mainView.getBottom() + dy);
            }
            //计算mainView滑动的百分比
            float fraction = mainView.getLeft() / dragRange;
            //执行伴随动画
            executeAnim(fraction);

            //更改状态,接口回调
            if (fraction == 0 && currentState != DragState.Close) {
                //更改为关闭状态
                currentState = DragState.Close;
                if (listener != null) {
                    listener.onClose();
                }
            } else if (fraction == 1f && currentState != DragState.Open) {
                //更改为打开状态
                currentState = DragState.Open;
                if (listener != null) {
                    listener.onOpen();
                }
            }
            //将fraction暴露给外界
            if (listener != null) {
                listener.onDraging(fraction);
            }
        }

        /**
         * 手指抬起的执行该方法
         * @param releasedChild
         * @param xvel
         * @param yvel
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (mainView.getLeft() < dragRange / 2) {
                //在左半边,平滑动画
                close();
            } else {
                //在右半边
                open();
            }

            //处理用户的稍微滑动
            if (xvel > 200 && currentState != DragState.Open) {
                open();
            } else if (xvel < -200 && currentState != DragState.Close) {
                close();
            }
        }
    };

    /**
     * 打开菜单
     */
    public void open() {
        viewDragHelper.smoothSlideViewTo(mainView, (int) dragRange, mainView.getTop());
        ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
    }

    /**
     * 关闭菜单
     */
    public void close() {
        viewDragHelper.smoothSlideViewTo(mainView, 0, mainView.getTop());
        ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
    }

    /**
     * 执行伴随动画
     *
     * @param fraction
     */
    private void executeAnim(float fraction) {
        //缩小mainView
        // mainView.setScaleX(1 - fraction * 0.2f);//0.8-1
        //mainView.setScaleY(1 - fraction * 0.2f);

        ViewHelper.setScaleX(mainView, 1 - fraction * 0.2f);
        ViewHelper.setScaleY(mainView, 1 - fraction * 0.2f);

        //menuView移动动画:-menuView.getMeasuredWidth() / 2    0
        ViewHelper.setTranslationX(menuView, -menuView.getMeasuredWidth() / 2 + fraction * menuView.getMeasuredWidth() / 2);

        //menuView放大动画
        ViewHelper.setScaleX(menuView, 0.5f);
        ViewHelper.setScaleY(menuView, 0.5f);
        ViewHelper.setScaleX(menuView, 0.5f + 0.5f * fraction);
        ViewHelper.setScaleY(menuView, 0.5f + 0.5f * fraction);

        //menuView透明度动画
        ViewHelper.setAlpha(menuView, 0.3f);
        ViewHelper.setAlpha(menuView, 0.3f + 0.7f * fraction);

        //给SlidingMenu的背景添加黑色的遮罩效果:menuView拉出来的时候背景慢慢变淡
        getBackground().setColorFilter((Integer) ColorUtil.evaluateColor
                (fraction, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (viewDragHelper.continueSettling(true)) {
            //如果动画没有结束,继续执行动画
            ViewCompat.postInvalidateOnAnimation(SlidingMenu.this);
        }
    }

    private onDragStateChangeListener listener;

    public void setonDragStateChangeListener(onDragStateChangeListener listener) {
        this.listener = listener;
    }

    //接口
    public interface onDragStateChangeListener {

        //打开的回调
        void onOpen();

        //关闭的回调
        void onClose();

        //正在拖拽中的回调
        void onDraging(float fraction);
    }
}

使用


/**
 * QQ侧滑菜单
 */
public class MainActivity extends AppCompatActivity {

    private ListView menu_listView, main_listview;

    private SlidingMenu mSlidingMenu;

    private ImageView ivHead;

    private MyLinearLayout myLinearLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        findViews();
        init();
        initListener();
    }

    private void init() {
        menu_listView.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, Constant.sCheeseStrings) {
            //重写getView(),可改变字体颜色
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView textView = (TextView) super.getView(position, convertView, parent);
                textView.setTextColor(Color.WHITE);
                return textView;
            }
        });

        main_listview.setAdapter(new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, Constant.NAMES) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View view = super.getView(position, convertView, parent);
                //先缩小view
                ViewHelper.setScaleX(view, 0.5f);
                ViewHelper.setScaleY(view, 0.5f);

                //以属性动画放大
                ViewPropertyAnimator.animate(view).scaleX(1f).setDuration(350).start();
                ViewPropertyAnimator.animate(view).scaleY(1f).setDuration(350).start();
                return view;
            }
        });
    }

    private void initListener() {
        mSlidingMenu.setonDragStateChangeListener(new SlidingMenu.onDragStateChangeListener() {
            @Override
            public void onOpen() {
                Log.i("info", "onOpen");
                //打开的时候让menu_listView随机选中一个条目
                menu_listView.smoothScrollToPosition(new Random().nextInt(menu_listView.getCount()));
            }

            @Override
            public void onClose() {
                Log.i("info", "onClose");
                //关闭的时候让主界面左上角的图片抖一下
                TranslateAnimation animation = new TranslateAnimation(
                        Animation.RELATIVE_TO_SELF, 0,
                        Animation.RELATIVE_TO_SELF, 0.5f,
                        Animation.RELATIVE_TO_SELF, 0,
                        Animation.RELATIVE_TO_SELF, 0);
                animation.setDuration(500);
                animation.setInterpolator(new CycleInterpolator(4)); //设置循环移动次数
                ivHead.startAnimation(animation);

            }

            @Override
            public void onDraging(float fraction) {
                Log.i("info", "onDraging" + fraction);
                ivHead.setAlpha(1 - fraction);
            }
        });
        //把SlidingMenu设置给myLinearLayout
        myLinearLayout.setSlidingMenu(mSlidingMenu);
    }

    private void findViews() {
        menu_listView = (ListView) findViewById(R.id.menu_listview);
        main_listview = (ListView) findViewById(R.id.main_listview);
        mSlidingMenu = (SlidingMenu) findViewById(R.id.slidingMenu);
        ivHead = (ImageView) findViewById(R.id.iv_head);
        myLinearLayout = (MyLinearLayout) findViewById(R.id.my_layout);
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyehai.slidingmenu.widget.SlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/slidingMenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg">

    <include layout="@layout/layout_menu" />

    <include layout="@layout/layout_main" />

</com.xiaoyehai.slidingmenu.widget.SlidingMenu>

layout_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="20dp"
    android:paddingTop="50dp" >

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/head" />

    <ListView
        android:id="@+id/menu_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="5dp"
        android:listSelector="@android:color/transparent" >
    </ListView>

</LinearLayout>

layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyehai.slidingmenu.widget.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/my_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#459BFE">

        <ImageView
            android:id="@+id/iv_head"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="15dp"
            android:background="@drawable/head" />
    </RelativeLayout>

    <ListView
        android:id="@+id/main_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:listSelector="@android:color/transparent"></ListView>

</com.xiaoyehai.slidingmenu.widget.MyLinearLayout>

当SlidingMenu打开的时候,拦截并消费触摸事件,让main_listview不能上下滑动

package com.xiaoyehai.slidingmenu.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/**
 * 当SlidingMenu打开的时候,拦截并消费触摸事件,让main_listview不能上下滑动
 * Created by xiaoyehai on 2016/11/30.
 */
public class MyLinearLayout extends LinearLayout {

    private SlidingMenu slidingMenu;

    public void setSlidingMenu(SlidingMenu slidingMenu) {
        this.slidingMenu = slidingMenu;
    }

    public MyLinearLayout(Context context) {
        this(context, null);
    }

    public MyLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (slidingMenu != null && slidingMenu.getCurrentState() == SlidingMenu.DragState.Open) {
            //当SlidingMenu打开的时候,拦截并消费触摸事件
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (slidingMenu != null && slidingMenu.getCurrentState() == SlidingMenu.DragState.Open) {
            //当SlidingMenu打开的时候,拦截并消费触摸事件
            if (event.getAction() == MotionEvent.ACTION_UP) {
                //点击mainview抬起时关闭菜单
                slidingMenu.close();
            }
            return true;
        }
        return super.onTouchEvent(event);
    }
}

ColorUtil

public class ColorUtil {
    /**
     * 根据百分比,从一个颜色到另一个颜色的渐变
     *
     * @param fraction   百分比0-1
     * @param startValue 开始颜色
     * @param endValue   结束颜色
     * @return
     */
    public static Object evaluateColor(float fraction, Object startValue,
                                       Object endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
                | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
                | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
                | (int) ((startB + (int) (fraction * (endB - startB))));
    }
}

Constant

package com.xiaoyehai.slidingmenu;

public interface Constant {
    public static final String[] sCheeseStrings = {
            "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
            "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
            "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
            "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
            "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
            "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
            "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
            "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
            "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
    };

    public static final String[] NAMES = new String[]{"宋江", "卢俊义", "吴用",
            "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深",
            "武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘",
            "雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍",
            " 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪",
            "魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方",
            "郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充",
            "李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿",
            "陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩",
    };
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
实现Android侧菜单有多种方式,其中一种比较常见的实现方式是使用DrawerLayout和NavigationView。 步骤如下: 1. 在XML布局文件中添加DrawerLayout和NavigationView,其中NavigationView中可以添加菜单项。 ``` <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 主界面内容 --> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> <!-- 侧菜单 --> <android.support.design.widget.NavigationView android:id="@+id/navigation_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header" app:menu="@menu/nav_menu" /> </android.support.v4.widget.DrawerLayout> ``` 2. 在Activity中设置DrawerLayout和NavigationView的监听器,并在onOptionsItemSelected方法中处理菜单项点击事件。 ``` public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; private NavigationView mNavigationView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDrawerLayout = findViewById(R.id.drawer_layout); mNavigationView = findViewById(R.id.navigation_view); mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { // 处理菜单项点击事件 return false; } }); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, mDrawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); mDrawerLayout.addDrawerListener(toggle); toggle.syncState(); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { mDrawerLayout.openDrawer(GravityCompat.START); return true; } return super.onOptionsItemSelected(item); } } ``` 3. 在NavigationView中添加菜单项,并为菜单项设置图标和标题。 ``` <menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_home" android:icon="@drawable/ic_home" android:title="Home" /> <item android:id="@+id/nav_gallery" android:icon="@drawable/ic_gallery" android:title="Gallery" /> <item android:id="@+id/nav_slideshow" android:icon="@drawable/ic_slideshow" android:title="Slideshow" /> </group> </menu> ``` 至此,实现了一个简单的Android侧菜单。如果要实现仿QQ删除功能,可以在ListView或RecyclerView中添加动删除的功能,并在删除时更新侧菜单中的未读消息数等信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值