1. 是什么?

ColorStateList(颜色状态列表)是一个可以定义在 XML 布局文件中,并最终根据 ColorStateList 应用的 View 的状态显示不同颜色的对象。

A ColorStateList is an object you can define in XML that you can apply as a color, but will actually change colors, depending on the state of the View object to which it is applied.

最终效果如下:

界面中两按钮文字的颜色随着按钮的状态而改变。

2. 怎么用?

从 ColorStateList 的定义可以知道,创建 ColorStateList 的方式应该不止有一种。接下来,我们就尝试从两方面创建 ColorStateList:

  1. XML
  2. Java 代码

2.1 如何在 XML 中定义 ColorStateList

2.1.1 文件位置
res/color/filename.xml
复制代码
2.1.2 编译之后的数据类型
ColorStateList
复制代码
2.1.3 应用方式
  1. In Java: R.color.filename
  2. In XML: @[package:]color/filename
2.1.4 语法
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" ><itemandroid:color="hex_color"android:state_pressed=["true" | "false"]android:state_focused=["true" | "false"]android:state_selected=["true" | "false"]android:state_checkable=["true" | "false"]android:state_checked=["true" | "false"]android:state_enabled=["true" | "false"]android:state_window_focused=["true" | "false"] />
</selector>
复制代码
2.1.5 属性解析
属性 定义 取值范围
color 不同状态的颜色值 十六进制的颜色值。
可以是如下格式:
#RGB
#ARGB
#RRGGBB
#AARRGGBB
state_pressed View 按下的状态 true,false。
true,按下;
false,默认状态,即没有按下之前的状态。
state_selected View 选中的状态 true,false。
true,选中;
false,未选中。

其他的属性类似,在此就不做赘述了。想要了解更多关于 state_xxx 的内容,请查看Color state list resource。

2.1.6 示例
//1. text_color_state_list.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/green_700" android:state_pressed="true" /><item android:color="@color/grey_700" android:state_pressed="false" /><!--默认项--><item android:color="@color/grey_700" />
</selector>
复制代码
//2. 在 XML 布局文件中应用 text_color_state_list
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"tools:context=".MainActivity"><Buttonandroid:id="@+id/alphabet_a"android:layout_width="@dimen/avatar_size"android:layout_height="@dimen/padding_seventy_two"android:text="@string/alphabet_a"android:textColor="@color/text_color_state_list"android:textSize="@dimen/font_thirty_two" /></LinearLayout>
复制代码

最终效果如下:

//3. 在 Java 代码中使用 text_color_state_list
public class MainActivity extends AppCompatActivity {private Button  mAlphaB;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView(){mAlphaB = findViewById(R.id.alphabet_b);Resources resources = getResources();ColorStateList colorStateList = resources.getColorStateList(R.color.text_color_state_list);mAlphaB.setTextColor(colorStateList);}}
复制代码

在 Java 中使用在 XML 中定义的 ColorStateList 的效果与在 XML 中使用在 XML 中定义的 ColorStateList 的效果一样,所以就不赘述了。

2.1.7 注意事项
2.1.7.1 ColorStateList 中定义的默认 Item 一定要放在最下面

ColorStateList 中定义的默认 Item 一定要放在最下面,否则后面的 Item 将被忽略,Android Framework 在此处选择资源的时候,并不是按照“最优选项”选择的,而是按照从上到下选择第一个匹配的。

Remember that the first item in the state list that matches the current state of the object will be applied. So if the first item in the list contains none of the state attributes above, then it will be applied every time, which is why your default value should always be last, as demonstrated in the following example.

举个例子:

  1. 默认 Item 放在最下面:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/green_700" android:state_pressed="true" /><item android:color="@color/grey_700" android:state_pressed="false" /><!--默认项--><item android:color="@color/grey_700" />
</selector>
复制代码

最终效果如下:

  1. 默认 Item 放在最上面:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><!--默认项--><item android:color="@color/grey_700" /><item android:color="@color/green_700" android:state_pressed="true" /><item android:color="@color/grey_700" android:state_pressed="false" />
