当前位置: 首页 > news >正文

Android SystemUI组件(06)导航栏创建分析虚拟按键

该系列文章总纲链接:专题分纲目录 Android SystemUI组件


本章关键点总结 & 说明:

说明:本章节持续迭代之前章节的思维导图,主要关注左侧SystemBars分析中导航栏部分即可。

1 导航栏创建之makeStatusBarView

通过上一篇文章的分析,我们知道 addNavigationBar是在addStatusBarWindow之后执行的,addStatusBarWindow代码实现如下:

private void addStatusBarWindow() {makeStatusBarView();//创建statusbar视图mStatusBarWindowManager = new StatusBarWindowManager(mContext);mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}

addStatusBarWindow中makeStatusBarView也执行了导航栏相关逻辑,相关代码如下:

protected PhoneStatusBarView makeStatusBarView() {final Context context = mContext;//...try {//关键点1:导航栏显示与否boolean showNav = mWindowManagerService.hasNavigationBar();//是否显示导航栏if (showNav) {//关键点2:加载导航栏布局mNavigationBarView =(NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);mNavigationBarView.setDisabledFlags(mDisabled);mNavigationBarView.setBar(this);mNavigationBarView.setOnVerticalChangedListener(new NavigationBarView.OnVerticalChangedListener() {@Overridepublic void onVerticalChanged(boolean isVertical) {if (mSearchPanelView != null) {mSearchPanelView.setHorizontal(isVertical);}mNotificationPanel.setQsScrimEnabled(!isVertical);}});//设置导航栏触摸事件mNavigationBarView.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {checkUserAutohide(v, event);return false;}});}} catch (RemoteException ex) {// no window manager? good luck with that}//...startGlyphRasterizeHack();return mStatusBarView;
}

这里主要分析两个部分:navigationBar显示与否 和 导航栏Layout相关。

1.1 navigationBar显示与否

导航栏显示与否关键在于方法mWindowManagerService.hasNavigationBar的实现,这里最终是掉用到了PhoneWindowManager中的hasNavigationBar方法。代码如下所示:

public class PhoneWindowManager implements WindowManagerPolicy {//...public boolean hasNavigationBar() {return mHasNavigationBar;}//...
}

这里变量mHasNavigationBar的赋值操作为:

mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);

这里可以看到 导航栏的配置主要是在/res/res/values中名为config_showNavigationBar的标识,即为默认的配置,如下所示:

    <!-- Whether a software navigation bar should be shown. NOTE: in the future this may beautodetected from the Configuration. --><bool name="config_showNavigationBar">false</bool>

因此,这里可以根据需求修改该配置。

1.2 导航栏Layout相关

加载导航栏布局的语句为:

NavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);

从布局文件(res\layout\navigation_bar.xml)中来来看,xml文件内容如下:

