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);
}
}