Android开源AndroidSideMenu实现抽屉和侧滑菜单
发布时间 - 2026-01-10 23:15:31 点击率:次AndroidSideMenu能够让你轻而易举地创建侧滑菜单。需要注意的是,该项目自身并不提供任何创建菜单的工具,因此,开发者可以自由创建内部菜单。
核心类如下:
/*
* Copyright dmitry.zaicew@gmail.com Dmitry Zaitsev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.agimind.widget;
import java.util.LinkedList;
import java.util.Queue;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.FrameLayout;
public class SlideHolder extends FrameLayout {
public final static int DIRECTION_LEFT = 1;
public final static int DIRECTION_RIGHT = -1;
protected final static int MODE_READY = 0;
protected final static int MODE_SLIDE = 1;
protected final static int MODE_FINISHED = 2;
private Bitmap mCachedBitmap;
private Canvas mCachedCanvas;
private Paint mCachedPaint;
private View mMenuView;
private int mMode = MODE_READY;
private int mDirection = DIRECTION_LEFT;
private int mOffset = 0;
private int mStartOffset;
private int mEndOffset;
private boolean mEnabled = true;
private boolean mInterceptTouch = true;
private boolean mAlwaysOpened = false;
private boolean mDispatchWhenOpened = false;
private Queue<Runnable> mWhenReady = new LinkedList<Runnable>();
private OnSlideListener mListener;
public SlideHolder(Context context) {
super(context);
initView();
}
public SlideHolder(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SlideHolder(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mCachedPaint = new Paint(
Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG
| Paint.DITHER_FLAG
);
}
@Override
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mEnabled;
}
/**
*
* @param direction - direction in which SlideHolder opens. Can be: DIRECTION_LEFT, DIRECTION_RIGHT
*/
public void setDirection(int direction) {
closeImmediately();
mDirection = direction;
}
/**
*
* @param allow - if false, SlideHolder won't react to swiping gestures (but still will be able to work by manually invoking mathods)
*/
public void setAllowInterceptTouch(boolean allow) {
mInterceptTouch = allow;
}
public boolean isAllowedInterceptTouch() {
return mInterceptTouch;
}
/**
*
* @param dispatch - if true, in open state SlideHolder will dispatch touch events to main layout (in other words - it will be clickable)
*/
public void setDispatchTouchWhenOpened(boolean dispatch) {
mDispatchWhenOpened = dispatch;
}
public boolean isDispatchTouchWhenOpened() {
return mDispatchWhenOpened;
}
/**
*
* @param opened - if true, SlideHolder will always be in opened state (which means that swiping won't work)
*/
public void setAlwaysOpened(boolean opened) {
mAlwaysOpened = opened;
requestLayout();
}
public int getMenuOffset() {
return mOffset;
}
public void setOnSlideListener(OnSlideListener lis) {
mListener = lis;
}
public boolean isOpened() {
return mAlwaysOpened || mMode == MODE_FINISHED;
}
public void toggle(boolean immediately) {
if(immediately) {
toggleImmediately();
} else {
toggle();
}
}
public void toggle() {
if(isOpened()) {
close();
} else {
open();
}
}
public void toggleImmediately() {
if(isOpened()) {
closeImmediately();
} else {
openImmediately();
}
}
public boolean open() {
if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
open();
}
});
return true;
}
initSlideMode();
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
invalidate();
return true;
}
public boolean openImmediately() {
if(isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
openImmediately();
}
});
return true;
}
mMenuView.setVisibility(View.VISIBLE);
mMode = MODE_FINISHED;
requestLayout();
if(mListener != null) {
mListener.onSlideCompleted(true);
}
return true;
}
public boolean close() {
if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
close();
}
});
return true;
}
initSlideMode();
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
invalidate();
return true;
}
public boolean closeImmediately() {
if(!isOpened() || mAlwaysOpened || mMode == MODE_SLIDE) {
return false;
}
if(!isReadyForSlide()) {
mWhenReady.add(new Runnable() {
@Override
public void run() {
closeImmediately();
}
});
return true;
}
mMenuView.setVisibility(View.GONE);
mMode = MODE_READY;
requestLayout();
if(mListener != null) {
mListener.onSlideCompleted(false);
}
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int parentLeft = 0;
final int parentTop = 0;
final int parentRight = r - l;
final int parentBottom = b - t;
View menu = getChildAt(0);
int menuWidth = menu.getMeasuredWidth();
if(mDirection == DIRECTION_LEFT) {
menu.layout(parentLeft, parentTop, parentLeft+menuWidth, parentBottom);
} else {
menu.layout(parentRight-menuWidth, parentTop, parentRight, parentBottom);
}
if(mAlwaysOpened) {
if(mDirection == DIRECTION_LEFT) {
mOffset = menuWidth;
} else {
mOffset = 0;
}
} else if(mMode == MODE_FINISHED) {
mOffset = mDirection*menuWidth;
} else if(mMode == MODE_READY) {
mOffset = 0;
}
View main = getChildAt(1);
main.layout(
parentLeft + mOffset,
parentTop,
parentLeft + mOffset + main.getMeasuredWidth(),
parentBottom
);
invalidate();
Runnable rn;
while((rn = mWhenReady.poll()) != null) {
rn.run();
}
}
private boolean isReadyForSlide() {
return (getWidth() > 0 && getHeight() > 0);
}
@Override
protected void onMeasure(int wSp, int hSp) {
mMenuView = getChildAt(0);
if(mAlwaysOpened) {
View main = getChildAt(1);
if(mMenuView != null && main != null) {
measureChild(mMenuView, wSp, hSp);
LayoutParams lp = (LayoutParams) main.getLayoutParams();
if(mDirection == DIRECTION_LEFT) {
lp.leftMargin = mMenuView.getMeasuredWidth();
} else {
lp.rightMargin = mMenuView.getMeasuredWidth();
}
}
}
super.onMeasure(wSp, hSp);
}
private byte mFrame = 0;
@Override
protected void dispatchDraw(Canvas canvas) {
try {
if(mMode == MODE_SLIDE) {
View main = getChildAt(1);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
/*
* On new versions we redrawing main layout only
* if it's marked as dirty
*/
if(main.isDirty()) {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
main.draw(mCachedCanvas);
}
} else {
/*
* On older versions we just redrawing our cache
* every 5th frame
*/
if(++mFrame % 5 == 0) {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
main.draw(mCachedCanvas);
}
}
/*
* Draw only visible part of menu
*/
View menu = getChildAt(0);
final int scrollX = menu.getScrollX();
final int scrollY = menu.getScrollY();
canvas.save();
if(mDirection == DIRECTION_LEFT) {
canvas.clipRect(0, 0, mOffset, menu.getHeight(), Op.REPLACE);
} else {
int menuWidth = menu.getWidth();
int menuLeft = menu.getLeft();
canvas.clipRect(menuLeft+menuWidth+mOffset, 0, menuLeft+menuWidth, menu.getHeight());
}
canvas.translate(menu.getLeft(), menu.getTop());
canvas.translate(-scrollX, -scrollY);
menu.draw(canvas);
canvas.restore();
canvas.drawBitmap(mCachedBitmap, mOffset, 0, mCachedPaint);
} else {
if(!mAlwaysOpened && mMode == MODE_READY) {
mMenuView.setVisibility(View.GONE);
}
super.dispatchDraw(canvas);
}
} catch(IndexOutOfBoundsException e) {
/*
* Possibility of crashes on some devices (especially on Samsung).
* Usually, when ListView is empty.
*/
}
}
private int mHistoricalX = 0;
private boolean mCloseOnRelease = false;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(((!mEnabled || !mInterceptTouch) && mMode == MODE_READY) || mAlwaysOpened) {
return super.dispatchTouchEvent(ev);
}
if(mMode != MODE_FINISHED) {
onTouchEvent(ev);
if(mMode != MODE_SLIDE) {
super.dispatchTouchEvent(ev);
} else {
MotionEvent cancelEvent = MotionEvent.obtain(ev);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(cancelEvent);
cancelEvent.recycle();
}
return true;
} else {
final int action = ev.getAction();
Rect rect = new Rect();
View menu = getChildAt(0);
menu.getHitRect(rect);
if(!rect.contains((int) ev.getX(), (int) ev.getY())) {
if (action == MotionEvent.ACTION_UP && mCloseOnRelease && !mDispatchWhenOpened) {
close();
mCloseOnRelease = false;
} else {
if(action == MotionEvent.ACTION_DOWN && !mDispatchWhenOpened) {
mCloseOnRelease = true;
}
onTouchEvent(ev);
}
if(mDispatchWhenOpened) {
super.dispatchTouchEvent(ev);
}
return true;
} else {
onTouchEvent(ev);
ev.offsetLocation(-menu.getLeft(), -menu.getTop());
menu.dispatchTouchEvent(ev);
return true;
}
}
}
private boolean handleTouchEvent(MotionEvent ev) {
if(!mEnabled) {
return false;
}
float x = ev.getX();
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
mHistoricalX = (int) x;
return true;
}
if(ev.getAction() == MotionEvent.ACTION_MOVE) {
float diff = x - mHistoricalX;
if((mDirection*diff > 50 && mMode == MODE_READY) || (mDirection*diff < -50 && mMode == MODE_FINISHED)) {
mHistoricalX = (int) x;
initSlideMode();
} else if(mMode == MODE_SLIDE) {
mOffset += diff;
mHistoricalX = (int) x;
if(!isSlideAllowed()) {
finishSlide();
}
} else {
return false;
}
}
if(ev.getAction() == MotionEvent.ACTION_UP) {
if(mMode == MODE_SLIDE) {
finishSlide();
}
mCloseOnRelease = false;
return false;
}
return mMode == MODE_SLIDE;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean handled = handleTouchEvent(ev);
invalidate();
return handled;
}
private void initSlideMode() {
mCloseOnRelease = false;
View v = getChildAt(1);
if(mMode == MODE_READY) {
mStartOffset = 0;
mEndOffset = mDirection*getChildAt(0).getWidth();
} else {
mStartOffset = mDirection*getChildAt(0).getWidth();
mEndOffset = 0;
}
mOffset = mStartOffset;
if(mCachedBitmap == null || mCachedBitmap.isRecycled() || mCachedBitmap.getWidth() != v.getWidth()) {
mCachedBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
mCachedCanvas = new Canvas(mCachedBitmap);
} else {
mCachedCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
}
v.setVisibility(View.VISIBLE);
mCachedCanvas.translate(-v.getScrollX(), -v.getScrollY());
v.draw(mCachedCanvas);
mMode = MODE_SLIDE;
mMenuView.setVisibility(View.VISIBLE);
}
private boolean isSlideAllowed() {
return (mDirection*mEndOffset > 0 && mDirection*mOffset < mDirection*mEndOffset && mDirection*mOffset >= mDirection*mStartOffset)
|| (mEndOffset == 0 && mDirection*mOffset > mDirection*mEndOffset && mDirection*mOffset <= mDirection*mStartOffset);
}
private void completeOpening() {
mOffset = mDirection*mMenuView.getWidth();
requestLayout();
post(new Runnable() {
@Override
public void run() {
mMode = MODE_FINISHED;
mMenuView.setVisibility(View.VISIBLE);
}
});
if(mListener != null) {
mListener.onSlideCompleted(true);
}
}
private Animation.AnimationListener mOpenListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
completeOpening();
}
};
private void completeClosing() {
mOffset = 0;
requestLayout();
post(new Runnable() {
@Override
public void run() {
mMode = MODE_READY;
mMenuView.setVisibility(View.GONE);
}
});
if(mListener != null) {
mListener.onSlideCompleted(false);
}
}
private Animation.AnimationListener mCloseListener = new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
completeClosing();
}
};
private void finishSlide() {
if(mDirection*mEndOffset > 0) {
if(mDirection*mOffset > mDirection*mEndOffset/2) {
if(mDirection*mOffset > mDirection*mEndOffset) mOffset = mEndOffset;
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
} else {
if(mDirection*mOffset < mDirection*mStartOffset) mOffset = mStartOffset;
Animation anim = new SlideAnimation(mOffset, mStartOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
}
} else {
if(mDirection*mOffset < mDirection*mStartOffset/2) {
if(mDirection*mOffset < mDirection*mEndOffset) mOffset = mEndOffset;
Animation anim = new SlideAnimation(mOffset, mEndOffset);
anim.setAnimationListener(mCloseListener);
startAnimation(anim);
} else {
if(mDirection*mOffset > mDirection*mStartOffset) mOffset = mStartOffset;
Animation anim = new SlideAnimation(mOffset, mStartOffset);
anim.setAnimationListener(mOpenListener);
startAnimation(anim);
}
}
}
private class SlideAnimation extends Animation {
private static final float SPEED = 0.6f;
private float mStart;
private float mEnd;
public SlideAnimation(float fromX, float toX) {
mStart = fromX;
mEnd = toX;
setInterpolator(new DecelerateInterpolator());
float duration = Math.abs(mEnd - mStart) / SPEED;
setDuration((long) duration);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
float offset = (mEnd - mStart) * interpolatedTime + mStart;
mOffset = (int) offset;
postInvalidate();
}
}
public static interface OnSlideListener {
public void onSlideCompleted(boolean opened);
}
}
使用:
package com.agimind.sidemenuexample;
import com.agimind.widget.SlideHolder;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.app.ActionBar;
import android.app.Activity;
public class MainActivity extends Activity {
private SlideHolder mSlideHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSlideHolder = (SlideHolder) findViewById(R.id.slideHolder);
// mSlideHolder.setAllowInterceptTouch(false);
// mSlideHolder.setAlwaysOpened(true);
/*
* toggleView can actually be any view you want. Here, for simplicity,
* we're using TextView, but you can easily replace it with button.
*
* Note, when menu opens our textView will become invisible, so it quite
* pointless to assign toggle-event to it. In real app consider using UP
* button instead. In our case toggle() can be replaced with open().
*/
ActionBar actionBar = getActionBar();
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setHomeButtonEnabled(true);
View toggleView = findViewById(R.id.textView);
toggleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSlideHolder.toggle();
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
mSlideHolder.toggle();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
}
布局如下:
<com.agimind.widget.SlideHolder xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/slideHolder"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity" >
<ScrollView
android:layout_width="200dp"
android:layout_height="fill_parent"
android:background="@android:color/black" >
<LinearLayout
android:layout_width="200dp"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/menu_settings" />
</LinearLayout>
</ScrollView>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/swipe"
android:textSize="25sp" />
</RelativeLayout>
</com.agimind.widget.SlideHolder>
下载:AndroidSideMenu
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android
# AndroidSideMenu
# 抽屉菜单
# 侧滑菜单
# Android 侧滑抽屉菜单的实现代码
# Android 抽屉效果的导航菜单实现代码实例
# Android实现自定义滑动式抽屉菜单效果
# Android App中DrawerLayout抽屉效果的菜单编写实例
# Android组件之DrawerLayout实现抽屉菜单
# Android开发实现抽屉菜单
# 的是
# 让你
# 轻而易举
# 该项目
# 需要注意
# 大家多多
# Override
# DITHER_FLAG
# setEnabled
# isEnabled
# enabled
# FILTER_BITMAP_FLAG
# attrs
# initView
# defStyle
# ANTI_ALIAS_FLAG
# void
# return
# gestures
# swiping
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
合肥制作网站的公司有哪些,合肥聚美网络科技有限公司介绍?
Laravel如何编写单元测试和功能测试?(PHPUnit示例)
怎么用AI帮你为初创公司进行市场定位分析?
如何用美橙互联一键搭建多站合一网站?
在Oracle关闭情况下如何修改spfile的参数
猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?
如何用PHP快速搭建CMS系统?
电商网站制作价格怎么算,网上拍卖流程以及规则?
如何在Tomcat中配置并部署网站项目?
北京网站制作公司哪家好一点,北京租房网站有哪些?
Laravel怎么解决跨域问题_Laravel配置CORS跨域访问
怎么用AI帮你设计一套个性化的手机App图标?
C++用Dijkstra(迪杰斯特拉)算法求最短路径
百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭
瓜子二手车官方网站在线入口 瓜子二手车网页版官网通道入口
如何在万网ECS上快速搭建专属网站?
Laravel安装步骤详细教程_Laravel环境搭建指南
如何在搬瓦工VPS快速搭建网站?
简单实现Android文件上传
实例解析Array和String方法
简历没回改:利用AI润色让你的文字更专业
如何在云指建站中生成FTP站点?
Laravel怎么集成Log日志记录_Laravel单文件与每日日志配置及自定义通道【详解】
,交易猫的商品怎么发布到网站上去?
开心动漫网站制作软件下载,十分开心动画为何停播?
Laravel如何使用Telescope进行调试?(安装和使用教程)
php结合redis实现高并发下的抢购、秒杀功能的实例
利用vue写todolist单页应用
Python面向对象测试方法_mock解析【教程】
JavaScript如何操作视频_媒体API怎么控制播放
,南京靠谱的征婚网站?
Linux系统运维自动化项目教程_Ansible批量管理实战
微信小程序 配置文件详细介绍
android nfc常用标签读取总结
C++时间戳转换成日期时间的步骤和示例代码
Laravel如何集成Inertia.js与Vue/React?(安装配置)
米侠浏览器网页图片不显示怎么办 米侠图片加载修复
Laravel用户密码怎么加密_Laravel Hash门面使用教程
电商网站制作多少钱一个,电子商务公司的网站制作费用计入什么科目?
北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?
Laravel如何创建自定义中间件?(Middleware代码示例)
Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析
Laravel Seeder填充数据教程_Laravel模型工厂Factory使用
深圳网站制作平台,深圳市做网站好的公司有哪些?
Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程
Laravel辅助函数有哪些_Laravel Helpers常用助手函数大全
七夕网站制作视频,七夕大促活动怎么报名?
Python数据仓库与ETL构建实战_Airflow调度流程详解
EditPlus中的正则表达式 实战(1)
Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询