<com.android.systemui.statusbar.phone.NavigationBarViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:systemui="http://schemas.android.com/apk/res-auto"android:layout_height="match_parent"android:layout_width="match_parent"android:background="@drawable/system_bar_background"><!--横向导航栏--><FrameLayout android:id="@+id/rot0"android:layout_height="match_parent"android:layout_width="match_parent"><LinearLayoutandroid:layout_height="match_parent"android:layout_width="match_parent"android:orientation="horizontal"android:clipChildren="false"android:clipToPadding="false"android:id="@+id/nav_buttons"android:animateLayoutChanges="true"><!-- navigation controls --><Viewandroid:layout_width="@dimen/navigation_side_padding"android:layout_height="match_parent"android:layout_weight="0"android:visibility="invisible"/><!--back按钮--><com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:src="@drawable/ic_sysbar_back"systemui:keyCode="4"android:layout_weight="0"android:scaleType="center"android:contentDescription="@string/accessibility_back"/><View android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:visibility="invisible"/><!--home按钮--><com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:src="@drawable/ic_sysbar_home"systemui:keyCode="3"systemui:keyRepeat="false"android:layout_weight="0"android:scaleType="center"android:contentDescription="@string/accessibility_home"/><View android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:visibility="invisible"/><!--recent按钮--><com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"android:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:src="@drawable/ic_sysbar_recent"android:layout_weight="0"android:scaleType="center"android:contentDescription="@string/accessibility_recent"/><FrameLayoutandroid:layout_width="@dimen/navigation_side_padding"android:layout_height="match_parent"android:layout_weight="0" ><com.android.systemui.statusbar.policy.KeyButtonViewandroid:id="@+id/menu"android:layout_width="@dimen/navigation_extra_key_width"android:layout_height="match_parent"android:contentDescription="@string/accessibility_menu"android:src="@drawable/ic_sysbar_menu"android:visibility="invisible"android:scaleType="centerInside"android:layout_gravity="end"systemui:keyCode="82" /><com.android.systemui.statusbar.policy.KeyButtonViewandroid:id="@+id/ime_switcher"android:layout_width="@dimen/navigation_extra_key_width"android:layout_height="match_parent"android:contentDescription="@string/accessibility_ime_switch_button"android:scaleType="centerInside"android:src="@drawable/ic_ime_switcher_default"android:visibility="invisible"android:layout_gravity="end" /></FrameLayout></LinearLayout><!-- lights out layout to match exactly --><LinearLayoutandroid:layout_height="match_parent"android:layout_width="match_parent"android:orientation="horizontal"android:id="@+id/lights_out"android:visibility="gone"><ImageViewandroid:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:layout_marginStart="@dimen/navigation_side_padding"android:src="@drawable/ic_sysbar_lights_out_dot_small"android:scaleType="center"android:layout_weight="0"/><View android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"android:visibility="invisible"/><ImageViewandroid:layout_width="@dimen/navigation_key_width"android:layout_height="match_parent"android:src="@drawable/ic_sysbar_lights_out_dot_large"android:scaleType="center"android:layout_weight="0"/><View android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"android:visibility="invisible"/><ImageViewandroid:layout_width="@dimen/navigation_key_width"android:layout_marginEnd="@dimen/navigation_side_padding"android:layout_height="match_parent"android:src="@drawable/ic_sysbar_lights_out_dot_small"android:scaleType="center"android:layout_weight="0"/></LinearLayout><com.android.systemui.statusbar.policy.DeadZoneandroid:id="@+id/deadzone"android:layout_height="match_parent"android:layout_width="match_parent"systemui:minSize="@dimen/navigation_bar_deadzone_size"systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"systemui:holdTime="@integer/navigation_bar_deadzone_hold"systemui:decayTime="@integer/navigation_bar_deadzone_decay"systemui:orientation="horizontal"android:layout_gravity="top"/></FrameLayout><!--纵向显示--><FrameLayout android:id="@+id/rot90"android:layout_height="match_parent"android:layout_width="match_parent"android:visibility="gone"android:paddingTop="0dp"><LinearLayout android:layout_height="match_parent"android:layout_width="match_parent"android:orientation="vertical"android:clipChildren="false"android:clipToPadding="false"android:id="@+id/nav_buttons"android:animateLayoutChanges="true"><!-- navigation controls --><FrameLayoutandroid:layout_weight="0"android:layout_width="match_parent"android:layout_height="@dimen/navigation_side_padding" ><com.android.systemui.statusbar.policy.KeyButtonViewandroid:id="@+id/ime_switcher"android:layout_width="match_parent"android:layout_height="@dimen/navigation_extra_key_width"android:contentDescription="@string/accessibility_ime_switch_button"android:scaleType="centerInside"android:src="@drawable/ic_ime_switcher_default"android:layout_gravity="top"android:visibility="invisible" /><com.android.systemui.statusbar.policy.KeyButtonViewandroid:id="@+id/menu"android:layout_width="match_parent"android:layout_height="40dp"android:contentDescription="@string/accessibility_menu"android:src="@drawable/ic_sysbar_menu_land"android:scaleType="centerInside"android:layout_gravity="top"android:visibility="invisible"systemui:keyCode="82" /></FrameLayout><!--recent按钮--><com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/recent_apps"android:layout_height="@dimen/navigation_key_width"android:layout_width="match_parent"android:src="@drawable/ic_sysbar_recent_land"android:scaleType="center"android:layout_weight="0"android:contentDescription="@string/accessibility_recent"/><View android:layout_height="match_parent"android:layout_width="match_parent"android:layout_weight="1"android:visibility="invisible"/><!--home按钮--><com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/home"android:layout_height="@dimen/navigation_key_width"android:layout_width="match_parent"android:src="@drawable/ic_sysbar_home_land"android:scaleType="center"systemui:keyCode="3"systemui:keyRepeat="false"android:layout_weight="0"android:contentDescription="@string/accessibility_home"/><View android:layout_height="match_parent"android:layout_width="match_parent"android:layout_weight="1"android:visibility="invisible"/><!--back按钮--><com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back"android:layout_height="@dimen/navigation_key_width"android:layout_width="match_parent"android:src="@drawable/ic_sysbar_back_land"android:scaleType="center"systemui:keyCode="4"android:layout_weight="0"android:contentDescription="@string/accessibility_back"/><Viewandroid:layout_height="@dimen/navigation_side_padding"android:layout_width="match_parent"android:layout_weight="0"android:visibility="invisible"/></LinearLayout><!-- lights out layout to match exactly --><LinearLayout android:layout_height="match_parent"android:layout_width="match_parent"android:orientation="vertical"android:id="@+id/lights_out"android:visibility="gone"><ImageViewandroid:layout_height="@dimen/navigation_key_width"android:layout_marginTop="@dimen/navigation_side_padding"android:layout_width="match_parent"android:src="@drawable/ic_sysbar_lights_out_dot_small"android:scaleType="center"android:layout_weight="0"/><View android:layout_height="match_parent"android:layout_width="match_parent"android:layout_weight="1"android:visibility="invisible"/><ImageViewandroid:layout_height="@dimen/navigation_key_width"android:layout_width="match_parent"android:src="@drawable/ic_sysbar_lights_out_dot_large"android:scaleType="center"android:layout_weight="0"/><View android:layout_height="match_parent"android:layout_width="match_parent"android:layout_weight="1"android:visibility="invisible"/><ImageViewandroid:layout_height="@dimen/navigation_key_width"android:layout_marginBottom="@dimen/navigation_side_padding"android:layout_width="match_parent"android:src="@drawable/ic_sysbar_lights_out_dot_small"android:scaleType="center"android:layout_weight="0"/></LinearLayout><com.android.systemui.statusbar.policy.DeadZoneandroid:id="@+id/deadzone"android:layout_height="match_parent"android:layout_width="match_parent"systemui:minSize="@dimen/navigation_bar_deadzone_size"systemui:maxSize="@dimen/navigation_bar_deadzone_size_max"systemui:holdTime="@integer/navigation_bar_deadzone_hold"systemui:decayTime="@integer/navigation_bar_deadzone_decay"systemui:orientation="vertical"android:layout_gravity="top"/></FrameLayout></com.android.systemui.statusbar.phone.NavigationBarView>

