欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

仿QQ侧滑菜单

程序员文章站 2022-05-15 17:00:13
...

效果图

仿QQ侧滑菜单

仿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[]{"宋江", "卢俊义", "吴用",
            "公孙胜", "关胜", "林冲", "秦明", "呼延灼", "花荣", "柴进", "李应", "朱仝", "鲁智深",
            "武松", "董平", "张清", "杨志", "徐宁", "索超", "戴宗", "刘唐", "李逵", "史进", "穆弘",
            "雷横", "李俊", "阮小二", "张横", "阮小五", " 张顺", "阮小七", "杨雄", "石秀", "解珍",
            " 解宝", "燕青", "朱武", "黄信", "孙立", "宣赞", "郝思文", "韩滔", "彭玘", "单廷珪",
            "魏定国", "萧让", "裴宣", "欧鹏", "邓飞", " 燕顺", "杨林", "凌振", "蒋敬", "吕方",
            "郭 盛", "安道全", "皇甫端", "王英", "扈三娘", "鲍旭", "樊瑞", "孔明", "孔亮", "项充",
            "李衮", "金大坚", "马麟", "童威", "童猛", "孟康", "侯健", "陈达", "杨春", "郑天寿",
            "陶宗旺", "宋清", "乐和", "龚旺", "丁得孙", "穆春", "曹正", "宋万", "杜迁", "薛永", "施恩",
    };
}