自定义简(粗)易(暴)SlidingMenu

本文介绍了一种自定义ViewGroup实现滑动菜单的方法。通过让整个ViewGroup响应滑动事件,并控制内部两个TextView(主视图和侧边菜单)的位置来实现滑动效果。文章详细介绍了滑动检测、测量、布局更新等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这个代码只能配本文的布局文件使用,因为在代码中写死了是SlidingMenu里是两个TextView。大家可以参考里面的滑动实现,还有测量和布局的实现。

这里采用了一个很笨的实现方案:即在整个ViewGroup向右滑动,为了保持MainView不动,我让MainView在ViewGroup向右滑动使Menu滑出时向左移动。其实可以采用只让MenuView(mSubView)动的方式去实现,只是想玩一下,看效果。

总结一下写这个View时应该注意的细节:

1.判断当前动作是向左还是向右,不能用ev.getX()-mLastX,因为两次的都是相等的,因为两次事件发生的太近了,最好使用VelocityTracker判断速度的方向(值的正负)。

2.如果只是需要ViewGroup的某个控件动,可以为某个控件设置一个Scroller(一般的View中都会维护一个mScroller成员,如果要用的话,用户就可以去设置),然后调用它的invalidate,一般的View都会把onComputeScroll()安照常规写好(如TextView),可参照下面mScrollerForMainView

package com.example.zhangjinbiao.myapplication;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;
import android.widget.TextView;

public class SlideMenu extends ViewGroup {
    // x轴的上一次位置
    private float mLastx;
    // 模拟弹性滑动
    private Scroller mScrooler;
    private Scroller mScrollerForMainView;

    // 标记当前现实状态,默认为true显示的是主界面
    private boolean mIsMain = true;
    //main view,below to the subview
    private View mMainView;
    //subView,the menu,which is obove the main view
    private View mSubView;

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

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

    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScrooler = new Scroller(context);
        mScrollerForMainView = new Scroller(context);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       /* super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        View left = this.getChildAt(0);
        left.measure(left.getLayoutParams().width, heightMeasureSpec);
        View main = this.getChildAt(1);
        main.measure(widthMeasureSpec, heightMeasureSpec);*/
       /* final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        final int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        final int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);*/
        mMainView = getChildAt(0);
        mSubView = getChildAt(1);