可以看到,横向和纵向是加载两个不同的FrameLayout配置文件。由于文件过长,这里使用简图来描述,如下:

简单解读说明如下:

  • nav_buttons:4个控件,back,home,recent,menu。
  • lights_out:多数情况不可见,当处于低辨识度模式下,nav_buttons隐藏且lights_out显示,显示为三个不明显的小灰点,降低对用户视线的干扰。
  • search_light:多数情况不可见,当HOME按键被禁后serach功能还可用,此时会变成可见,用于提示用户该功能可使用。
  • deadzone:防止边界误触操作。

导航栏显示以及布局由屏幕的方向来决定,而导航栏有两种不同的显示方式,横向显示和竖向显示,同时 我从mRotatedViews变量 分析:

public class NavigationBarView extends LinearLayout {//...View[] mRotatedViews = new View[4];//...//布局加载完成后,会回调onFinishInflate方法@Overridepublic void onFinishInflate() {//屏幕方位0和180方向显示的导航栏为rot0,90和270显示的导航栏为rot90mRotatedViews[Surface.ROTATION_0] =mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90];mCurrentView = mRotatedViews[Surface.ROTATION_0];getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);updateRTLOrder();}
}

这里也可以看到,在加载完xml文件后,会根据不同的旋转角度加载不同的layout布局文件。

2 导航栏创建之addNavigationBar 入口分析

addNavigationBar 代码实现如下:

// For small-screen devices (read: phones) that lack hardware navigation buttons
private void addNavigationBar() {if (mNavigationBarView == null) return;//关键点1prepareNavigationBarView();//关键点2: getNavigationBarLayoutParams分析mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
}

2.1 prepareNavigationBarView分析

继续分析prepareNavigationBarView,代码实现如下:

private void prepareNavigationBarView() {mNavigationBarView.reorient();//设置导航栏三个图标的点击事件mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);mNavigationBarView.getRecentsButton().setLongClickable(true);mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener);mNavigationBarView.getBackButton().setLongClickable(true);mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);updateSearchPanel();
}

