Android——侧滑面板

1.完成布局

activity——main.xml




    
        
        
    

layout_drawer_menu.xml





    

        
            
            
        

        
            
            
        

        
            
            
        

        
            
            
        

        
            
            
        

        
            
            
        

        
            
            
        

        
            
            
        

    

layout_main_content.xml




    
        
        
        
    

    

2.布局测量&摆放

SlideMenu.java

1.继承ViewGroup,重写构造方法

2.重写onMeasure()和onLayout()方法

注:

继承ViewGroup,要摆放子控件,一定需要重写onLayout()方法。(onMeasure --> onLayout --> onDraw)

继承View,一般不需要重写onLayout()方法。(onMeasure --> onDraw)

package com.example.drawerlayout.view;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

import androidx.annotation.RequiresApi;
import androidx.core.view.MenuCompat;

import com.example.drawerlayout.util.LogUtil;
import com.example.drawerlayout.util.StatusBarUtil;

/**
 * @Author xxc
 * @Date 2019/12/29 0:54
 * @Version 1.0
 * @Description 自定义控件--侧滑面板/抽屉面板
 */
public class SlideMenu extends ViewGroup {

    public static final int MENU_CLOSE = 0;
    public static final int MENU_OPEN = 1;
    private static final String TAG = SlideMenu.class.getSimpleName();
    private Context mContext;
    private Activity mActivity;
    private int drawerMenuMeasureWidth;
    private int drawerMenuWidth;
    private int menuState = 0; // 默认菜单为关闭状态
    private Scroller scroller;
    private float downX;
    private float downY;
    private float moveX;

    public SlideMenu(Context context) {
        super(context);
        init(context);
    }

    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {
        mContext = context;
        mActivity = (Activity) context;
//        StatusBarUtil.setTranslucent(mActivity);
        // 初始化滚动器,数值模拟器
        scroller = new Scroller(mContext);
    }

    /**
     * @description: 测量子控件的宽高
     * @author: xxc
     * @date: 2019/12/29 1:20
     * @param widthMeasureSpec 当前控件宽度测量规则
     * @param heightMeasureSpec 当前控件高度测量规则
     * @return void
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        // 设置侧滑面板宽高
        View drawerMenu = getChildAt(0);
        drawerMenuWidth = drawerMenu.getLayoutParams().width;
        drawerMenu.measure(drawerMenuWidth, heightMeasureSpec);

        View mainContent = getChildAt(1);
//        int mainContentWidth = mainContent.getLayoutParams().width;
        mainContent.measure(widthMeasureSpec, heightMeasureSpec);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * @description: 摆放子控件
     * @author: xxc
     * @date: 2019/12/29 1:26
     * @param changed 当前控件大小,位置是否发生改变
     * @param left 当前控件左边距
     * @param top 当前控件顶边距
     * @param right 当前控件右边距
     * @param bottom 当前控件底边距
     * @return void
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        View drawerMenu = getChildAt(0);
        drawerMenuMeasureWidth = drawerMenu.getMeasuredWidth();
        drawerMenu.layout(-drawerMenuWidth, 0, 0, bottom);

        View mainContent = getChildAt(1);
        mainContent.layout(left, top, right, bottom);
    }

    /**
     * 处理触摸事件
     * @param event
     * @return true -->完全自定义控件时,返回true,消费事件
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                moveX = event.getX();
                // 计算滑动偏移量
                int scrollX = (int) (downX - moveX);
                // 计算将要滚动到的位置
                int newScrollPosition = getScrollX() + scrollX;

                if (newScrollPosition < -drawerMenuWidth) {
                    scrollTo(-drawerMenuWidth, 0);
                } else if (newScrollPosition > 0) {
                    scrollTo(0, 0);
                } else {
                    scrollBy(scrollX, 0);
                }

                downX = moveX;
                break;
            case MotionEvent.ACTION_UP:
                int drawerMenuCenter = (int) (-drawerMenuWidth / 2.0f);
                // 根据当前滚动到的位置,与菜单宽度的一半作比较,确定打开或关闭菜单
                if (getScrollX() < drawerMenuCenter) {
                    // 打开菜单
//                    scrollTo(-drawerMenuWidth, 0);
                    menuState = MENU_OPEN;
                    openOrCloseMenu();
                } else {
                    // 关闭菜单
//                    scrollTo(0, 0);
                    menuState = MENU_CLOSE;
                    openOrCloseMenu();
                }
                break;
            default:
                break;
        }
        return true;
    }

    /**
     * @description: 根据当前状态,执行menu的开/关动画
     * @author: xxc
     * @date: 2019/12/30 22:57
     * @param
     * @return
     */
    private void openOrCloseMenu() {

        int startX = getScrollX();
        int dx = 0;
        if (menuState == MENU_OPEN) {
            // 打开菜单
            // dx = 结束位置 - 当前手指滑动到抬起的位置(也就是startX)
            dx = -drawerMenuWidth - startX;
        } else if (menuState == MENU_CLOSE) {
            // 关闭菜单
            dx = 0 - startX;
        }

        // 平滑滚动
        /**
         * startX:开始滚动的X坐标
         * startY:开始滚动的Y坐标
         * dx:水平方向要移动的距离
         * dy:垂直方向要移动的距离
         * duration:动画时长
         */
        int duration = Math.abs(dx * 2);
        // 1.此时只是模拟了开始平滑的数据
        scroller.startScroll(startX, 0, dx, 0, duration);
        // 强制重绘界面,从而持续执行动画。-->drawChild()-->computeScroll();
        invalidate();
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (scroller.computeScrollOffset()) { // 直到duration事件之后结束
            // 获取要滚动到的位置
            int currX = scroller.getCurrX();
            LogUtil.d(TAG + ":" + currX);
            scrollTo(currX, 0);
            invalidate();
        }
    }

