原文出自 http://blog.csdn.net/zhaizu/article/details/51038113,转载请注明。

安卓自带文本控件 TextView 有七个比较恶心人的地方:

  1. 默认情况下,点击 ClickableSpan 的文本时会同时触发绑定在 TextView 的监听事件;
  2. 默认情况下,点击 ClickableSpan 的文本之外的文本时,TextView 会消费该事件,而不会传递给父 View;
  3. 固定 TextView 行数的时候,点击 ClickableSpan 文本会出现滚动现象;
  4. 在 8.0 系统上 Html.from() + ClickableSpan + TextUtils.concat() 点击事件表现异常;
  5. Spannable 与 Ellispsize 同时使用时,Ellipsize 失效;
  6. TextView 性能较低;
  7. 折行策略不尽人意,在小米 2s 上尤其明显;
  8. 诡异的 ClickableSpan 表现,在 TextView 上绑定点击事件,又在某段子串上绑定 ClickableSpan,根据 ClickableSpan#onClick() 内部的代码不同,会只触发 ClickableSpan 或既触发 ClickableSpan 又触发 TextView 绑定的事件;
  9. 明明设置2行,当前3行都是 ImageSpan 实现的 emoji 时,第3行的顶部会被绘制出来;
  10. 关于 Drawable 尺寸,无法在xml文件中设置其尺寸,必须在 Java 文件中设置,解决方案见自定义drawable大小可控的textview
  11. 关于 Drawable 位置,如果 TextView 宽度大于文本宽度,Drawable 会顶头展示,而非与文本紧紧粘连在一起展示,解决方案为重写 onDraw() 方法,具体见下文第6小结。

本文探究出现前三个坑的原因,以及第二和第三个坑的解决方法。

0. 关于 ClickableSpan

使用 ClickableSpan 富文本实现在同一个 TextView 中的文本的颜色、大小、背景色等属性的多样化和个性化。如下图红色框内是一个 TextView(也可能是多个 TextView),但是却有两种不同的颜色,这种效果就可以用 Spannale 实现:

Spannable richText = new Spannale("<font color=#E3E5F3>Alan海波</font>回复<font color=#E3E5F3>大赞</font>:你走开···");
textView.setText(richText);

如果不仅颜色不同,还要对某些文字添加响应事件(如跳转链接等),可以使用如下方式:

String username = "Alan 海波";
String content = "你走开……";
SpannableString spannableString = new SpannableString(username);
ClickableSpan span = new ClickableSpan() {@Overridepublic void onClick(View widget) {// do sth.    }@Overridepublic void updateDrawState(TextPaint ds) {ds.setColor(getResources().getColor(R.color.link_color));ds.setUnderlineText(false);}
};
spannableString.setSpan(span, 0, username.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
Spanned replyText = Html.fromHtml("<font color=" + getColor(R.color.deep_gray) + ">回复</font>");
Spanned colon = Html.fromHtml("<font color=" +getColor(R.color.link_color) + ">:</font>");
Spanned body = Html.fromHtml("<font color=" + getColor(R.color.text_color) + ">" + content + "</font>");
Spanned richText = (Spanned) android.text.TextUtils.concat(spannableString, replyText, spannableString, colon, body);
textView.setText(richText);
tv.setMovementMethod(LinkMovementMethod.getInstance());

注意:

  • Html.fromHtml(string) 中如果 string 过大,会抛出 java.io.IOException: Push back buffer is full,具体见 Code example for SpannedString(不靠谱的解决方法);
  • Html.fromHtml(string) 会将 string 中的 ‘\r’ 和 ‘\n’ 替换成空格,需要显式的将 ‘\r’(早期 Mac 系统)和 ‘\n’ (Unix 和 Max OS X)和 ‘\r\n’(Windows) 替换成 html 识别的 ‘< br>’,不替换的话,如果 string 中出现 “xx&xx\r” 形式的子串,会发生 IOException:
Html.fromHtml(string.replace("\r\n", "< br>").replace("\n", "< br>")).replace("\r", "< br>"));
  • 用 ClickableSpan 给 TextView 中的文本设置响应事件 a,再对 TextView 设置响应事件 b,在某些机型上点击文本时会同时触发 a 和 b;
  • 安卓原生仅仅支持以下 HTML tag 标签:(具体见 http://commonsware.com/blog/Android/2010/05/26/html-tags-supported-by-textview.html):
<a href="...">
<b>
<big>
<blockquote>
<br>
<cite>
<dfn>
<div align="...">
<em>
<font size="..." color="..." face="...">
<h1>
<h2>
<h3>
<h4>
<h5>
<h6>
<i>
<img src="...">
<p>
<small>
<strike>
<strong>
<sub>
<sup>
<tt>
<u>

有支持所有的 HTML tag 标签的库,具体见 https://github.com/NightWhistler/HtmlSpanner;

1. 从一只 bug 说起

微信版本: 6.3.8,复现机型:Genymotion HTC Evo(4.1.1 系统)或红米1W真机(MIUI-JHBCNBL30.0, Android 4.2.2),其他机型暂时没测。
正常逻辑:朋友圈里的一条评论,点击评论人或被回复人的昵称可以进入该用户主页(监听事件 A),点击评论内容的其他地方则弹出输入框和软键盘(监听事件 B)。
Bug 是:点击昵称,既弹出了输入框和软键盘,又进入了用户主页。即,点击昵称时 A 和 B 事件同时被触发了,显然这是不合理的。具体复现过程如下图:

2. Bug 原因分析

我们找出 4.1.1 系统的源码,通过 TextView 事件分发相关的代码我们可以看到:

       @Overridepublic boolean onTouchEvent(MotionEvent event) {final int action = event.getActionMasked();if (mEditor != null) mEditor.onTouchEvent(event);final boolean superResult = super.onTouchEvent(event);/** Don't handle the release after a long press, because it will* move the selection away from whatever the menu action was* trying to affect.*/if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {mEditor.mDiscardNextActionUp = false;return superResult;}final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&(mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()&& mText instanceof Spannable && mLayout != null) {boolean handled = false;if (mMovement != null) {handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);}final boolean textIsSelectable = isTextSelectable();if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {// The LinkMovementMethod which should handle taps on links has not been installed// on non editable text that support text selection.// We reproduce its behavior here to open links for these.ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),getSelectionEnd(), ClickableSpan.class);if (links.length > 0) {links[0].onClick(this);handled = true;}}// more code

当我们点击昵称时,进入 TextView.onTouchEvent()方法,先执行到第 7 行,调用了 super.onTouchEvent(event),触发了绑定在 TextView 上的弹出输入框和软键盘的事件,此时运行并没有结束,而是继续往下执行了第 39 行,调用了 links[0].onClick(this),触发了昵称上的 ClickableSpan 事件,即进入个人主页。故而,两个监听事件都被执行了。

还有一点需要注意。当你在事件 A 和 B 中打印日志时,你会发现 B 事件的日志总是出现在 A 的前面,恰好与代码的执行顺序相反。至于原因,还请高手赐教。

3. 高版本系统上没有该 Bug

虽然如此,但是同样的微信版本,在 4.4 或者 6.0 的机器上就没有此 bug。

究其原因,在 runnable 执行的时候,此时页面已经发生了跳转,由于某种神秘原因,被 post 出去的 Runnable 消息即performClick() 没有执行。

而如果将页面跳转的动作改成打印日志或设置 TextView 文本等操作,则二者都会执行,即 performClick() 正常执行。

如下代码,给 ClickableSpan 设定的点击事件是设置 tv1 的文本为 “clickablespan is clicked“,给 tv2 设置的监听事件是设置 tv2 的文本为 ”textview’s listener is triggered“,从 gif 图的执行结果可以看出,两个事件都被执行了。

WiredClickableSpan.java

public class WiredClickableSpan extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_wired_layout);final TextView tv1 = (TextView) findViewById(R.id.tv1);final TextView tv2 = (TextView) findViewById(R.id.tv2);final TextView content = (TextView) findViewById(R.id.comment_item_detail_content);String text1 = "我是和常常大声点发大水发送到发送到发";SpannableString str1 = new SpannableString(text1);ClickableSpan cs1 = new ClickableSpan() {@Overridepublic void onClick(View widget) {//                Intent intent = new Intent(WiredClickableSpan.this, DemoClickableSpan.class);
//                startActivity(intent);tv1.setText("clickablespan is clicked");}};str1.setSpan(cs1, 3, 9, Spannable.SPAN_INCLUSIVE_INCLUSIVE);content.setText(str1);content.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {tv2.setText("textview's listener is triggered");}});content.setMovementMethod(LinkMovementMethod.getInstance());}

activity_wired_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/ll"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:orientation="vertical"><TextViewandroid:id="@+id/comment_item_detail_content"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="13sp" /><TextViewandroid:id="@+id/tv1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="13sp" /><TextViewandroid:id="@+id/tv2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="13sp" /></LinearLayout>

执行结果

4. 另一个问题

仔细查看 TextView.onTouchEvent(MotionEvent action)LinkMovementMethod.onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) 的相关代码,你会发现:如果给一个 TextView 的文本的某些字符设置 ClickableSpan,点击 ClickableSpan 区域之外的文本时,TextView 将消费该事件,而不会将其传递给父 View。那么,在这种情况下,如何才能让事件传递给父 View 呢?

这篇博文给出了一个思路:《仿微信朋友圈,点击评论,生成自定义超链接,并处理》。下面我们将采用另外一种方法解决这个问题。

我们假设一条评论包括头像、昵称、时间、评论内容,由于点击评论的空白部分、时间(无 ClickableSpan 的 TextView)、评论内容(带有 ClickableSpan 的 TextView),都会触发相同的效果:(1)评论背景变色;(2)弹出回复框,所以我们假定变色的 selector 和弹出回复框的事件都绑定在父 View 即 LinearLayout 上,而且回复内容的 TextView 默认会消费事件。

那么问题来了,当我点击回复内容的 ClickableSpan 区域之外的内容时,回复内容所在的 TextView 默认会消费该事件,不会传递给父 View,从而不会产生背景变色和弹出回复框。

这显然是矛盾的。

假设有误,如果事件绑定在父 View 上没有问题,那么问题就出在 TextView 会消费事件。

要想实现该效果,点击 ClickableSpan 区域之外的 TextView 不能消费,要把事件传递给父 View 去处理。

在给出解决方案之前,我们先来探究一下点击 ClickableSpan 区域之外的文本时 TextView 会消费事件原因。

来看 LinkMovementMethod.onTouchEvent(TextView widget, Spnnable buffer, MotionEvent event) 的代码:

    @Overridepublic boolean onTouchEvent(TextView widget, Spannable buffer,MotionEvent event) {int action = event.getAction();if (action == MotionEvent.ACTION_UP ||action == MotionEvent.ACTION_DOWN) {int x = (int) event.getX();int y = (int) event.getY();x -= widget.getTotalPaddingLeft();y -= widget.getTotalPaddingTop();x += widget.getScrollX();y += widget.getScrollY();Layout layout = widget.getLayout();int line = layout.getLineForVertical(y);int off = layout.getOffsetForHorizontal(line, x);ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);if (link.length != 0) {if (action == MotionEvent.ACTION_UP) {link[0].onClick(widget);} else if (action == MotionEvent.ACTION_DOWN) {Selection.setSelection(buffer,buffer.getSpanStart(link[0]),buffer.getSpanEnd(link[0]));}return true;} else {Selection.removeSelection(buffer);}}return super.onTouchEvent(widget, buffer, event);}

点击 ClickableSpan 的文本时,执行第 25 行代码 link[0].onClick(widget) ,ClickableSpan 的事件执行,然后第 32 行返回 true,这是正确的过程;

当点击 ClickableSpan 文本之外的文本时,执行最后一行 return super.onTouchEvent(widget, buffer, event) ,单步 debug 你会发现 super.onTouchEvent(widget, buffer, event) = true,最终这个 true 被 TextView.onTouchEvent() 返回给父View,即告诉父 View:这个事件我消费了,你别管了。

这就是本质原因,虽然我们不知道为什么这么设计,但是这个设计显然不符合我们的要求。

既然问题就出在最后一行代码上,改下就好了。

将最后一行代码改为:

return false;

即可。

但是,改完之后发现 TextView 还是会消费事件。究其原因,是因为我们在调用 TextView.setMovementMethod() 的时候源码调用了 fixFocusableAndClickableSettigns() 方法:

public final void setMovementMethod(MovementMethod movement) {if (mMovement != movement) {mMovement = movement;if (movement != null && !(mText instanceof Spannable)) {setText(mText);}fixFocusableAndClickableSettings();// SelectionModifierCursorController depends on textCanBeSelected, which depends on// mMovementif (mEditor != null) mEditor.prepareCursorControllers();}}private void fixFocusableAndClickableSettings() {if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {setFocusable(true);setClickable(true);setLongClickable(true);} else {setFocusable(false);setClickable(false);setLongClickable(false);}}

fixFocusableAndClickableSettings() 方法中,会执行:

setFocusable(true);
setClickable(true);
setLongClickable(true);

根据我的另一篇博客《浅尝安卓事件分发机制》,我们知道执行这三行代码之后,TextView 仍然会消费事件,于是我们得显式的执行:

setFocusable(false);
setClickable(false);
setLongClickable(false);

综上,完整的代码如下:

class ClickableMovementMethod extends BaseMovementMethod {private static ClickableMovementMethod sInstance;public static ClickableMovementMethod getInstance() {if (sInstance == null) {sInstance = new ClickableMovementMethod();}return sInstance;}@Overridepublic boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {int action = event.getActionMasked();if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {int x = (int) event.getX();int y = (int) event.getY();x -= widget.getTotalPaddingLeft();y -= widget.getTotalPaddingTop();x += widget.getScrollX();y += widget.getScrollY();Layout layout = widget.getLayout();int line = layout.getLineForVertical(y);int off = layout.getOffsetForHorizontal(line, x);ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);if (link.length > 0) {if (action == MotionEvent.ACTION_UP) {link[0].onClick(widget);} else {Selection.setSelection(buffer, buffer.getSpanStart(link[0]),buffer.getSpanEnd(link[0]));}return true;} else {Selection.removeSelection(buffer);}}return false;}@Overridepublic void initialize(TextView widget, Spannable text) {Selection.removeSelection(text);}
}// more code
TextView content = new TextView(getContext());
// more code
content.setMovementMethod(ClickableMovementMethod.getInstance());
content.setFocusable(false);
content.setClickable(false);
content.setLongClickable(false);

这样修改会不会有副作用?

我们知道,安卓应用层的事件分发是比较复杂的,轻易不要改写,而且由于 EditText 继承 TextView,所以 EditText 的长按选择文本等操作都跟 LinkMovementMethod 有关。

抛开上述考虑,这种改写是没问题的,即第2个坑的解决方法仅限于该场景使用,不保证这种改写在其他场景下能够正确。

5. 滚动的问题

对于滚动的问题,解决方法见 Can I disable the scrolling in TextView when using LinkMovementMethod?,集成到 ClickableMovementMethod :

class ClickableMovementMethod extends BaseMovementMethod implements View.OnTouchListener {private static ClickableMovementMethod sInstance;public static ClickableMovementMethod getInstance() {if (sInstance == null) {sInstance = new ClickableMovementMethod();}return sInstance;}@Overridepublic boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {int action = event.getActionMasked();if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {int x = (int) event.getX();int y = (int) event.getY();x -= widget.getTotalPaddingLeft();y -= widget.getTotalPaddingTop();x += widget.getScrollX();y += widget.getScrollY();Layout layout = widget.getLayout();int line = layout.getLineForVertical(y);int off = layout.getOffsetForHorizontal(line, x);ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);if (link.length > 0) {if (action == MotionEvent.ACTION_UP) {link[0].onClick(widget);} else {Selection.setSelection(buffer, buffer.getSpanStart(link[0]),buffer.getSpanEnd(link[0]));}return true;} else {Selection.removeSelection(buffer);}}return false;}@Overridepublic boolean onTouch(View v, MotionEvent event) {TextView widget = (TextView) v;Object text = widget.getText();if (text instanceof Spanned) {Spanned buffer = (Spanned) text;int action = event.getAction();if (action == MotionEvent.ACTION_UP|| action == MotionEvent.ACTION_DOWN) {int x = (int) event.getX();int y = (int) event.getY();x -= widget.getTotalPaddingLeft();y -= widget.getTotalPaddingTop();x += widget.getScrollX();y += widget.getScrollY();Layout layout = widget.getLayout();int line = layout.getLineForVertical(y);int off = layout.getOffsetForHorizontal(line, x);ClickableSpan[] link = buffer.getSpans(off, off,ClickableSpan.class);if (link.length != 0) {if (action == MotionEvent.ACTION_UP) {link[0].onClick(widget);} else if (action == MotionEvent.ACTION_DOWN) {// Selection only works on Spannable text. In our case setSelection doesn't work on spanned text
//                        Selection.setSelection((Spannable) buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]));}return true;}}}return false;}@Overridepublic void initialize(TextView widget, Spannable text) {Selection.removeSelection(text);}
}// more code
TextView content = new TextView(getContext());
// more code
content.setMovementMethod(ClickableMovementMethod.getInstance());
content.setOnTouchListener(ClickableMovementMethod.getInstance());
content.setFocusable(false);
content.setClickable(false);
content.setLongClickable(false);

6. Drawable 位置

public class StickyDrawableTextView extends TextView {public StickyDrawableTextView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public StickyDrawableTextView(Context context, AttributeSet attrs) {super(context, attrs);}public StickyDrawableTextView(Context context) {super(context);}@Overrideprotected void onDraw(Canvas canvas) {Drawable[] drawables = getCompoundDrawables();if (drawables != null) {Drawable drawableLeft = drawables[0];Drawable drawableRight = drawables[2];float textWidth = getPaint().measureText(getText().toString());int drawablePadding = getCompoundDrawablePadding();if (drawableLeft != null) {Rect bounds = drawableLeft.getBounds();int drawableWidth = bounds.width();float bodyWidth = textWidth + drawableWidth + drawablePadding;float offsetX = Math.max(0, (getWidth() - bodyWidth) / 2 - getPaddingLeft());canvas.translate(offsetX, 0);} else if (drawableRight != null) {Rect bounds = drawableRight.getBounds();int drawableWidth = bounds.width();float bodyWidth = textWidth + drawableWidth + drawablePadding;float offsetX = Math.min(0, (bodyWidth - getWidth()) / 2 + getPaddingRight());canvas.translate(offsetX, 0);}}super.onDraw(canvas);}
}

7. 更多好文

  • TextView 高级教程
  • 安卓中计算 TextView 的宽高、打点
  • android ClickableSpan intercepts the click event
  • 仿微信朋友圈,点击评论,生成自定义超链接,并处理
  • 【对比Java学Kotlin】协程简史

原文出自 http://blog.csdn.net/zhaizu/article/details/51038113,转载请注明。

安卓 TextView 七宗罪相关推荐

  1. 好产品要满足人性七宗罪

    好产品要满足人性"七宗罪": 作为一个有20年实战经验的互联网老兵,周鸿祎称得上是一个优秀的产品经理,一手打造了众多国民级的产品. 每个人都有成为产品经理的潜质.但是," ...

  2. 好产品要满足人性七宗罪:

    作为一个有20年实战经验的互联网老兵,周鸿祎称得上是一个优秀的产品经理,一手打造了众多国民级的产品. 每个人都有成为产品经理的潜质.但是,"人人都可以是产品经理"的观点有局限性. ...

  3. 驳“AJAX 的七宗罪”

    (本文转载自"Java视线",原文地址:http://forum.javaeye.com/viewtopic.php?t=13844,作者dlee) (AJA X的七宗罪:http ...

  4. 驳AXAJ的七宗罪 (转)

    我不带任何主观色彩来评一下这个所谓的 "AJAX 的七宗罪". 1.连带着 Flash 和 Ajax 一块骂了. 引用:没有链接的web就像森林中迷路的羔羊,这句看似广告语,其实是 ...

  5. OpenStack进入第二阶段需要解决的“七宗罪”

    以下内容节选编译自分析人士Steve Chambers近期撰写了一份OpenStack市场研究报告,发表在Wikibon Premium网站.报告的名称为<OpenStack进入第二阶段> ...

  6. 产品经理学习---人性七宗罪:打造完美产品的金钥匙

    那些我们不愿承认的人性七宗罪,恰好是打造产品的最佳依据,正视人性之恶,打造产品之美. 但丁在<神曲>中称人有七宗罪:傲慢,嫉妒,暴怒,懒惰,贪婪,贪吃和色欲.个人认为七宗罪其实是人的本能, ...

  7. CSDN Blog 之七宗罪

    CSDN Blog 之七宗罪 第一,不稳定,就为这个,我曾一度离开这里,不过终究还是有点怀念我写的那点儿东西.目前的稳定性比以前有所改善. 第二,我习惯用firefox,csdn的blog大体上还算兼 ...

  8. 让你此生难成大器的七宗罪

    是,你没看错......说的就是你--说的就是你呢! 我此刻死盯着你的眼睛(好吧,也不是那么名副其实的"盯着",因为你看到的是这篇文章不是我,但请想象,我现在正在死盯着你,我的镭射 ...

  9. 电信资费七宗罪,终端的故事

    电信资费七宗罪,终端的故事 文 | 宁宇 你的手机是自己买的,还是运营商连合约一起卖给你的?运营商让你以优惠的价格甚至零元拿到了终端,运营商获得了什么利益?为何如今客户对合约机从爱走向恨?这条走了十几 ...

最新文章

  1. Oracle总结第二篇【视图、索引、事务、用户权限、批量操作】
  2. The genome polishing tool POLCA makes fast and accurate corrections in genome assemblies
  3. 基于 TensorFlow 在手机端实现文档检测
  4. C语言数据段分类,13.2.1 段的分类
  5. ★宣传广告变成社会标准
  6. E1. Rubik‘s Cube Coloring (easy version) 贪心,满二叉树(1300)
  7. js调用浏览器的下载框
  8. hive与spark的匹配版本汇总
  9. 生成目录树CMD命令(bat文件)
  10. saltstack(三) --- salt-httpapi
  11. HTML CSS样式表布局
  12. Java学习----方法的覆盖
  13. 【二分图匹配】E. 过山车
  14. 离散傅里叶变换MATLAB实现
  15. 《Java核心技术 卷1 基础知识 原书第10版》
  16. 用于敏捷开发的免费 UML 工具 2022
  17. 计算机说课稿模板小学数学,【小学数学说课稿范文模板+范文(精编20篇)】 说课稿模板小学数学...
  18. Python Matplotlib add_subplot 和 subplots_adjust详解及代码详细说明 配图片说明
  19. Microsoft Store微软应用商店打不开怎么办?完美解决方案!
  20. 小猿日记 - 程序猿的日常日记(3)

热门文章

  1. 西建大历年电子与通信工程复试真题_电子与通信工程学院考研复试经验
  2. 服务器进不了浏览器不支持,省考报名系统打开显示服务器进不去,该如何解决?...
  3. Base64编码/解码VB6超精简版(适用于中、英文)
  4. linux 下载文件放哪里,请问是用Yum install命令安装时下载的文件包放在哪里了?...
  5. POI pdf ppt word excel
  6. linux打开网络摄像头失败,Opencv没有检测到linux上的firewire网络摄像头
  7. idea配置Tomcat(8以上的版本)之后控制台出现乱码问题
  8. 批处理脚本之批量打开常用软件
  9. 摘自【北京迅为】itop-3568开发板快速启动手册 第二章 Windows安装串口终端
  10. msi True Color在系统升级后失效的解决方案