</selector>
复制代码

最终效果如下:

由上面的运行效果可知:当默认的 Item 在最上面的时候,Button 的文字颜色并不会随着 Button 状态的改变而改变。因此在后面定义 ColorStateList 的时候,如果想要应用 ColorStateList 的 View 内容(字体或者其他)的颜色随着 View 的状态而改变,就需要把 ColorStateList 中默认的 Item 定义在最下面。

2.1.7.2 ColorStateList 是不能用于 View 的 Background
//1. View 部分源码
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {...public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {this(context);final TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);...for (int i = 0; i < N; i++) {int attr = a.getIndex(i);switch (attr) {case com.android.internal.R.styleable.View_background:background = a.getDrawable(attr);break;...}...}...}...}
复制代码

由 View 源码可知:View 的 Background 最终是通过 TypedArray 的 GetDrawable 方法获取的。

//2. TypedArray 部分源码
public class TypedArray {.../*** Retrieve the Drawable for the attribute at <var>index</var>.* <p>* This method will throw an exception if the attribute is defined but is* not a color or drawable resource.** @param index Index of attribute to retrieve.** @return Drawable for the attribute, or {@code null} if not defined.* @throws RuntimeException if the TypedArray has already been recycled.* @throws UnsupportedOperationException if the attribute is defined but is*         not a color or drawable resource.*/@Nullablepublic Drawable getDrawable(@StyleableRes int index) {return getDrawableForDensity(index, 0);}...}
复制代码

由 TypedArray 源码可知,在 TypedArray 的 GetDrawable 中只能接收纯 Color 或者 Drawable Resource,而 ColorStateList 并未在此范围内,因此 ColorStateList 是不能用于 View 的 Background(如果在 View 的 Background 中引用 ColorStateList,应用程序将会 Crash)。

throws UnsupportedOperationException if the attribute is defined but is not a color or drawable resource.

2.1.7.2 StateListDrawable 是不能用于 TextView 系的 TextColor
//1. TextView 部分源码
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {...public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {...readTextAppearance(context, a, attributes, true /* styleArray */);...}...
}
复制代码
//2. readTextAppearance 方法
private void readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray) {...for (int i = 0; i < n; i++) {...switch (index) {case com.android.internal.R.styleable.TextAppearance_textColorHighlight:attributes.mTextColorHighlight = appearance.getColor(attr, attributes.mTextColorHighlight);break;...}...}...}
复制代码

通过 TextView 源码可知,TextView 的 TextColor 最终是通过 TypedArray 的 GetColor 方法获取的。

//3. TypedArray 部分源码
public class TypedArray {.../*** Retrieve the color value for the attribute at <var>index</var>.  If* the attribute references a color resource holding a complex* {@link android.content.res.ColorStateList}, then the default color from* the set is returned.* <p>* This method will throw an exception if the attribute is defined but is* not an integer color or color state list.** @param index Index of attribute to retrieve.* @param defValue Value to return if the attribute is not defined or*                 not a resource.** @return Attribute color value, or defValue if not defined.* @throws RuntimeException if the TypedArray has already been recycled.* @throws UnsupportedOperationException if the attribute is defined but is*         not an integer color or color state list.*/@ColorIntpublic int getColor(@StyleableRes int index, @ColorInt int defValue) {if (mRecycled) {throw new RuntimeException("Cannot make calls to a recycled instance!");}final int attrIndex = index;index *= STYLE_NUM_ENTRIES;final int[] data = mData;final int type = data[index + STYLE_TYPE];if (type == TypedValue.TYPE_NULL) {return defValue;} else if (type >= TypedValue.TYPE_FIRST_INT&& type <= TypedValue.TYPE_LAST_INT) {return data[index + STYLE_DATA];} else if (type == TypedValue.TYPE_STRING) {final TypedValue value = mValue;if (getValueAt(index, value)) {final ColorStateList csl = mResources.loadColorStateList(value, value.resourceId, mTheme);return csl.getDefaultColor();}return defValue;} else if (type == TypedValue.TYPE_ATTRIBUTE) {final TypedValue value = mValue;getValueAt(index, value);throw new UnsupportedOperationException("Failed to resolve attribute at index " + attrIndex + ": " + value);}throw new UnsupportedOperationException("Can't convert value at index " + attrIndex+ " to color: type=0x" + Integer.toHexString(type));}...}复制代码