    /**
     * 触摸事件拦截判断
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = ev.getX();
                downY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float scrollX = Math.abs(ev.getX() - downX);
                float scrollY = Math.abs(ev.getY() - downY);
                if (scrollX > scrollY && scrollX > 5) {
                    // 拦截事件,交由自己的onTouchEvent()处理事件
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    public void openMenu() {
        menuState = MENU_OPEN;
        openOrCloseMenu();
    }

    public void closeMenu() {
        menuState = MENU_CLOSE;
        openOrCloseMenu();
    }

    public int getMenuState() {
        return menuState;
    }

    public void switchMenuState() {
        if (menuState == MENU_CLOSE) {
            openMenu();
        } else if (menuState == MENU_OPEN) {
            closeMenu();
        }
    }
}

3.scrollTo(int x, int y)和scrollBy(int x, int y)

在onTouchEvent()中,左右滑动打开或关闭侧滑面板。

简单说一下scrollTo与scrollBy:

scrollTo是每次从最开始位置滚动到新位置,如scrollTo(0,0) --> scrollTo(10, 0),scrollTo(0,0) --> scrollTo(20, 0);(想象成一个点最终滚动到(20, 0)这个位置)。

scrollBy是每次在新的位置继续滚动,如scrollBy(0,0) --> scrollBy(10, 0) --> scrollBy(20, 0);(想象成一个点最终滚动到(30, 0)这个位置)。

所以在onTouchEvent里需要注意ACTION_MOVE的处理。

注:这里还有一个问题(downX-moveX)在向右滑动时是为负的,你可能会有疑问,手机不是向右和向下为正吗,为什么向右滑动,scroll**的X却是取负值呢?我们要显示的菜单是在屏幕的左侧的,当我们向右滑动时菜单显示出来,此时真正被滑动的并不是菜单,而是手机四周的一个框,其相对于我们的手势做反方向运动,从而将菜单面板显示出来。(想象一下手机屏幕就是一个框,这个框左侧还没显示的就是菜单面板,当我们手势向右,框就向左运动,从而将菜单面板显示出来!)

4.平滑动画

ACTION_UP中根据根据打开的菜单面板大小判断是要完全打开或者关闭菜单。

为了不使打开或关闭菜单太过突兀,在openOrCloseMenu()方法中执行了一个平移动画。

其中invalidate()方法会使界面重绘,从而持续执行动画(computeScroll()),直到完全打开或关闭菜单。

5.拦截事件

onInterceptTouchEvent()方法中,根据情况拦截事件,执行自己的onTouchEvent()方法,使得在菜单范围内的左右滑动事件也生效,可以关闭or打开菜单!

6.其他

BaseActivity(稍微搞了一下沉浸式状态栏,没搞好)

package com.example.drawerlayout.activity;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.example.drawerlayout.R;
import com.example.drawerlayout.util.StatusBarUtil;
import com.example.drawerlayout.util.StatusBarUtils;

public abstract class BaseActivity extends AppCompatActivity {

    private Context mContext;
    private Activity mActivity;
    private Context appContext;
    private Application mApplication;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStatusBar();
    }

    protected void setStatusBar() {
        /**
         * 这里做了两件事情,
         * 1.使状态栏透明并使contentView填充到状态栏
         * 2.预留出状态栏的位置,防止界面上的控件离顶部靠的太近。这样就可以实现开头说的第二种情况的沉浸式状态栏了
         */
        StatusBarUtil.setTransparent(this);
    }

}

MainActivity

package com.example.drawerlayout.activity;

import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import com.example.drawerlayout.R;
import com.example.drawerlayout.util.StatusBarUtil;
import com.example.drawerlayout.view.SlideMenu;

public class MainActivity extends BaseActivity {
    private ImageButton mBackDrawerMenu;
    private SlideMenu mainSlideMenu;

//    private Context mContext;

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

        initViews();
        onClickEvent();
    }

    /**
     * @description: 实现控件的点击事件
     * @author: xxc
     * @date: 2019/12/30 23:51
     * @param
     * @return
     */
    private void onClickEvent() {
        mBackDrawerMenu.setOnClickListener(v -> {
            mainSlideMenu.switchMenuState();
        });
    }

    /**
     * @description: 初始化控件
     * @author: xxc
     * @date: 2019/12/30 23:48
     * @param
     * @return
     */
    private void initViews() {
        mBackDrawerMenu = findViewById(R.id.image_for_drawer_menu);
        mainSlideMenu = findViewById(R.id.main_slide_menu);
    }

    //    @RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    protected void setStatusBar() {
//        StatusBarUtil.setColor(this, getColor(R.color.status_bar));
        // 0:表示完全透明
        StatusBarUtil.setColor(this, getResources().getColor(R.color.status_bar), 0);
    }
}

你可能感兴趣的:(Android)