-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcustom-image-span.html
More file actions
339 lines (243 loc) · 16.3 KB
/
custom-image-span.html
File metadata and controls
339 lines (243 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
<!DOCTYPE html>
<html>
<head>
<!-- [[! Document Settings ]] -->
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- [[! Page Meta ]] -->
<title>ImageSpan的定制使用</title>
<meta name="description" content="与机器,人,神共舞 - 编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景" />
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="/assets/images/favicon.ico" >
<!-- [[! Styles'n'Scripts ]] -->
<link rel="stylesheet" type="text/css" href="/assets/css/screen.css" />
<link rel="stylesheet" type="text/css"
href="//fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400" />
<link rel="stylesheet" type="text/css" href="/assets/css/syntax.css" />
<!-- [[! Ghost outputs important style and meta data with this tag ]] -->
<link rel="canonical" href="/" />
<meta name="referrer" content="origin" />
<link rel="next" href="/page2/" />
<meta property="og:site_name" content="与机器,人,神共舞" />
<meta property="og:type" content="website" />
<meta property="og:title" content="与机器,人,神共舞" />
<meta property="og:description" content="编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景" />
<meta property="og:url" content="/" />
<meta property="og:image" content="/assets/images/cover1.jpg" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="与机器,人,神共舞" />
<meta name="twitter:description" content="编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景" />
<meta name="twitter:url" content="/" />
<meta name="twitter:image:src" content="/assets/images/cover1.jpg" />
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Website",
"publisher": "Tao's Page",
"url": "/",
"image": "/assets/images/cover1.jpg",
"description": "编程,读书,思考,旅行,与机器对话,与人交谈,对神发问,探索,体验人生美丽的风景"
}
</script>
<meta name="generator" content="Jekyll 3.0.0" />
<link rel="alternate" type="application/rss+xml" title="与机器,人,神共舞" href="/rss.xml" />
</head>
<body class="home-template nav-closed">
<div class="nav">
<h3 class="nav-title">Menu</h3>
<a href="#" class="nav-close">
<span class="hidden">Close</span>
</a>
<ul>
<li class="nav-home " role="presentation"><a href="/">Home</a></li>
<li class="nav-about " role="presentation"><a href="/about.html">About</a></li>
<li class="nav-fables " role="presentation"><a href="/tag/machine/">Machine</a></li>
<li class="nav-speeches " role="presentation"><a href="/tag/human/">Human</a></li>
<li class="nav-fiction " role="presentation"><a href="/tag/god/">God</a></li>
<li class="nav-author " role="presentation"><a href="/author/hetao/">Author</a></li>
</ul>
<a class="subscribe-button icon-feed" href="/vocab.html">Apps</a>
</div>
<span class="nav-cover"></span>
<div class="site-wrapper">
<!-- [[! Everything else gets inserted here ]] -->
<!-- default -->
<!-- The comment above "< default" means - insert everything in this file into -->
<!-- the [body] of the default.hbs template, which contains our header/footer. -->
<!-- Everything inside the #post tags pulls data from the post -->
<!-- #post -->
<header class="main-header post-head no-cover">
<nav class="main-nav clearfix">
</nav>
</header>
<main class="content" role="main">
<article class="post tag-machine">
<header class="post-header">
<h1 class="post-title">ImageSpan的定制使用</h1>
<section class="post-meta">
<!-- <a href='/'>Tao He</a> -->
<time class="post-date" datetime="2019-10-19">19 Oct 2019</time>
<!-- [[tags prefix=" on "]] -->
on
<a href='/tag/machine'>Machine</a>
</section>
</header>
<section class="post-content">
<p>最近在做一个具体业务需求时,为了实现UI设计的要求,需要实现在同一段文字中,有一段文字是需要有形状的背景色,有一段文字中还有网络图片的插入,如果要达到这两个要求,简单的使用SpannableStringBuilder肯定是达不到要求的,虽然SpannableStringBuilder可以设置背景色,但是并不能绘制形状,说白了就是只能设置颜色,不能设置文字的背景图片。想要在文字中插入图片,可以直接使用ImageSpan,但是ImageSpan并不能加载网络图片。因此这种简单的使用方式都是行不通的,需要考虑去定制ImageSpan实现我们特殊的要求。</p>
<ul>
<li>实现给同一段文字的一部分文字区域设置背景图片</li>
</ul>
<p>这个功能的实现还是要继承ImageSpan,将我们的背景图片传进去,并且override ImageSpan的draw方法,根据对文字区域的测量,分别绘制出背景和文字。实现代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// BgImageSpan
public class BgImageSpan extends ImageSpan {
private int textSize = 20;
private int color = Color.GRAY;
private TextView mTextView;
static float textboundhight;
static float textY;
String mText;
public BgImageSpan(Drawable d, TextView tv, String text) {
super(d);
mTextView = tv;
mText = text;
textSize = (int) mTextView.getTextSize();
}
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
int bottom, Paint paint) {
String str = mText;
Rect bounds = new Rect();
paint.setTextSize(textSize);
paint.getTextBounds(str, 0, str.length(), bounds);
int textHeight = bounds.height();
int textWidth = bounds.width();
getDrawable().setBounds(0, top, (int)(bounds.width() * 1.3) , bottom);
super.draw(canvas, str, start, end, x, top, y, bottom, paint);
paint.setColor(mTextView.getTextColors().getDefaultColor());
paint.setTypeface(Typeface.create("normal", Typeface.NORMAL));
Rect bounds1 = getDrawable().getBounds();
float textX = x + bounds1.width() / 2 - bounds.width() / 2;
if (textboundhight == 0) {
textboundhight = bounds.height();
textY = (bounds1.height()) / 2 + textboundhight / 2;
}
canvas.drawText(str, textX, textY, paint);
}
}
</code></pre></div></div>
<p>使用方法和ImageSpan并没有什么区别:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
String username = "用户名:";
String message = "哈哈,我是一个天才";
SpannableStringBuilder ssb = new SpannableStringBuilder(username);
ssb.append(message);
Rect bounds = new Rect();
Paint paint = mContent.getPaint();
paint.getTextBounds(username, 0, username.length(), bounds);
Drawable bgDrawable = getDrawable(R.drawable.round_rect);
bgDrawable.setBounds(0, 0, (int)(bounds.width() * 1.3), bounds.height());
ImageSpan nameBgSpan = new BgImageSpan(bgDrawable, mContent, username);
ssb.setSpan(nameBgSpan, 0, username.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ForegroundColorSpan contentColorSpan = new ForegroundColorSpan(Color.parseColor("#ffc800"));
ssb.setSpan(contentColorSpan, username.length(), username.length() + message.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mContent.setText(ssb);
mContent.postInvalidate();
</code></pre></div></div>
<p>使用代码需要注意一下几点:</p>
<ol>
<li>传给BgImageSpan的Drawable需要设置Bounds,然后再传进去,否则可能会出现字符重叠的问题。</li>
<li>如果设置了Bounds还有字符重叠错乱的问题,可以调用TextView的postInvalidate重绘。</li>
</ol>
<ul>
<li>第二种实现同一段文字的一部分文字区域设置背景图片</li>
</ul>
<p>这种实现的思路很简单,就是通过inflate一个单独的布局,然后用这个inflate好的view生成图片,然后传给一个ImageSpan,即可完成,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>View view = LayoutInflater.from(this).inflate(R.layout.container, null);
TextView textView = view.findViewById(R.id.tv_value);
textView.setText(username);
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
ImageSpan nameBgSpan = new ImageSpan(this, bitmap);
ssb.setSpan(nameBgSpan, 0, username.length(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
</code></pre></div></div>
<ul>
<li>实现一段文字中插入网络图片</li>
</ul>
<p>插入网络图片和插入本地图片其实没有本质区别,要插入网络图片,就得先获取到网络图片后再设置到ImageSpan中去。代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String url = "http://img2.imgtn.bdimg.com/it/u=1467875646,1039972052&fm=26&gp=0.jpg";
RequestOptions options = new RequestOptions()
.dontAnimate()
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(url)
.apply(options)
.into(new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
if (resource instanceof BitmapDrawable) {
resource.setBounds(0, 0, 50, 50);
ImageSpan iconSpan = new ImageSpan(resource);
ssb.setSpan(iconSpan, username.length(), username.length() + 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mContent.setText(ssb);
mContent.postInvalidate();
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
</code></pre></div></div>
<p>使用起来需要注意的问题和上面的BgImageSpan相同,也是先要设置Drawable的Bounds。</p>
<p>虽然这两个实现看起来也没有什么难度,但是还是需要把这种解决过的,不那么常规的方法记录下来,以后遇到同样的问题可节省很多调研和调试的事件。</p>
</section>
<footer class="post-footer">
<!-- Everything inside the #author tags pulls data from the author -->
<!-- #author-->
<!-- Add Disqus Comments -->
</footer>
</article>
</main>
<aside class="read-next">
<!-- [[! next_post ]] -->
<a class="read-next-story no-cover" href="/most-simple-leakcanary-principle">
<section class="post">
<h2>最简LeakCanary原理解析</h2>
<p>LeakCanary现在几乎成了Android开发过程中的一个标配,使用起来非常简单,能够帮助开发者发现和规避大部分的内存泄露问题。虽然大部分的开发者都或多或少,或深或浅的接触过LeakCanary,说起来是比较熟悉的一个第三方框架了,但是谈到LeakCanary的原理可能就会陌生很多。虽然网上也有非常多的讲解LeakCanary的文章,但是很多博文要么陷入无止境的代码细节中,要么就泛泛而谈,都不能很清楚的将LeakCanary的原理讲清楚。这篇文章尽力规避前两种文章的问题,用最小的篇幅把LeakCanary的原理讲清楚。 #### LeakCanary到底是怎么工作的? LeakCanary能够准确,及时地检测到内存泄露,有以下几个关键点 * 检测保留的实例 LeakCanary能够工作的基石是一个叫做ObjectWatcher的库,它hook了Android中Activity和Fragment的生命周期,能够自动检测到Activity和Fragment的销毁和将要被GC,这些被检测到的Activity和Fragment的实例被传给了`ObjectWatcher`,`ObjectWatcher`以WeakReference持有他们。如果这些WeakReference在5秒后或者一次GC周期以后还没有被清理,那么LeakCanary认为这些实例被保留了,没有被回收,泄露发生了。检测没有被回收的实例是LeakCanary能够工作起来的基石,也是后续处理的基础,这一点非常重要。 * Dump 堆 这一步需要对检测到的泄露进行处理,当然也不是检测到一个实例就会触发dump,而是有一个阈值,当达到一定数量实例的泄露后就会触发LeakCanary将Java堆内存dump到`.hprof`文件中去,当然了,这个文件存储在Android文件系统中。这个触发dump的阈值是如何确定的呢?如果App还可见,那这个阈值默认是5,如果App不可见,阈值默认是1。 * 分析Java 堆 LeakCanary使用`Shark`来分析`.hprof`文件,找出阻止实例被回收的引用链:leak...</p>
</section>
</a>
<!-- [[! /next_post ]] -->
<!-- [[! prev_post ]] -->
<a class="read-next-story prev no-cover" href="/fix-video-view-leak">
<section class="post">
<h2>修复VideoView引起的内存泄露小计</h2>
<p>最近写了非常简单的新手引导视频页面,逻辑很简单,就是新手用户在第一次使用App时可以点击引导视频入口,然后进入一个视频播放页面,为了快速实现功能,就直接使用了VideoView,从需求开发到交付也都没什么问题,需求上线后我打开LeakCanary,想观察下最近有没有新增的内存泄露,竟然发现这个视频页面竟然泄露了。排查了一圈也没有发现有什么会阻止Activity销毁。但是LeakCanary打出了引用链,发现和VideoView有关,通过Google发现,竟然是VideoView自身的bug!这种情况也不是第一次遇见,那也得解决啊,所以开始想办法。 首先显明确是谁导致了Activity的销毁,通过查看VideoView的源码,发现罪魁祸首是AudioManager,它可能会长期持有Context(即泄露的Activity)。很明显是因为生命周期不一致导致的泄露,因此最先想到的就是在创建VideoView时不要传Activity的Context,传给它ApplicationContext。当然了,在布局中创建的VideoView传入的就是Activity的Context,所以需要用代码动态创建: mVideoView = new VideoView(getApplicationContext()); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); mContainer.addView(mVideoView,...</p>
</section>
</a>
<!-- [[! /prev_post ]] -->
</aside>
<!-- /post -->
<footer class="site-footer clearfix">
<section class="copyright"><a href="/">与机器,人,神共舞</a> © 2024</section>
</footer>
</div>
<!-- [[! Ghost outputs important scripts and data with this tag ]] -->
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<!-- [[! The main JavaScript file for Casper ]] -->
<script type="text/javascript" src="/assets/js/jquery.fitvids.js"></script>
<script type="text/javascript" src="/assets/js/index.js"></script>
<!-- Add Google Analytics -->
<!-- Google Analytics Tracking code -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-78960009-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>