管理系统UI之三:隐藏Navigation Bar(Hiding the Navigation Bar)

本课程将指导您如何在Android 4.0及以上版本中隐藏导航栏,并让内容显示在其后方,提供沉浸式用户体验。通过使用SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION标志,实现导航栏隐藏的同时保持内容区域布局稳定,确保用户交互不影响视觉体验。

这节课将教您

  1. 在4.0及以上版本中隐藏导航栏

  2. 让内容显示在导航栏之后

本节课程将教您如何对导航栏进行隐藏,这个特性是Android 4.0版本中引入的。

即便本课程关注如何隐藏导航栏,但是在实际的开发中,你最好让状态栏与导航栏同时消失。在保证导航栏易于再次访问的情况下,隐藏导航栏与状态栏使内容区域占据了整个显示空间,因此提供了一个更加沉浸式的用户体验。


一、在4.0及以上版本中隐藏导航栏

你可以在Android 4.0以及以上版本,使用SYSTEM_UI_FLAG_HIDE_NAVIGATION标志来隐藏导航栏。代码如下:

View decorView = getWindow().getDecorView();
// Hide both the navigation bar and the status bar.
// SYSTEM_UI_FLAG_FULLSCREEN is only available on Android 4.1 and higher, but as
// a general rule, you should design your app to hide the status bar whenever you
// hide the navigation bar.
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
              | View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);

注意以下几点

  • 使用这个方法时,触摸屏幕的任何一个区域都是使导航栏(与状态栏)重新显示。用户的交互使这个标志SYSTEM_UI_FLAG_HIDE_NAVIGATION被清除。

  • 一旦这个标志被清除了,如果你想再次隐藏导航栏,你就需要重新对这个标志进行设定。在下一节响应UI可见性的变化中,将详细讲解应用监听系统UI变化来做出相应的调整操作。

  • 在不同的地方设置UI标签是有所区别的。如果你在activity的onCreate()方法中隐藏系统栏,当用户按下home键系统栏就会重新显示。当用户再重新打开activity的时候,onCreate()不会被调用,所以系统栏还会保持可见。如果你想让在不同activity之间切换时,系统UI保持不变,你需要在onReasume()与onWindowFocusChaned()里设定UI标签。

  • setSystemUiVisibility()仅仅在被调用的View显示的时候才会生效。

  • 当从View导航到别的地方时,用setSystemUiVisibility()设置的标签会被清除。


二、让内容显示在导航栏之后

在Android 4.1与更高的版本中,你可以让应用的内容显示在导航栏的后面,这样当导航栏展示或隐藏的时候内容区域就不会发生布局大小的变化。可以使用SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION标志来做到这个效果。同时,你也有可能需要SYSTEM_UI_FLAG_LAYOUT_STABLE这个标志来帮助你的应用维持一个稳定的布局。

当你使用这种方法的时候,就需要你来确保应用中特定区域不会被系统栏掩盖。更详细的信息可以浏览隐藏状态栏一文。