        int height = 0;
        int width = 0;
        if(mMainView != null && mSubView != null){
            measureChildren(widthMeasureSpec, heightMeasureSpec);
            height = mMainView.getMeasuredHeight() > mSubView.getMeasuredHeight() ?
                    mMainView.getMeasuredHeight() : mSubView.getMeasuredHeight();
            width = mMainView.getMeasuredWidth() > mSubView.getMeasuredWidth() ?
                    mMainView.getMeasuredWidth() : mSubView.getMeasuredWidth();
            if(mMainView instanceof TextView){
                TextView tv = (TextView)mMainView;
                tv.setScroller(mScrollerForMainView);
            }
        }
        setMeasuredDimension(width, height);



    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
       /* View left = this.getChildAt(0);
        left.layout(-left.getLayoutParams().width, 0, 0, b);

        View main = this.getChildAt(1);
        main.layout(l, t, r, b);*/
        if(mMainView != null){
            int right = r - l;
            int bottom = b - t;
            mMainView.layout(0, 0, right, bottom);
        }
        if (mSubView != null) {
            int left = -(mSubView.getMeasuredWidth());
            int right = left + mSubView.getMeasuredWidth();
            int bottom = b  - t;
            mSubView.layout(left, 0, right, bottom);
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN :
                // getX是返回的相对于当前view左边缘的横轴距离,getRawx是返回的是相对于屏幕左边缘的距离,
                // 因为这里的viewgroup是撑满全屏的所以用这两个方法的效果都是一样的
                mLastx = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE :

                float x =  event.getX();
                //上一次x轴位置减去当前X轴的值为这一次滑动的增量
                float deltaX = mLastx - x;
                //加上原本内容在X轴的偏移量得到它将要便宜的量
                int scrollX = getScrollX() + (int) deltaX;
                if (scrollX > 0) {
                    //大于0的时候代表侧滑菜单已经出来了所以直接scrollTo(0,0)即可
                    scrollTo(0, 0);
                    scrollToForMainView(0);
                } else if (scrollX > -mSubView.getWidth()) {
                    //小于侧滑菜单的宽度的负值的时候则代表侧滑菜单已经完全隐藏起来了
                    scrollTo(scrollX, 0);
                    scrollToForMainView(-scrollX);
                } /*else {
                    //此时侧滑菜单既没有完全隐藏也没有完全现实根据增量值进行正常的scrollBy即可
                    scrollBy((int) deltaX, 0);
                }*/
                /*int deltaX = mLastx - x;
                //加上原本内容在X轴的偏移量得到它将要便宜的量
                int scrollX = getScrollX() + deltaX;
                if(-scrollX > mSubView.getWidth()/2){
                    //if scrollx larger than half of thd width of the menu, show the menu

                }*/
                mLastx = x;
                break;
            case MotionEvent.ACTION_UP :
                // 获取手指抬起时候的内容偏移量
                final int upX = getScrollX();
                final float currentX = event.getX();
                final float dx = mLastx - currentX;
                if (upX < -mSubView.getWidth()/ 2) {
                    // 滑动距离大于了左侧菜单的一半则将菜单显示出来
                    switchToMenu();
                } else {
                    // 反之菜单收起
                    switchToScreen();
                }

                break;
        }
        return true;
    }

    private void switchToScreen() {
        int startX = getScrollX();// 起始的X轴偏移量
        int deltaX;
         deltaX = -startX;
        mScrooler.startScroll(startX, 0, deltaX, 0, Math.abs(deltaX) * 3);
        invalidate();
        mIsMain = true;
        scrollMainViewWhenSwitch();
        getX();
    }

    @Override
    public void computeScroll() {
        if (mScrooler.computeScrollOffset()) {
            scrollTo(mScrooler.getCurrX(), mScrooler.getCurrY());
            //postInvalidate();
        }
    }

    public void switchToMenu() {
        int startX = getScrollX();// 起始的X轴偏移量
        int deltaX;

            // 如果要显示的是菜单界面则目标的偏移x点是菜单的左边界
            /*deltaX = -getChildAt(0).getLayoutParams().width - startX;*/
            deltaX = -mSubView.getWidth() - startX;

        mScrooler.startScroll(startX, 0, deltaX, 0, Math.abs(deltaX) * 3);
        invalidate();
        mIsMain = false;
        scrollMainViewWhenSwitch();

       /* mIsMain = !mIsMain;
        switchToScreen();*/
    }

    void scrollMainViewWhenSwitch(){
        int mainViewStartX= mMainView.getScrollX();
        int mainViewDeltaX;
        if(mIsMain == true){
            mainViewDeltaX = -mainViewStartX;
        }else{
            mainViewDeltaX = mSubView.getWidth()-mainViewStartX;
        }
        mScrollerForMainView.startScroll(mainViewStartX, 0, mainViewDeltaX, 0,Math.abs(mainViewDeltaX) * 3);
        mMainView.invalidate();

    }

   void scrollToForMainView(int x){
       mMainView.scrollTo(x, 0);

   }
}

下面是MainActivity:

public class MainActivity extends Activity implements View.OnLayoutChangeListener{


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sliding_menu);
       
    }

    
}




下面是ViewGroup的布局文件

<?xml version="1.0" encoding="utf-8"?>
<com.example.zhangjinbiao.myapplication.SlideMenu xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
<TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimaryDark"
    android:text="wo ai ni a hua qi lai lalala hello world"
    android:gravity="center"/>
    <TextView
        android:layout_width="200dp"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"
        android:text="hi! i am menu"
        android:gravity="center"/>
</com.example.zhangjinbiao.myapplication.SlideMenu>






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值