由 TypedArray 源码可知,在 TypedArray 的 getColor 中只能接收纯 Color 或者 Color State List,而 StateListDrawable 并未在此范围内,因此 StateListDrawable 是不能用于 TextView 系的 TextColor(如果在 TextView 的 TextColor 中引用 StateListDrawable 程序将会出 Bug,但是不会 Crash)。

throws UnsupportedOperationException if the attribute is defined but is not an integer color or color state list.

2.2 如何在代码中定义 ColorStateList

2.2.1 ColorStateList 源码解析

ColorStateList 部分源码如下:

public class ColorStateList extends ComplexColor implements Parcelable {.../*** Creates a ColorStateList that returns the specified mapping from* states to colors.*/public ColorStateList(int[][] states, @ColorInt int[] colors) {mStateSpecs = states;mColors = colors;onColorsChanged();}...}
复制代码

由上面的源码可知,在创建 ColorStateList 的时候,需要传入两个数组,第一个数组是存储状态值的,第二个数组是存储状态对应颜色值的。
简单对比一下 XML 中定义 ColorStateList 的语法,其实很容易就明白为什么在 ColorStateList 构造方法中存储状态值的数组是二维数组。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" ><itemandroid:color="hex_color"android:state_pressed=["true" | "false"]android:state_focused=["true" | "false"]android:state_selected=["true" | "false"]android:state_checkable=["true" | "false"]android:state_checked=["true" | "false"]android:state_enabled=["true" | "false"]android:state_window_focused=["true" | "false"] />
</selector>
复制代码

因为在每一个 Item 中可以有很多个状态(state_xxx),每一个 Item 中的所有这些状态只对应一个颜色值。也就是说,ColorStateList 构造方法中的存储状态的数组的第一层数组的 Size 只要和存储状态对应颜色值的数组的 Size 一致就好了。

举个例子(伪代码):