public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) { // We've already done the navigation bar, status bar, and all screen decor windows. If the // status bar can receive input, we need to layout it again to accommodate for the IME // window. if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar || win == mNavigationBar || win == mCarDialLeft || win == mCarDialRight || mScreenDecorWindows.contains(win)) { return; } final WindowManager.LayoutParams attrs = win.getAttrs(); final int type = attrs.type; final int fl = PolicyControl.getWindowFlags(win, attrs); final int pfl = attrs.privateFlags; final int sim = attrs.softInputMode; final int requestedSysUiFl = 0; //PolicyControl.getSystemUiVisibility(null, attrs); final int sysUiFl = 0; // requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs); if(DEBUG_HUIDU){ Slog.d(TAG, "huidu add getsystemuivisibility type = " + type + " fl = " + fl + " pfl = " + pfl + " sim = " + sim + " requestedSysUiFl = " + requestedSysUiFl + " sysUiFl = " + sysUiFl); } if( win.getOwningPackage()!= null){ String pkg_bright = win.getOwningPackage(); // "com.android.systemui/com.android.systemui.settings.BrightnessDialog" if("com.android.systemui".equals(pkg_bright)){ Log.d(TAG, "huidu add layoutWindowLw systemui = " + win); return ; } } displayFrames = win.getDisplayFrames(displayFrames); final WindowFrames windowFrames = win.getWindowFrames(); sTmpLastParentFrame.set(windowFrames.mParentFrame); final Rect pf = windowFrames.mParentFrame; final Rect df = windowFrames.mDisplayFrame; final Rect cf = windowFrames.mContentFrame; final Rect vf = windowFrames.mVisibleFrame; final Rect dcf = windowFrames.mDecorFrame; final Rect sf = windowFrames.mStableFrame; dcf.setEmpty(); windowFrames.setParentFrameWasClippedByDisplayCutout(false); final boolean hasNavBar = hasNavigationBar() && ((mNavigationBar != null && mNavigationBar.isVisibleLw()) || (mCarDialLeft != null && mCarDialLeft.isVisibleLw()) || (mCarDialRight != null && mCarDialRight.isVisibleLw())); final int adjust = sim & SOFT_INPUT_MASK_ADJUST; final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN; final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR; sf.set(displayFrames.mStable); if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) { final @InsetsType int typesToFit = attrs.getFitInsetsTypes(); final @InsetsSide int sidesToFit = attrs.getFitInsetsSides(); final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit); final Rect dfu = displayFrames.mUnrestricted; Insets insets = Insets.of(0, 0, 0, 0); for (int i = types.size() - 1; i >= 0; i--) { final InsetsSource source = mDisplayContent.getInsetsPolicy() .getInsetsForDispatch(win).peekSource(types.valueAt(i)); if (source == null) { continue; } insets = Insets.max(insets, source.calculateInsets( dfu, attrs.isFitInsetsIgnoringVisibility())); } int left = (sidesToFit & Side.LEFT) != 0 ? insets.left : 0; final int top = (sidesToFit & Side.TOP) != 0 ? insets.top : 0; int right = (sidesToFit & Side.RIGHT) != 0 ? insets.right : 0; final int bottom = (sidesToFit & Side.BOTTOM) != 0 ? insets.bottom : 0; final boolean isAppScren = true; // dfu 是显示器大小:dfu.right = 1920,dfu.bottom = 560 final String pkg = win.getOwningPackage(); if(DEBUG_HUIDU){ Log.w(TAG,"huidu add getOwningPackage = "+pkg); } if(pkg != null){ if(pkg.equals("com.android.systemui")){ return ; } } if(isAppScren){ // huidu add if(!"cn.huidu.lcd.player".equals(pkg) && !"cn.huidu.toolbox".equals(pkg)){ if(mCarDialLeft!=null && mCarDialLeft.isVisibleLw()){ left = getNavigationCarBarWidth(mCarDialLeft,displayFrames.mRotation,mDisplayContent.getConfiguration().uiMode); } if(mCarDialRight!=null && mCarDialRight.isVisibleLw()){ right = getNavigationCarBarWidth(mCarDialRight,displayFrames.mRotation,mDisplayContent.getConfiguration().uiMode); } } } if(DEBUG_HUIDU){ Log.w(TAG,"huidu add getOwningPackage left "+left+",top="+top+",right="+(dfu.right - right)+",bottom="+(dfu.bottom - bottom)); } df.set(left, top, dfu.right - right, dfu.bottom - bottom); // df.set(left, top, dfu.right - right, dfu.bottom - bottom); //df.set(400, top, 1520, dfu.bottom - bottom); /// HD CODE ADD special-shaped screen if (SystemProperties.getBoolean("ro.hd.special.screen", false)) { Slog.d(TAG, "huidu insets.left: " + insets.left + ", insets.top: " + insets.top + ", insets.right: " + insets.right + ", insets.bottom: " + insets.bottom); Slog.d(TAG, "huidu pkg: " + pkg); String mpkg = SystemProperties.get("persist.hd.display.packagename", "cn.huidu.lcd.player"); if (!pkg.equals(mpkg)) { /// HD CODE ADD 为什么要平移窗口内容,因为切割屏时,它是从左上角原点往下 int mLeft = SystemProperties.getInt("ro.hd.display.left", 0); int mTop = SystemProperties.getInt("ro.hd.display.top", 0); int mRight = SystemProperties.getInt("ro.hd.display.right", 0); int mBottom = SystemProperties.getInt("ro.hd.display.bottom", 0); int navBarState = getNavBarState(mContext); int statusBarState = getStatusBarState(mContext); int navBarHeight = getNavigationBarHeight(displayFrames.mRotation, mDisplayContent.getConfiguration().uiMode); int statusBarHeight = getStatusBarHeight(displayFrames); Slog.d(TAG, " navBarState = " + navBarState + " navBarHeight = " + navBarHeight + " statusBarState = " + statusBarState + " statusBarHeight = " + statusBarHeight); if (navBarState == 1 && statusBarState == 0) { mTop -= statusBarHeight; } if (navBarState == 0) { mBottom += navBarHeight; } df.set(mLeft, mTop, dfu.right - mRight, dfu.bottom - mBottom); } Slog.d(TAG, "huidu dfu.left: " + dfu.left + ", dfu.top: " + dfu.top + ", dfu.right: " + dfu.right + ", dfu.bottom: " + dfu.bottom); } /// HD CODE END if (attached == null) { pf.set(df); if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) { final InsetsSource source = mDisplayContent.getInsetsPolicy() .getInsetsForDispatch(win).peekSource(ITYPE_IME); if (source != null) { pf.inset(source.calculateInsets(pf, false /* ignoreVisibility */)); } } vf.set(adjust != SOFT_INPUT_ADJUST_NOTHING ? displayFrames.mCurrent : displayFrames.mDock); } else { pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df); vf.set(attached.getVisibleFrameLw()); } cf.set(adjust != SOFT_INPUT_ADJUST_RESIZE ? displayFrames.mDock : displayFrames.mContent); dcf.set(displayFrames.mSystem); } else if (type == TYPE_INPUT_METHOD) { vf.set(displayFrames.mDock); cf.set(displayFrames.mDock); df.set(displayFrames.mDock); pf.set(displayFrames.mDock); // IM dock windows layout below the nav bar... pf.bottom = df.bottom = displayFrames.mUnrestricted.bottom; // ...with content insets above the nav bar cf.bottom = vf.bottom = displayFrames.mStable.bottom; if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) { // The status bar forces the navigation bar while it's visible. Make sure the IME // avoids the navigation bar in that case. if (mNavigationBarPosition == NAV_BAR_RIGHT) { pf.right = df.right = cf.right = vf.right = displayFrames.mStable.right; } else if (mNavigationBarPosition == NAV_BAR_LEFT) { pf.left = df.left = cf.left = vf.left = displayFrames.mStable.left; } } // In case the navigation bar is on the bottom, we use the frame height instead of the // regular height for the insets we send to the IME as we need some space to show // additional buttons in SystemUI when the IME is up. if (mNavigationBarPosition == NAV_BAR_BOTTOM) { final int rotation = displayFrames.mRotation; final int uimode = mService.mPolicy.getUiMode(); final int navHeightOffset = getNavigationBarFrameHeight(rotation, uimode) - getNavigationBarHeight(rotation, uimode); if (navHeightOffset > 0) { cf.bottom -= navHeightOffset; sf.bottom -= navHeightOffset; vf.bottom -= navHeightOffset; dcf.bottom -= navHeightOffset; } } // IM dock windows always go to the bottom of the screen. attrs.gravity = Gravity.BOTTOM; } else if (type == TYPE_VOICE_INTERACTION) { df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); if (adjust != SOFT_INPUT_ADJUST_RESIZE) { cf.set(displayFrames.mDock); } else { cf.set(displayFrames.mContent); } if (adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); } } else if (type == TYPE_WALLPAPER) { layoutWallpaper(displayFrames, pf, df, cf); } else if (type == TYPE_CAR_DIAL_LEFT) { // huidu update : 添加车载界面处理 df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); cf.set(displayFrames.mUnrestricted); vf.set(displayFrames.mUnrestricted); attrs.gravity = Gravity.LEFT; } else if (type == TYPE_CAR_DIAL_RIGHT) { // huidu update : 添加车载界面处理 df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); cf.set(displayFrames.mUnrestricted); vf.set(displayFrames.mUnrestricted); attrs.gravity = Gravity.RIGHT; } else if (win == mStatusBar || type == TYPE_NOTIFICATION_SHADE) { df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); cf.set(displayFrames.mStable); vf.set(displayFrames.mStable); if (adjust == SOFT_INPUT_ADJUST_RESIZE) { // cf.bottom should not be below the stable bottom, or the content might be obscured // by the navigation bar. if (cf.bottom > displayFrames.mContent.bottom) { cf.bottom = displayFrames.mContent.bottom; } } else { if (cf.bottom > displayFrames.mDock.bottom) { cf.bottom = displayFrames.mDock.bottom; } if (vf.bottom > displayFrames.mContent.bottom) { vf.bottom = displayFrames.mContent.bottom; } } } else { dcf.set(displayFrames.mSystem); final boolean isAppWindow = type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW; final boolean topAtRest = win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw(); if (isAppWindow && !topAtRest) { if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 && (fl & FLAG_FULLSCREEN) == 0 && (fl & FLAG_TRANSLUCENT_STATUS) == 0 && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (pfl & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) == 0) { // Ensure policy decor includes status bar dcf.top = displayFrames.mStable.top; } if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0 && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0 && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (pfl & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) == 0) { // Ensure policy decor includes navigation bar dcf.bottom = displayFrames.mStable.bottom; dcf.right = displayFrames.mStable.right; } } if (layoutInScreen && layoutInsetDecor) { if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + "): IN_SCREEN, INSET_DECOR"); // This is the case for a normal activity window: we want it to cover all of the // screen space, and it can take care of moving its contents to account for screen // decorations that intrude into that space. if (attached != null) { // If this window is attached to another, our display // frame is the same as the one we are attached to. setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, cf, vf, displayFrames); } else { if (type == TYPE_STATUS_BAR_ADDITIONAL || type == TYPE_STATUS_BAR_SUB_PANEL) { // Status bar panels are the only windows who can go on top of the status // bar. They are protected by the STATUS_BAR_SERVICE permission, so they // have the same privileges as the status bar itself. // // However, they should still dodge the navigation bar if it exists. pf.left = df.left = hasNavBar ? displayFrames.mDock.left : displayFrames.mUnrestricted.left; pf.top = df.top = displayFrames.mUnrestricted.top; pf.right = df.right = hasNavBar ? displayFrames.mRestricted.right : displayFrames.mUnrestricted.right; pf.bottom = df.bottom = hasNavBar ? displayFrames.mRestricted.bottom : displayFrames.mUnrestricted.bottom; if (DEBUG_LAYOUT) Slog.v(TAG, "Laying out status bar window: " + pf); } else if ((sysUiFl & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0 && (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW || type == TYPE_VOLUME_OVERLAY || type == TYPE_KEYGUARD_DIALOG)) { // Asking for layout as if the nav bar is hidden, lets the application // extend into the unrestricted overscan screen area. We only do this for // application windows and certain system windows to ensure no window that // can be above the nav bar can do this. df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); } else { df.set(displayFrames.mRestricted); pf.set(displayFrames.mRestricted); } if ((fl & FLAG_FULLSCREEN) == 0) { if (win.isVoiceInteraction()) { cf.set(displayFrames.mVoiceContent); } else { // IME Insets are handled on the client for ADJUST_RESIZE in the new // insets world if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_NONE || adjust != SOFT_INPUT_ADJUST_RESIZE) { cf.set(displayFrames.mDock); } else { cf.set(displayFrames.mContent); } } } else { // Full screen windows are always given a layout that is as if the status // bar and other transient decors are gone. This is to avoid bad states when // moving from a window that is not hiding the status bar to one that is. cf.set(displayFrames.mRestricted); } applyStableConstraints(sysUiFl, fl, cf, displayFrames); if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); } } } else if (layoutInScreen || (sysUiFl & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) { if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + "): IN_SCREEN"); // A window that has requested to fill the entire screen just // gets everything, period. if (type == TYPE_STATUS_BAR_ADDITIONAL || type == TYPE_STATUS_BAR_SUB_PANEL) { cf.set(displayFrames.mUnrestricted); df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); if (hasNavBar) { pf.left = df.left = cf.left = displayFrames.mDock.left; pf.right = df.right = cf.right = displayFrames.mRestricted.right; pf.bottom = df.bottom = cf.bottom = displayFrames.mRestricted.bottom; } if (DEBUG_LAYOUT) Slog.v(TAG, "Laying out IN_SCREEN status bar window: " + pf); } else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) { // The navigation bar has Real Ultimate Power. df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); if (DEBUG_LAYOUT) Slog.v(TAG, "Laying out navigation bar window: " + pf); } else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT) && ((fl & FLAG_FULLSCREEN) != 0)) { // Fullscreen secure system overlays get what they ask for. Screenshot region // selection overlay should also expand to full screen. cf.set(displayFrames.mUnrestricted); df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); } else if (type == TYPE_BOOT_PROGRESS) { // Boot progress screen always covers entire display. cf.set(displayFrames.mUnrestricted); df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); } else if ((sysUiFl & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0 && (type == TYPE_NOTIFICATION_SHADE || type == TYPE_TOAST || type == TYPE_DOCK_DIVIDER || type == TYPE_VOICE_INTERACTION_STARTING || (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) { // Asking for layout as if the nav bar is hidden, lets the // application extend into the unrestricted screen area. We // only do this for application windows (or toasts) to ensure no window that // can be above the nav bar can do this. // XXX This assumes that an app asking for this will also // ask for layout in only content. We can't currently figure out // what the screen would be if only laying out to hide the nav bar. cf.set(displayFrames.mUnrestricted); df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); } else if ((sysUiFl & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) { df.set(displayFrames.mRestricted); pf.set(displayFrames.mRestricted); // IME Insets are handled on the client for ADJUST_RESIZE in the new insets // world if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_NONE || adjust != SOFT_INPUT_ADJUST_RESIZE) { cf.set(displayFrames.mDock); } else { cf.set(displayFrames.mContent); } } else { cf.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); pf.set(displayFrames.mRestricted); } applyStableConstraints(sysUiFl, fl, cf, displayFrames); if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); } } else if (attached != null) { if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + "): attached to " + attached); // A child window should be placed inside of the same visible // frame that its parent had. setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, cf, vf, displayFrames); } else { if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + "): normal window"); // Otherwise, a normal window must be placed inside the content // of all screen decorations. if (type == TYPE_STATUS_BAR_ADDITIONAL) { // Status bar panels can go on // top of the status bar. They are protected by the STATUS_BAR_SERVICE // permission, so they have the same privileges as the status bar itself. cf.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); pf.set(displayFrames.mRestricted); } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) { // These dialogs are stable to interim decor changes. cf.set(displayFrames.mStable); df.set(displayFrames.mStable); pf.set(displayFrames.mStable); } else { pf.set(displayFrames.mContent); if (win.isVoiceInteraction()) { cf.set(displayFrames.mVoiceContent); df.set(displayFrames.mVoiceContent); } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) { cf.set(displayFrames.mDock); df.set(displayFrames.mDock); } else { cf.set(displayFrames.mContent); df.set(displayFrames.mContent); } if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); } } } dcf.setEmpty() 如何修改应用窗口大小
最新发布
09-11
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值