导航栏布局的明确显示在prepareNavigationBarView中的mNavigationBarView.reorient();来决定,我们查看reorient方法,代码实现如下:

public void reorient() {//获取屏幕旋转方向final int rot = mDisplay.getRotation();//隐藏导航栏布局for (int i=0; i<4; i++) {mRotatedViews[i].setVisibility(View.GONE);}//根据屏幕方向显示导航栏布局mCurrentView = mRotatedViews[rot];mCurrentView.setVisibility(View.VISIBLE);getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);//初始化导航栏的转换效果,这些效果可能包括动画和过渡。mBarTransitions.init(mVertical);//根据 mDisabledFlags 设置导航栏的禁用状态setDisabledFlags(mDisabledFlags, true /* force */);//设置菜单按钮的可见性setMenuVisibility(mShowMenu, true /* force */);//如果导航栏处于横屏/垂直模式,mDelegateHelper 对象交换 X 和 Y 坐标,以适应横屏布局。if (mDelegateHelper != null) {mDelegateHelper.setSwapXY(mVertical);}updateTaskSwitchHelper();setNavigationIconHints(mNavigationIconHints, true);
}

2.2 getNavigationBarLayoutParams分析

我们回到PhoneStatusBar的addNavigationBar继续分析最后一个导航栏的LayoutParameters,它决定了导航栏在窗体上的显示位置,getNavigationBarLayoutParams代码实现如下:

private WindowManager.LayoutParams getNavigationBarLayoutParams() {/*初始化参数说明如下:FLAG_TOUCHABLE_WHEN_WAKING:当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到事件FLAG_NOT_FOCUSABLE:不获取焦点FLAG_NOT_TOUCH_MODAL:即使该window在可获得焦点情况下,仍然把该window之外的任何event发送到该window之后的其他windowFLAG_WATCH_OUTSIDE_TOUCH:不接受事件,转发到其他windowFLAG_SPLIT_TOUCH:当window设置这个flag,window会接收来自window边界之外发送给其他window的点击事件,支持多点触控.当这个flag没有设置的时候,第一下点击则决定了哪个window会接收整个点击事件,直到手指拿开.当设置了这个flag,这每一个点击事件(不一定是第一个)都决定了那个window来接收剩下的点击事件,直到手指拿开.点击事件会被分开传递给多个window.*/WindowManager.LayoutParams lp = new WindowManager.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,0| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,PixelFormat.TRANSLUCENT);// this will allow the navbar to run in an overlay on devices that support thisif (ActivityManager.isHighEndGfx()) {//硬件加速参数lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}lp.setTitle("NavigationBar");//窗口名称lp.windowAnimations = 0;//不设置窗口动画return lp;
}

上面的LayoutParames决定了导航栏在窗体的大小和显示的位置效果,当然这也是受父窗口影响的,当我们有变更导航栏的显示需求时就可以同各国修正LayoutParames参数来解决。

3 导航栏虚拟按键工作原理

虚拟按键是用来替代物理按键的,而这也是导航栏最重要的工作,输入子系统(IMS)中有一个关键的方法:injectInputEvent,即直接模拟物理按键上报输入事件,它是虚拟按键的实现基础。导航栏中的KeyButtonView就是该接口使用者之一,KeyButtonView中最重要的字段是mCode,用于指示其生成的按键事件的键值。

导航栏中有4个KeyButtonView,其中back、home、menu分别产生 KEY_BACK、KEY_HOME 、KEY_MENU 三种按键事件,recent不产生按键事件。

接下来关注KeyButtonView的两个部分:从触屏事件转换到按键事件、键盘事件发送。

3.1 从触屏事件转换到按键事件

这里从KeyButtonView的onTouchEvent()方法(该方法是触屏的回调方法)开始分析,代码实现如下:

