-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1269 lines (457 loc) · 53.8 KB
/
index.html
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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html class="theme-next mist use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.8.0">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">
<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">
<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">
<link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">
<meta name="keywords" content="Hexo, NexT, Wakim, Blog">
<meta name="description" content="努力 奋斗">
<meta property="og:type" content="website">
<meta property="og:title" content="Wakim's泛泛之谈">
<meta property="og:url" content="http://yoursite.com/index.html">
<meta property="og:site_name" content="Wakim's泛泛之谈">
<meta property="og:description" content="努力 奋斗">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Wakim's泛泛之谈">
<meta name="twitter:description" content="努力 奋斗">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Mist',
version: '5.1.4',
sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
fancybox: true,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
duoshuo: {
userId: '0',
author: '博主'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="http://yoursite.com/">
<title>Wakim's泛泛之谈</title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container sidebar-position-left
page-home">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">Wakim's泛泛之谈</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle"></p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br>
首页
</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section">
<i class="menu-item-icon fa fa-fw fa-th"></i> <br>
分类
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
归档
</a>
</li>
</ul>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2019/04/09/自定义View的一种使用场景/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Wakim Sun">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Wakim's泛泛之谈">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/04/09/自定义View的一种使用场景/" itemprop="url">自定义View的一种使用场景</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2019-04-09T11:29:22+08:00">
2019-04-09
</time>
</span>
<span class="post-category">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Android/" itemprop="url" rel="index">
<span itemprop="name">Android</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>在我的SideProject中有这样一个场景,使用RecyclerView和CardView的组合实现一个展示列表,其中CardView有subItem,每个CardView的subItem个数可能不一样,subItem包括name和weight两个value,分别靠左、靠右对齐。效果如下:</p>
<p><img src="/2019/04/09/自定义View的一种使用场景/list.gif" alt="1"></p>
<p>用TextView结合String换行无法实现weight value靠右对齐。用CardView嵌套ListView又显得太重,还有性能问题。分析下来,更合理的做法是自定义一个View实现。</p>
<p>Android的自定义View控件可以分为三种:组合式、继承式、自绘式,这个场景适合用自绘式实现。分享下代码:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OptionsTextView</span> <span class="keyword">extends</span> <span class="title">View</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> TextPaint mContentPaint;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> List<RouletteModel.Item> mOptionList;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">float</span> mLineSpace;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> mSingleTextHeight;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Rect mBounds;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">float</span> mTextSize;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// spacing between name and weight</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">float</span> mSpacing;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">OptionsTextView</span><span class="params">(Context context)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(context,<span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">OptionsTextView</span><span class="params">(Context context, AttributeSet attrs)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>(context, attrs,<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">OptionsTextView</span><span class="params">(Context context, AttributeSet attrs, <span class="keyword">int</span> defStyleAttr)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(context, attrs, defStyleAttr);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 也可以自定义属性,在xml中传入</span></span><br><span class="line"> mLineSpace = getResources().getDimension(R.dimen.roulette_spacing_smaller);</span><br><span class="line"> mSpacing = getResources().getDimension(R.dimen.roulette_spacing_normal);</span><br><span class="line"> mTextSize = getResources().getDimension(R.dimen.roulette_font_normal);</span><br><span class="line"></span><br><span class="line"> mBounds = <span class="keyword">new</span> Rect();</span><br><span class="line"></span><br><span class="line"> mContentPaint = <span class="keyword">new</span> TextPaint();</span><br><span class="line"> mContentPaint.setTextSize(mTextSize);</span><br><span class="line"> mContentPaint.setAntiAlias(<span class="keyword">true</span>);</span><br><span class="line"> mContentPaint.setColor(context.getResources().getColor(R.color.roulette_black));</span><br><span class="line"> mContentPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 计算文字的高度</span></span><br><span class="line"> Paint.FontMetricsInt fm = mContentPaint.getFontMetricsInt();</span><br><span class="line"> mSingleTextHeight = Math.abs(fm.top - fm.bottom);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 这个自定义View作为RecyclerView的item,当复用时由于dataList的数据改变,高度可能有变化,</span></span><br><span class="line"><span class="comment"> * 所以需要调用requestLayout,而不是invalidate,requestLayout会请求布局,invalidate只会重绘</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addOptionList</span><span class="params">(List<RouletteModel.Item> dataList)</span> </span>{</span><br><span class="line"> mOptionList = dataList;</span><br><span class="line"></span><br><span class="line"> requestLayout();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onMeasure</span><span class="params">(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>.onMeasure(widthMeasureSpec,heightMeasureSpec);</span><br><span class="line"> setMeasuredDimension(getMeasuredWidth(), (<span class="keyword">int</span>)getViewHeight());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 根据OptionList计算View的高度</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">float</span> <span class="title">getViewHeight</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> mSingleTextHeight * mOptionList.size() + mLineSpace * (mOptionList.size() - <span class="number">1</span>) + getPaddingBottom() + getPaddingTop();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onDraw</span><span class="params">(Canvas canvas)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (mOptionList == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < mOptionList.size(); i++) {</span><br><span class="line"> <span class="comment">// 先画右边的权重文字区域</span></span><br><span class="line"> String weight = String.valueOf(mOptionList.get(i).weight);</span><br><span class="line"> mContentPaint.setTextSize(mTextSize);</span><br><span class="line"> mContentPaint.setTextAlign(Paint.Align.RIGHT);</span><br><span class="line"> canvas.drawText(weight, getMeasuredWidth() - getPaddingRight(), getPaddingTop() + mSingleTextHeight * (i + <span class="number">1</span>) + mLineSpace * i, mContentPaint);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 再画左边的选项文字区域</span></span><br><span class="line"> String name = mOptionList.get(i).name;</span><br><span class="line"> <span class="keyword">float</span> totalWidth = mContentPaint.measureText(mOptionList.get(i).name + weight) + mSpacing;</span><br><span class="line"> <span class="comment">// 如果超出总长度超出绘制区域宽度</span></span><br><span class="line"> <span class="keyword">if</span> (totalWidth >= getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) {</span><br><span class="line"> <span class="comment">// 算出选项文字区域可用的宽度,注意为了美观两者之间有个间距</span></span><br><span class="line"> mContentPaint.getTextBounds(weight, <span class="number">0</span>, weight.length(), mBounds);</span><br><span class="line"> <span class="keyword">float</span> desiredWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - mBounds.width() - mSpacing;</span><br><span class="line"></span><br><span class="line"> mContentPaint.getTextBounds(name, <span class="number">0</span>, name.length(), mBounds);</span><br><span class="line"> <span class="keyword">float</span> desiredTextSize = mTextSize * desiredWidth / mBounds.width();</span><br><span class="line"> mContentPaint.setTextSize(desiredTextSize);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> mContentPaint.setTextAlign(Paint.Align.LEFT);</span><br><span class="line"> canvas.drawText(name, getPaddingLeft(), getPaddingTop() + mSingleTextHeight * (i + <span class="number">1</span>) + mLineSpace * i, mContentPaint);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2019/04/08/MVVM模式及响应式编程实践-搜索功能/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Wakim Sun">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Wakim's泛泛之谈">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/04/08/MVVM模式及响应式编程实践-搜索功能/" itemprop="url">MVVM模式及响应式编程实践-搜索功能</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2019-04-08T18:17:52+08:00">
2019-04-08
</time>
</span>
<span class="post-category">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Android/" itemprop="url" rel="index">
<span itemprop="name">Android</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>软件开发过程中为了提高代码的可维护性、可扩展性、减少依赖耦合,往往会遵循某种编程模式进行代码的编写。目前Android应用开发中较流行的编程模式有MVC、MVP、MVVM三种。</p>
<ul>
<li>MVC模式会使得Activity/Fragment臃肿庞大,并且Controller和View耦合较深。</li>
<li>MVP模式虽然引入Presenter解耦了视图操作和业务逻辑,但是Presenter会持有View层的抽象接口,接口粒度不好把握,而且需要手动调用接口才能实现UI响应,还要关心View层对象是否被销毁。</li>
<li>MVVM模式引入了ViewModel的概念用来处理对应的View层的业务逻辑,并借助DataBinding技术实现视图和数据的双向绑定,这也正好解决了MVP模式的弊端。<br>业务不复杂的时候MVC和MVP模式都可以满足。天猫进口App搜索功能的结果展示页包含排序、结果筛选、结果集,并且有较为复杂的用户交互过程,能否做好层次划分和模块组件化,直接影响到以后的扩展和维护成本。基于此,我采用了MVVM模式来实现。</li>
</ul>
<h3 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h3><p>一个拥有搜索功能的APP在产品设计上一般包括以下三个方面:搜索入口、启动页、结果页。假设APP的搜索入口在首页的ActionBar位置,进入首页的时候需要从服务端请求底纹信息展示在搜索入口,底纹可以是运营干预词也可以是个性化推荐词。点击搜索入口即进入启动页,把底纹带入启动页。启动页可以接收用户的输入,还包含输入联想词列表、历史记录和热搜词推荐。结果页由排序栏、外透筛选栏、内置筛选、结果集列表组成。为了做到模态切换,分别用两个fragment来承载启动页和结果页,下面介绍下启动页和结果页的MVVM结构是如何设计的。</p>
<ul>
<li>启动页的MVVM结构:</li>
</ul>
<p><img src="/2019/04/08/MVVM模式及响应式编程实践-搜索功能/launcher.png" alt="1"></p>
<p>Launcher VM包含了三个Child VM:Histroy VM、Popular VM、Fuzzy VM,这三个VM对应的View都允许用户操作跳往搜索结果页。为了做到VM对View层的无感知,各个Child VM负责操作Observer对象,LauncherFragment观察这个对象的属性变化并作出跳转的响应,这需要Launcher VM维护这个可观察的对象,并把它传给三个Child VM。</p>
<ul>
<li>结果页的MVVM结构:</li>
</ul>
<p><img src="/2019/04/08/MVVM模式及响应式编程实践-搜索功能/result.png" alt="2"></p>
<p>Result VM主要包含:SearchBox VM、ResultList VM、SortTab VM、PreFilter VM、Filter VM,这五个Child VM,其中SortTab VM、PreFilter VM、Filter VM会持有Result VM的Request Observer。在与服务端定义搜索接口时,约定了服务端对排序类型和筛选项增加selected字段记录当前结果集的query选择了哪些条件,这样客户端就不用做本地状态保存,只要根据selected字段渲染组件的选中和非选中状态。在MVVM模式中,要把页面的各个模块组件化,每个组件有对应的VM,当用户有操作行为发生的时候,这些Child VM可以更改自己负责的请求参数,Request Observer观察到数据的变化会发起新的搜索请求,然后根据服务端返回的数据通知各个Child VM更新对应的View。</p>
<p>另外,两个页面都创建了不是用来做UI展示的Fragment持有页面的顶级VM,使得它不随着View组件生命周期的销毁而销毁。通过这样的模式设计,Fragment里的代码量较少,把业务逻辑规划到各个Child VM分而治之,并且这些Child VM是可进行单元测试的,也使得将来的功能扩展更加方便。</p>
<h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><p><a href="https://blog.csdn.net/u011897062/article/details/82185027" target="_blank" rel="noopener">DataBinding</a></p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2019/04/08/Android消息推送的思考/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Wakim Sun">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Wakim's泛泛之谈">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/04/08/Android消息推送的思考/" itemprop="url">Android消息推送的思考</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2019-04-08T11:25:42+08:00">
2019-04-08
</time>
</span>
<span class="post-category">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/Android/" itemprop="url" rel="index">
<span itemprop="name">Android</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>消息推送是移动APP运营中的关键环节,然而对于面向国内的Android开发者,建设这个基础能力一直是个痛点加难点。虽然Google有推出GCM服务,但是由于Google服务在国内不稳定,加上国内各大厂商定制了ROM,阉割了Google服务,所以GCM在国内基本作废。各大厂商纷纷推出自家推送服务,小米、华为、Oppo、Vivo、魅族等都有,各种第三方推送服务也应运而生。各大厂商自家的推送服务进程在自己定制过的Android系统中,享有较高的优先级,因此稳定性不在话下。到了别的牌子的手机上就要想办法让推送服务的后台进程保活了。各种三方推送服务想尽一切办法让推送服务进程在后台包活,由此衍生了很多“黑科技”,遭殃的却是Android用户,造成了手机资源占用高,手机卡、费电等问题。</p>
<p>对于Android推送的方案,我的观点是宁愿降低消息到达率,也不耍流氓。在不用尽各种办法来保活后台进程的基础上最大限度的提高消息到达率。根据市场占有率分析,华为、小米、Oppo、Vivo占据大头,而这几家都有自己系统级的推送服务,所以接入他们的服务即可。这对海外用户,采用GCM的方案。站在运营人员的角度,希望抹平平台的差异,配置一份消息,就能够触到iOS和Android用户。考虑下来,需要自己起一个后端服务平台对接这些推送渠道(包括Apple的APNS)。这些渠道外的手机用户在静默状态近乎放弃,只能等到应用被打开之后,与服务器建立了长连接再推送。推送场景分为用户相关和用户无关。用户无关的全量推送可以按照服务器登记的所有deviceId进行下发。用户相关的推送则要根据服务器登记的最新的deviceId进行下发。假设用户1在A、B两台设备进行了登录,最近的一次登录是B设备,那么对于用户1来说用户相关的推送只要针对B设备进行下发。下发之前还要检查当前用户是否在线,当前在线则走应用内的长连接,不在线则判断是否拥有自主推送渠道,若有则发给对应的渠道下发消息,若无则存储这条消息等到下次用户在线走应用内的长连接下发。这种做法既能保证应用不占用过多的系统资源,还能保证推送服务的稳定性。不足之处是,没有自主推送渠道也没有GCM服务的手机在离线状态下,推送无法送达,不过这部分的市场占有率相对较低。</p>
<p>一个好消息是,工信部在2017年联合国内各大Android厂商和互联网企业成立了安卓统一推送联盟,想要解决这种乱象。时间已经到了2019年,期待尽快有个结果。让Android开发者把心力专注在开发产品本身,这才是提高用户粘性的根本。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2019/02/28/CrossFit健身记/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Wakim Sun">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Wakim's泛泛之谈">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/02/28/CrossFit健身记/" itemprop="url">CrossFit健身记</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2019-02-28T21:46:30+08:00">
2019-02-28
</time>
</span>
<span class="post-category">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/运动/" itemprop="url" rel="index">
<span itemprop="name">运动</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="CrossFit是什么"><a href="#CrossFit是什么" class="headerlink" title="CrossFit是什么"></a>CrossFit是什么</h3><p>CrossFit是近些年来发展最为迅猛的健身方式之一。燃,热血,拼到精疲力竭,这些强烈的氛围,让你欲罢不能。团队式训练,加上躁动的音乐,一组一组的动作限时循环,练完之后累趴在地上,这是其他健身房没有的,也是CF的魅力之一。CF馆的器械跟其他健身房有些差别,没有太多花样的各种铁具,没有那么多跑步机。也因此,CF馆不需要那么大的场地,不需要那么多的私教。CF馆必需的器械就几种,综合训练架、划船机、壶铃、哑铃、杠铃、跳绳、跳箱、拉力带、吊环、脚踏风车。这么看来,CF馆成本不大、但是卡费却不低。CF馆一般由教练开十人以内的团课。</p>
<h3 id="术语介绍"><a href="#术语介绍" class="headerlink" title="术语介绍"></a>术语介绍</h3><p>Warm Up:热身<br>WOD:每天的训练计划(Workout Of the Day)<br>PR:个人单个项目的最好记录(Personal Record)<br>Rep:训练动作的个数(Repetation)<br>Snatch:抓举,一般指完整的抓举,需要全蹲去接杠,即臀部低于膝盖的位置接杠<br>Power Snatch:高位抓举,接杠位置在臀部高于膝盖<br>Muscle snatch:力量抓举或称为直腿抓举<br>Hollow:刚体保持,是一种体操的基础训练动作,将身体平躺,同时肩背和双脚离开地面,保持腹部、大腿、脚尖绷紧,同时双手靠着双耳,眼睛目视脚尖<br>Arch:俗称超人,与Hollow相反,将身体趴在地上,保持胸部和双脚离开地面,收紧臀部和后背,眼睛目视地面</p>
<h3 id="Body-45"><a href="#Body-45" class="headerlink" title="Body 45"></a>Body 45</h3><p>这是课程类型之一,偏向无氧训练。WOD有四个动作:Good Morging、俯卧支撑、跳箱、背部划船。每个动作做40秒休息20秒,共做8轮。</p>
<p>热身动作:双手背后握紧,做Good Morning鞠躬姿势,10次/轮,共3轮。可以加个拉力带套在肩膀,脚踩住。注意背不能弯,屁股向后撅,双脚与髋同宽。</p>
<p>Good Morging主要拉伸大腿后侧肌肉。双手握住哑铃,先站直再下蹲,膝盖微屈,背部不要弯弓,屁股向后,哑铃要过膝。到位之后再双脚蹬直站立,屁股要夹紧,双脚站距与髋同宽。</p>
<p>俯卧支撑,平行放两个哑铃在地上,双手撑住,手腕不弯曲,呈一条直线向上,双脚并拢。先撑一次再双脚打开,再两边手各一次提拉哑铃,分别靠两边的大臂和背部发力向后拉,过程中要保持核心收紧不塌腰。</p>
<p>跳箱的高度和速度决定难度。跳上去之后要挺直腰收紧屁股,再下来,如此反复。</p>
<p>背部划船,姿势同Good Morning,发力窍门同俯卧支撑的后拉。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://yoursite.com/2019/02/26/职场上的大小考/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Wakim Sun">
<meta itemprop="description" content>
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="Wakim's泛泛之谈">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2019/02/26/职场上的大小考/" itemprop="url">职场上的大小考</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2019-02-26T21:46:30+08:00">
2019-02-26
</time>
</span>
<span class="post-category">
<span class="post-meta-divider">|</span>
<span class="post-meta-item-icon">
<i class="fa fa-folder-o"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/职场/" itemprop="url" rel="index">
<span itemprop="name">职场</span>
</a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>职场上每当年底的时候,公司会对员工进行全年的绩效考核,这种考核会决定员工当年的奖金和来年的升职加薪。有些公司还有半年考核,甚至是季度考核。绩效考核的时候一般有直属老板、老板的老板、HR、或者其他横向的老板参与。直属老板对员工的工作内容和表现是比较清楚的,但是其他人就不一定了。</p>
<p>考核主要看你Owner的项目结果好不好。你作为技术人员是通过什么技术手段,如何帮助项目取得了好的结果,要指出技术的难点和复杂性在哪里。而且好的结果还要考虑如何量化,如何做数据指标的对比,你做这项工作之前的数据和你接手之后的数据的直观比对,以此来打动评委。</p>
<p>如何在短时间内直观、有重点地向他们展示这一年的工作成果是很需要技巧的。“临时抱佛脚”在临近考核的时候匆匆忙忙做出不直观的PPT,或者Presentation的能力欠佳,又或者对解决的问题看得不够全面透彻、缺少独立的思考和见解,都会在考核过程中受到很多挑战。</p>
<p>所以在平时工作中就要做好总结、积累和练习。公司一般会要求员工写周报、双周报,甚至有的会要求写日报。日常工作中要重视这些总结,否则到了年底容易陷入不知如何做好这场“职场考试”的窘境中。下面我想从自己已经经历的三份工作中做一下这方面的总结,期待以后能够做得更好。</p>
<h3 id="2012-08-2014-04"><a href="#2012-08-2014-04" class="headerlink" title="2012.08~2014.04"></a>2012.08~2014.04</h3><p>第一份工作是在厦门歌乐电子企业有限公司,日立集团旗下的一家子公司。在日常工作中引入了“PDCA管理循环”,要求员工写日报总结一天的工作,以邮件的形式发给主管以及组内成员。PDCA循环,即计划(Plan)、执行(Do)、检查(Check)、调整(Action)。这个原则要求对手头的各项工作先做出计划、设定目标,然后实施计划、记录具体步骤过程,之后要检查进度和效果,有哪些阻碍和困难,最后把完成的总结出来,没完成的放到下一个循环中去。我当时的做法是在Excel中制作PDCA的表格填写日报,年终总结的时候方便回顾。一般P和A部分每天的变化比较小,而D和C可写的内容比较多。</p>
<p>这份工作总共经历过两次绩效考核,考核的等级分为A、B、C、D。2012年8月到2013年1月是第一年,刚毕业工作中表现平平,拿到了C,那一年的绩效考核是我职场生涯的第一次考试。2013年1月至2014年1月是第二年,这一年做了一个比较成功的项目,但是年底Review的时候自我感觉并不好,当众演讲的时候很紧张,PPT也做得不够直观,这一年拿到了B。等到2014年3月提出辞职的时候,HR找我聊说到因为去年拿了B,所以今年在加薪方面也会比大多数人多,但是我那时候已经下定决心要转型做移动互联网,所以就没有留下了。</p>
<p>总结这份工作,那个时候对这种工作管理的方式理解的并不深,很多地方没有做到位,特别是在PPT制作方面和Presentation方面的能力也都因为锻炼得少而做得不够好。</p>
<h3 id="2014-04-2016-09"><a href="#2014-04-2016-09" class="headerlink" title="2014.04~2016.09"></a>2014.04~2016.09</h3><p>第二份工作来到一家移动互联网创业公司,厦门幻世网络科技有限公司。因为是初创公司,所以这里有很多管理还不完善。也因为是创业公司,工作中引入了一些比较先进的协作方式。比如每天站立会沟通重要事项,采用敏捷开发方法小步快速迭代版本,把任务写在便签钉在墙上认领。日报也是要发的,但是没有严格的格式要求。年底没有绩效考核,只是普发十三薪(老板会根据员工的表现、贡献给予一定的期权)。在这里职场考核这方面的锻炼减少了,也就没有更多心得了。然而从2014年4月到2016年9月,在这里工作了将近两年半的时间,以至于刚去下一家公司的时候,对绩效考核的门道几乎是又从零开始摸索的。</p>
<p>值得一提的是,这份工作我专注在Android开发,特别是NDK开发,和iOS的哥们儿一起完成了一个跨平台移动开发框架,支撑了公司的移动端业务。</p>
<h3 id="2016-09-至今"><a href="#2016-09-至今" class="headerlink" title="2016.09~至今"></a>2016.09~至今</h3><p>第三份工作到了阿里,这里无论是对周报的严格要求程度,还是对绩效考核的重视程度,都可以说是近乎“苛刻”。这是好事,对员工的个人成长相当有帮助,也有利于这么一个庞大组织的管理。</p>
<p>首先说周报,一线的员工要把周报发给整个技术部门的邮件大组,如果是项目PM的角色,还要抄送业务方。Team Leader除了自己的个人工作汇报,还要收集团队里成员的周报汇总发给老板,老板要汇总Team Leader的周报发给老板的老板。所以为了老板们能有足够的时间写周报,一线的同学往往每周四晚上就要把周报发出来了。一线同学要写的是做项目的过程中踩的坑、某个技术点的解决方案、甚至可以是代码的分享,还可以写个人的学习、收获了什么新知识。要及时思考,有感就发。如果是项目PM,还要写项目背景、项目目标、关键时间节点(提测时间、上线时间、接口方的时间点、里程碑时间点等等)、当前进度(正常还是延期、延期的原因、带来的风险)、技术难点如何克服、技术重点总结、跨部门沟通事项、项目数据(业务数据、性能数据)、分析数据波动的原因(为什么涨、为什么跌)、项目思考(为什么要做这个项目、要解决现在存在的什么问题、做了之后真的能解决吗、有没有自己的见解)。</p>
<p>再说绩效考核,初期要先做好规划,今年要支撑哪些业务、要做什么技术提升。一般绩效分为以下几个方面,质量效率、业务支撑、团队协作、个人成长,所占的比例有所侧重。年底的时候再对这些目标进行自评。但是往往会发现,在年初要做好这样的规划是很难的,因为变化永远在意料之外。绩效考核的一个重要环节就是Review,要准备PPT把这一年的工作成果写清楚。这时候就能体会周报的重要性了。如果没有每周的总结积累,想在年底回顾这三百多天的工作内容和成果,以及重新思考一遍技术方案,那就是临时抱佛脚,不会做得很好的。PPT里要体现一年做的重点项目,业务方的诉求是什么,前期有没有数据可以推导出做这件事的必要性,技术上用什么方案实现了需求,带来的结果数据如何,为什么做这些能带来数据的利好,如果结果不好又是因为什么,个人从中有没有得到什么经验,成长在哪里(业务理解能力、技术沉淀)。当然还要做下一年的规划,当前还存在什么问题,用自己的观点规划产品,抓住重点规划技术。另外一个重要的点是,PPT要做得简洁直观,做完要留有时间试讲,自己掐时间模拟现场讲出来,克服紧张的心理。每一页和每一页之间要有逻辑地衔接。我有两次讲完都被主管提出讲得让听众昏昏欲睡。意思是讲的不够有激情、不够有吸引力、没有跟台下有良好的互动。考核的最后一个环节,往往是会有人提出一些比较有挑战性的问题,这时候要不畏“强权”,勇于探讨,把自己独立的思考讲出来,让提问者知道自己不是一个被动的执行者。</p>
<p>除了在平时工作中向自己Owner的项目拿结果,如果能帮助团队招聘到新人,在老板那里也是很加分的。因为老板的老板往往会给他设定这方面的kpi。老板除了要招新人还要保障现有成员不要流失,否则老板在被考核管理能力这一项时会站不住脚。</p>
<h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>总结职场上的考核要注意的点有如下几个:</p>
<ul>
<li>平时要保质保量的完成交到手里的每个任务,争取拿到超出预期的结果。</li>
<li>对工作要有系统性的思考,并且能逻辑清晰的表达。</li>
<li>不会写文章的工程师不是好的架构师。</li>
<li>做不好PPT的工程师不是好的架构师。</li>
</ul>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
</section>
</div>