//状态值(states 第一层 size 为 2)
int[][] states = new int[2][];
states[0] = new int[] {android.R.attr.state_xxx};
states[1] = new int[] {};
//不同状态对应的颜色值(colors size 为 2)
int[] colors = new int[] { R.color.pressed, R.color.normal};
ColorStateList colorList = new ColorStateList(states, colors);
复制代码
2.2.2 示例
public class MainActivity extends AppCompatActivity {private Button  mAlphaB;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView(){mAlphaB = findViewById(R.id.alphabet_b);ColorStateList colorStateList = createColorStateList(getResources().getColor(R.color.green_700), getResources().getColor(R.color.grey_700));mAlphaB.setTextColor(colorStateList);}private ColorStateList createColorStateList(int pressed, int normal) {//状态int[][] states = new int[2][];//按下states[0] = new int[] {android.R.attr.state_pressed};//默认states[1] = new int[] {};//状态对应颜色值(按下,默认)int[] colors = new int[] { pressed, normal};ColorStateList colorList = new ColorStateList(states, colors);return colorList;}}
复制代码

最终效果如下:

2.2.3 自定义 ColorStateList

除了上面的方式之外,还可以继承 ColorStateList 实现自定义 ColorStateList,但由于 ColorStateList 可更改的属性太少,所以自定义 ColorStateList 并没有什么意义。

简单示例:

public class CustomColorStateList extends ColorStateList {public CustomColorStateList(int[][] states, int[] colors) {super(states, colors);}}
复制代码

具体使用方法同《2.2.2 示例》一样,所以再次不做赘述。

3. 工作原理

下面是在代码中使用在 XML 布局文件中创建的 ColorStateList 的方法:

//1. text_color_state_list.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/green_700" android:state_pressed="true" /><item android:color="@color/grey_700" android:state_pressed="false" /><!--默认项--><item android:color="@color/grey_700" />
</selector>
复制代码
//2. 在 Java 代码中使用 text_color_state_list
public class MainActivity extends AppCompatActivity {private Button  mAlphaB;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView(){mAlphaB = findViewById(R.id.alphabet_b);Resources resources = getResources();ColorStateList colorStateList = resources.getColorStateList(R.color.text_color_state_list);mAlphaB.setTextColor(colorStateList);}}
复制代码

既然是通过 Button 的 SetTextColor 方法将 ColorStateList 应用到 Button 的字体颜色上的,那接下来就进到 Button 的 SetTextColor 方法一看究竟。

//3. 进入 Button 的 setTextColor 方法
public class TextView{...@android.view.RemotableViewMethodpublic void setTextColor(ColorStateList colors) {if (colors == null) {throw new NullPointerException();}mTextColor = colors;updateTextColors();}...}
复制代码

因为 Button 继承至 TextView,Button 的 SetTextColor 方法继承至 TextView,且未做任何更改,因此直接进入了 TextView 类中。

在 TextView 类的 SetTextColor 方法中调用了 UpdateTextColors 方法。

//4. 进入 updateTextColors 方法
public class TextView{...private void updateTextColors() {boolean inval = false;final int[] drawableState = getDrawableState();int color = mTextColor.getColorForState(drawableState, 0);if (color != mCurTextColor) {mCurTextColor = color;inval = true;}if (mLinkTextColor != null) {color = mLinkTextColor.getColorForState(drawableState, 0);if (color != mTextPaint.linkColor) {mTextPaint.linkColor = color;inval = true;}}if (mHintTextColor != null) {color = mHintTextColor.getColorForState(drawableState, 0);if (color != mCurHintTextColor) {mCurHintTextColor = color;if (mText.length() == 0) {inval = true;}}}if (inval) {// Text needs to be redrawn with the new colorif (mEditor != null) mEditor.invalidateTextDisplayList();invalidate();}}...}
复制代码

接着看下在 TextView 类中,哪里都调用了 TextView 的 UpdateTextColors 方法。

最终找到了 TextView 的 DrawableStateChanged 方法,即在 TextView 的 DrawableStateChanged 方法中调用了 TextView 的 UpdateTextColors 方法。

//5. 进入 drawableStateChanged 方法
public class TextView{...@Overrideprotected void drawableStateChanged() {super.drawableStateChanged();if (mTextColor != null && mTextColor.isStateful()|| (mHintTextColor != null && mHintTextColor.isStateful())|| (mLinkTextColor != null && mLinkTextColor.isStateful())) {updateTextColors();}if (mDrawables != null) {final int[] state = getDrawableState();for (Drawable dr : mDrawables.mShowing) {if (dr != null && dr.isStateful() && dr.setState(state)) {invalidateDrawable(dr);}}}}...}
复制代码

在 TextView 类的 DrawableStateChanged 方法中调用了父类的 DrawableStateChanged 方法,进入 TextView 的父类(View)中看下哪里都调用了 DrawableStateChanged 方法。

最终找到了 View 的 RefreshDrawableState 方法,即在 View 的 RefreshDrawableState 方法中调用了 DrawableStateChanged 方法。

//6. 进入 refreshDrawableState 方法
public class View{.../*** Call this to force a view to update its drawable state. This will cause* drawableStateChanged to be called on this view. Views that are interested* in the new state should call getDrawableState.** @see #drawableStateChanged* @see #getDrawableState*/public void refreshDrawableState() {mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;drawableStateChanged();ViewParent parent = mParent;if (parent != null) {parent.childDrawableStateChanged(this);}}...
}
复制代码

在 View 类中看下哪里都调用了 RefreshDrawableState 方法。

在 View 类中,发现有多个方法都调用了 RefreshDrawableState 方法,如:

  • setEnabled(boolean enabled)
  • setPressed(boolean pressed)
  • onWindowFocusChanged(boolean hasWindowFocus)
  • setHovered(boolean hovered)
  • setSelected(boolean selected)
  • setActivated(boolean activated)

是不是有一种似曾相识的感觉:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" ><itemandroid:color="hex_color"android:state_pressed=["true" | "false"]android:state_focused=["true" | "false"]android:state_selected=["true" | "false"]android:state_checkable=["true" | "false"]android:state_checked=["true" | "false"]android:state_enabled=["true" | "false"]android:state_window_focused=["true" | "false"] />
</selector>
复制代码

接下来,我们随便挑一个方法来分析——SetPressed 方法。

//7. 进入 setPressed 方法
public class View{.../*** Sets the pressed state for this view.** @see #isClickable()* @see #setClickable(boolean)** @param pressed Pass true to set the View's internal state to "pressed", or false to reverts*        the View's internal state from a previously set "pressed" state.*/public void setPressed(boolean pressed) {final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);if (pressed) {mPrivateFlags |= PFLAG_PRESSED;} else {mPrivateFlags &= ~PFLAG_PRESSED;}if (needsRefresh) {refreshDrawableState();}dispatchSetPressed(pressed);}...
}
复制代码

接下来看下,在 View 类中哪里都调用了 SetPressed 方法。

在 View 类中,发现有多个方法都调用了 SetPressed 方法,如:

  • removeUnsetPressCallback
  • onFocusChanged
  • resetPressedState
  • dispatchGenericMotionEventInternal
  • onKeyDown
  • onKeyUp
  • onTouchEvent

在上面的这些方法中,有一个方法引起了我们注意——onTouchEvent 处理触屏事件的方法。

Implement this method to handle touch screen motion events.

public class View{...public boolean onTouchEvent(MotionEvent event) {...if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP:...break;case MotionEvent.ACTION_DOWN:...if (isInScrollingContainer) {...} else {// Not inside a scrolling container, so show the feedback right away/////                                                               ////                       只看这里就好啦                          ////                                                               /////setPressed(true, x, y);checkForLongClick(0, x, y);}break;case MotionEvent.ACTION_CANCEL:...break;case MotionEvent.ACTION_MOVE:...break;}return true;}return false;}...}
复制代码

到这里,我们不难发现最终 ColorStateList 是如何起作用的:

在 View 的 OnTouchEvent 中根据用户操作确定当前 View 的状态,选择与该状态对应的颜色值并将其设置到 View 的 Paint上,进而在刷新界面的时候应用新的颜色。在 TextView 系控件中表现为:根据 TextView 系控件的状态将与该状态对应的颜色值设置到当前控件的 TextPaint 上,进而在刷新界面的时候应用新的颜色。

4. ColorStateList 与 StateListDrawable 之间的关系

ColorStateList 与 StateListDrawable 其实并没有什么关系。

ColorStateList 继承至 Object,而 StateListDrawable 间接继承至 Drawable。

如果非要从它们两个中间找到共同点,那就是它们都能根据当前 View 的状态改变自己的显示内容(ColorStateList 根据 View 状态显示不同的 Color,StateListDrawable 根据 View 状态显示不同的 Drawable)。

5. 参考文献

  1. Color State List Resource
  2. ColorStateList

转载于:https://juejin.im/post/5cbc5baf6fb9a0687015d3a1

ColorStateList 使用详解相关推荐

  1. Android Material Design 系列之 BottomNavigationView + ViewPager + Fragment + BadgeView 开发详解

    前言 BottomNavigationView 是 Material Design 提供的一个标准底部导航栏的实现,可以轻松的实现导航栏菜单之间的切换与浏览.底部导航使用户更方便的查看和切换最高层级的 ...

  2. android tabhost 分割线,FragmentTabHost使用方法详解

    FragmentTabHost使用方法详解 发布时间:2020-08-19 23:32:55 来源:脚本之家 阅读:171 作者:mChenys FragmentTabHost是support-v包下 ...

  3. Switch控件详解