public boolean onTouchEvent(MotionEvent ev) {final int action = ev.getAction();int x, y;switch (action) {//触屏事件-按下case MotionEvent.ACTION_DOWN://Log.d("KeyButtonView", "press");mDownTime = SystemClock.uptimeMillis();setPressed(true);//如果mCode被设置有值,则发送按键事件KeyEvent.ACTION_DOWNif (mCode != 0) {sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);} else {// Provide the same haptic feedback that the system offers for virtual keys.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);}if (mSupportsLongpress) {removeCallbacks(mCheckLongPress);postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());}break;//触屏事件-移动case MotionEvent.ACTION_MOVE:x = (int)ev.getX();y = (int)ev.getY();setPressed(x >= -mTouchSlop&& x < getWidth() + mTouchSlop&& y >= -mTouchSlop&& y < getHeight() + mTouchSlop);break;//触屏事件-取消case MotionEvent.ACTION_CANCEL:setPressed(false);//如果mCode被设置有值,则发送按键事件KeyEvent.ACTION_UP,带标记KeyEvent.FLAG_CANCELEDif (mCode != 0) {sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);}if (mSupportsLongpress) {removeCallbacks(mCheckLongPress);}break;//触屏事件-抬起case MotionEvent.ACTION_UP:final boolean doIt = isPressed();setPressed(false);//如果mCode被设置有值,则发送按键事件KeyEvent.ACTION_UPif (mCode != 0) {if (doIt) {sendEvent(KeyEvent.ACTION_UP, 0);sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);playSoundEffect(SoundEffectConstants.CLICK);} else {sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);}} else {//如果没设置,则此时触发OnClickListener//导航栏会采用这种方式来处理事件// no key code, just a regular ImageViewif (doIt) {performClick();}}if (mSupportsLongpress) {removeCallbacks(mCheckLongPress);}break;}return true;
}

整个过程就是 触摸事件转换成按键事件的一个过程。

3.2 键盘事件发送

接下来专注分析KeyButtonView的sendEvent方法,代码实现如下:

public void sendEvent(int action, int flags) {sendEvent(action, flags, SystemClock.uptimeMillis());
}

继续分析,代码实现如下:

void sendEvent(int action, int flags, long when) {//计算重复次数repeatCountfinal int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;//根据参数构建KeyEvent事件final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,InputDevice.SOURCE_KEYBOARD);//将KeyEvent事件加入到InputDispatcher的派发队列。//说明:INJECT_INPUT_EVENT_MODE_ASYNC表示加入派发队列后立刻返回,不阻塞,也不等待事件派发的成功与否。InputManager.getInstance().injectInputEvent(ev,InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}

接下来最关键的就是执行InputManager的injectInputEvent方法了。这一部分属于输入子系统了,感兴趣的伙伴可查看这篇文章的后半部分:

Android Framework 输入子系统 (10)Input命令解读_input swipe

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 在Linux中从视频流截取图片帧(ffmpeg )
  • 去除恢复出厂设置中UI文字显示
  • 3.无人机介绍
  • 《人工智能安全治理框架》1.0版
  • 【算法】模拟退火
  • [linux 驱动]i2c总线设备驱动详解与实战
  • 揭开Facebook AI的神秘面纱:如何利用人工智能提升社交体验
  • C++ | Leetcode C++题解之第395题至少有K个重复字符的最长子串
  • 还能买燃油车吗
  • Pygame中Sprite类实现多帧动画3-1
  • 一个例子彻底搞懂对线程模型的理解 !
  • 【Puppeteer】‘left‘ is already pressed, ‘${button}‘ is already pressed 的解决办法
  • Qt常用控件——QRadioButton和QCheckBox
  • 【VSCode v1.93.0】手动配置远程remote-ssh
  • 开源可视化大屏superset Docker环境部署
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • CEF与代理
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • fetch 从初识到应用
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • Js基础知识(四) - js运行原理与机制
  • Laravel 中的一个后期静态绑定
  • leetcode98. Validate Binary Search Tree
  • Linux gpio口使用方法
  • miaov-React 最佳入门
  • Redux 中间件分析
  • socket.io+express实现聊天室的思考(三)
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • VUE es6技巧写法(持续更新中~~~)
  • Vue官网教程学习过程中值得记录的一些事情
  • win10下安装mysql5.7
  • 笨办法学C 练习34:动态数组
  • 搭建gitbook 和 访问权限认证
  • 多线程 start 和 run 方法到底有什么区别?
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 聊一聊前端的监控
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • ​Python 3 新特性:类型注解
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • ## 1.3.Git命令
  • #### go map 底层结构 ####
  • #{} 和 ${}区别
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • (26)4.7 字符函数和字符串函数
  • (function(){})()的分步解析
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (附源码)php新闻发布平台 毕业设计 141646
  • (过滤器)Filter和(监听器)listener
  • (三)Kafka离线安装 - ZooKeeper开机自启
  • (三)uboot源码分析
  • (十六)视图变换 正交投影 透视投影
  • (四)js前端开发中设计模式之工厂方法模式
  • (转)3D模板阴影原理