Android——側滑面板

1.完成佈局

activity——main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.MainActivity">

    <com.example.drawerlayout.view.SlideMenu
        android:id="@+id/main_slide_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <include layout="@layout/layout_drawer_menu"/>
        <include layout="@layout/layout_main_content"/>
    </com.example.drawerlayout.view.SlideMenu>

</androidx.constraintlayout.widget.ConstraintLayout>

layout_drawer_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<!--側滑菜單-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="@dimen/drawer_menu_layout_width"
    android:layout_height="match_parent"
    android:background="@drawable/menu_bg">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/drawer_menu_news"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_news_image"
                android:src="@drawable/tab_news"
                style="@style/DrawerMenuItemImage" />
            <TextView
                android:id="@+id/drawer_menu_news_text"
                android:text="@string/drawer_menu_news"
                style="@style/DrawerMenuItemText" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/drawer_menu_read"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_read_image"
                android:src="@drawable/tab_read"
                style="@style/DrawerMenuItemImage"/>
            <TextView
                android:id="@+id/drawer_menu_read_text"
                android:text="@string/drawer_menu_read"
                style="@style/DrawerMenuItemText"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/drawer_menu_local"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_local_image"
                android:src="@drawable/tab_local"
                style="@style/DrawerMenuItemImage"/>
            <TextView
                android:id="@+id/drawer_menu_local_text"
                android:text="@string/drawer_menu_local"
                style="@style/DrawerMenuItemText"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/drawer_menu_ties"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_ties_image"
                android:src="@drawable/tab_ties"
                style="@style/DrawerMenuItemImage"/>
            <TextView
                android:id="@+id/drawer_menu_ties_text"
                android:text="@string/drawer_menu_ties"
                style="@style/DrawerMenuItemText"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/drawer_menu_pics"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_pics_image"
                android:src="@drawable/tab_pics"
                style="@style/DrawerMenuItemImage"/>
            <TextView
                android:id="@+id/drawer_menu_pics_text"
                android:text="@string/drawer_menu_pics"
                style="@style/DrawerMenuItemText"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/drawer_menu_ugc"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_ugc_image"
                android:src="@drawable/tab_ugc"
                style="@style/DrawerMenuItemImage"/>
            <TextView
                android:id="@+id/drawer_menu_ugc_text"
                android:text="@string/drawer_menu_ugc"
                style="@style/DrawerMenuItemText"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/drawer_menu_vote"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_vote_image"
                android:src="@drawable/tab_vote"
                style="@style/DrawerMenuItemImage"/>
            <TextView
                android:id="@+id/drawer_menu_vote_text"
                android:text="@string/drawer_menu_vote"
                style="@style/DrawerMenuItemText"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/drawer_menu_focus"
            style="@style/DrawerMenuItem">
            <ImageView
                android:id="@+id/drawer_menu_focus_image"
                android:src="@drawable/tab_focus"
                style="@style/DrawerMenuItemImage"/>
            <TextView
                android:id="@+id/drawer_menu_focus_text"
                android:text="@string/drawer_menu_focus"
                style="@style/DrawerMenuItemText"/>
        </LinearLayout>

    </LinearLayout>

</ScrollView>

layout_main_content.xml

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/main_content_top_bar_height"
        android:orientation="horizontal"
        android:background="@drawable/top_bar_bg">
        <ImageButton
            android:id="@+id/image_for_drawer_menu"
            android:layout_width="@dimen/main_content_top_bar_image_button_width"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:src="@drawable/main_back"
            android:background="@null"/>
        <ImageView
            android:id="@+id/main_content_top_bar_divider"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:src="@drawable/top_bar_divider"/>
        <TextView
            android:id="@+id/main_content_top_bar_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/main_content_top_bar_news"
            android:textColor="#FFFFFF"
            android:textSize="@dimen/main_content_top_bar_text_size"
            android:gravity="center"/>
    </LinearLayout>

    <TextView
        android:id="@+id/main_content_main_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="@string/main_content_main_view_text"
        android:textColor="#FF0000"
        android:textSize="@dimen/sp_20"/>

</LinearLayout>

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);
    }
}
發佈了40 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章