    Switch控件详解 原生效果 5.x 4.x 布局 <Switch android:id="@+id/setting_switch"android:layout_width ...

  4. Android 图片着色 Tint 详解2—xml设置、selector

    上篇文章介绍了给drawable设置tint 以达到颜色变化效果,这篇介绍下通过xml设置tint.代码设置tint以及如何设置tint selector. 在xml中设置tint 可以利用 andr ...

  5. 从命令行到IDE,版本管理工具Git详解(远程仓库创建+命令行讲解+IDEA集成使用)

    首先,Git已经并不只是GitHub,而是所有基于Git的平台,只要在你的电脑上面下载了Git,你就可以通过Git去管理"基于Git的平台"上的代码,常用的平台有GitHub.Gi ...

  6. JVM年轻代,老年代,永久代详解​​​​​​​

    秉承不重复造轮子的原则,查看印象笔记分享连接↓↓↓↓ 传送门:JVM年轻代,老年代,永久代详解 速读摘要 最近被问到了这个问题,解释的不是很清晰,有一些概念略微模糊,在此进行整理和记录,分享给大家.在 ...

  7. docker常用命令详解

    docker常用命令详解 本文只记录docker命令在大部分情境下的使用,如果想了解每一个选项的细节,请参考官方文档,这里只作为自己以后的备忘记录下来. 根据自己的理解,总的来说分为以下几种: Doc ...

  8. 通俗易懂word2vec详解词嵌入-深度学习

    https://blog.csdn.net/just_so_so_fnc/article/details/103304995 skip-gram 原理没看完 https://blog.csdn.net ...

  9. 深度学习优化函数详解(5)-- Nesterov accelerated gradient (NAG) 优化算法

    深度学习优化函数详解系列目录 深度学习优化函数详解(0)– 线性回归问题 深度学习优化函数详解(1)– Gradient Descent 梯度下降法 深度学习优化函数详解(2)– SGD 随机梯度下降 ...

最新文章

  1. docker安装在服务器的那个位置,docker容器卷通常会放在什么位置
  2. 如何linux中文改为英文,CentOS系统如何将中文语言改成英文
  3. cmd窗口设置charset
  4. Oracle数据库备份报错12514,Oracle数据库备份导出时,出错:ORA-12514
  5. ORA-12519: TNS:no appropriate service handler found 解决
  6. java gb13000 ucs2_采用GB 13000的UCS-2进行存储的文件怎么转换
  7. 深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)
  8. Nginx 状态监控、缓存的两种机制(学习笔记十四)
  9. 苹果推送iOS12.2系统更新:电信用户一定要升级!
  10. websocket server client 编写
  11. python 数据 模块 好用_Python最被低估的库,用好了效率提升10倍!
  12. 用Python在地图上模拟疫情扩散
  13. 捋一捋Kafka中的消费者API
  14. 微信小程序开发2.框架-视图层-WXS
  15. 四、《云原生 | Kubernetes篇》二进制安装部署k8s高可用集群V1.24
  16. python高级教程-2
  17. php排行榜系统,cms排行_PHP CMS系统排行榜
  18. idea开发SSM框架的高校大学学生社团管理网站bootstrap自适应响应式前端(javaweb-php-asp.netC#-j2ee)包含公告管理-社团活动管理-社团申请管理-社团审核-活动报名
  19. js jquery 计算两个时间差
  20. 什么是入耳式监听器,谁应该使用它们?

热门文章

  1. (王道408考研数据结构)第二章线性表-第二节2:顺序表的操作
  2. (王道408考研数据结构)第八章排序-第一节:排序综述
  3. 在Linux下使用iconv转换字符串编码
  4. 54. 二叉搜索树的第k大节点
  5. 应用程序无法正常启动(0xc000007b)错误的解决
  6. docker镜像没有ifconfig、ping指令
  7. QT5_chart_常见几种图形
  8. Linux系统编程:fifo有名管道的使用
  9. Wix 安装部署(二)自定义安装界面和行为
  10. bootstrap datetimepicker、bootstrap datepicker日期组件对范围的简单封装