功能-代码中动态设置

动态加载网络数据的布局

实现原理

把json字符串数据(指定格式的json字符串),按照规则通过类反射机制解析成所属的属性类,把这些属性类生成不同的View,包含嵌套View、同级View(设置好它们的属性)。最后添加到父View,调用setContentView这个view展示出来。
ps:其中解析成属性类的需要注意空指针异常、类转换异常

数据源

因为是demo,直接把数据写到本地的json文件中(assets/TextIT.json)

调用

MainActivity.java中的onCreate()中调用

1
2
3
4
//根据json 解析得到的json对象,来创建View
View sampleView = DynamicView.createView(this, jsonObject,ViewHolder.class);
sampleView.setLayoutParams(new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.MATCH_PARENT));
setContentView(sampleView);

DynamicViewcreateView()

重点代码

1
2
3
4
//创建View
View container = createViewInternal(context,jsonObject, parent,ids);
//解析布局参数(可以适配Android 所有原生位置的属性,为每个控件生成Params)
DynamicHelper.applyLayoutProperties(container,(List)container.getTag(INTERNAL_TAG_ID), parent, ids);

demo的完整源码

DynamicHelper.java

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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
package com.example.shenbh.showcarddemo.Dynamic;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.bumptech.glide.Glide;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
* Created by avocarrot on 11/12/2014.
* Helper function that apply properties in views
*/
public class DynamicHelper {

private static Context mContext;

/**
* apply dynamic properties that are not relative with layout in view
*
* @param view
* @param properties
*/
public static String applyStyleProperties(Context context, View view, List<DynamicProperty> properties) {
mContext = context;
String id = "";
for (DynamicProperty dynProp : properties) {
switch (dynProp.name) {
case ID: {
id = dynProp.getValueString();
Log.e("cx", "ID applyStyleProperties :" + id);
}
break;
case BACKGROUND: {
applyBackground(context, view, dynProp);
}
break;
case TEXT: {
applyText(view, dynProp);
}
break;
case TEXTCOLOR: {
applyTextColor(view, dynProp);
}
break;
case TEXTSIZE: {
applyTextSize(view, dynProp);
}
break;
case TEXTSTYLE: {
applyTextStyle(view, dynProp);
}
break;
case PADDING: {
applyPadding(view, dynProp);
}
break;
case PADDING_LEFT: {
applyPadding(view, dynProp, 0);
}
break;
case PADDING_TOP: {
applyPadding(view, dynProp, 1);
}
break;
case PADDING_RIGHT: {
applyPadding(view, dynProp, 2);
}
break;
case PADDING_BOTTOM: {
applyPadding(view, dynProp, 3);
}
break;
case MINWIDTH: {
applyMinWidth(view, dynProp);
}
break;
case MINHEIGTH: {
applyMinHeight(view, dynProp);
}
break;
case ELLIPSIZE: {
applyEllipsize(view, dynProp);
}
break;
case MAXLINES: {
applyMaxLines(view, dynProp);
}
break;
case ORIENTATION: {
applyOrientation(view, dynProp);
}
break;
case SUM_WEIGHT: {
applyWeightSum(view, dynProp);
}
break;
case GRAVITY: {
applyGravity(view, dynProp);
}
break;
case SRC: {
applySrc(context, view, dynProp);
}
break;
case SCALETYPE: {
applyScaleType(view, dynProp);
}
break;
case ADJUSTVIEWBOUNDS: {
applyAdjustBounds(view, dynProp);
}
break;
case DRAWABLELEFT: {
applyCompoundDrawable(view, dynProp, 0);
}
break;
case DRAWABLETOP: {
applyCompoundDrawable(view, dynProp, 1);
}
break;
case DRAWABLERIGHT: {
applyCompoundDrawable(view, dynProp, 2);
}
break;
case DRAWABLEBOTTOM: {
applyCompoundDrawable(view, dynProp, 3);
}
break;
case ENABLED: {
applyEnabled(view, dynProp);
}
break;
case SELECTED: {
applySelected(view, dynProp);
}
break;
case CLICKABLE: {
applyClickable(view, dynProp);
}
break;
case SCALEX: {
applyScaleX(view, dynProp);
}
break;
case SCALEY: {
applyScaleY(view, dynProp);
}
break;
case TAG: {
applyTag(view, dynProp);
}
break;
case FUNCTION: {
applyFunction(view, dynProp);
}
break;
case VISIBILITY: {
applyVisibility(view, dynProp);
}
break;
default:
break;
}
}
return id;
}

/**
* apply dynamic properties for layout in view
*
* 可以适配Android 所有原生位置的属性,这是为每个控件生成Params.
* @param view
* @param properties : layout properties to apply
* @param viewGroup : parent view
* @param ids : hashmap of ids <String, Integer> (string as setted in json, int that we use in layout)
*/
public static void applyLayoutProperties(View view, List<DynamicProperty> properties, ViewGroup viewGroup, HashMap<String, Integer> ids) {
if (viewGroup == null) {
return;
}
ViewGroup.LayoutParams params = createLayoutParams(viewGroup);

for (DynamicProperty dynProp : properties) {
try {
switch (dynProp.name) {
case LAYOUT_HEIGHT: {
params.height = dynProp.getValueInt();
}
break;
case LAYOUT_WIDTH: {
params.width = dynProp.getValueInt();
}
break;
case LAYOUT_MARGIN: {
if (params instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = ((ViewGroup.MarginLayoutParams) params);
p.bottomMargin = p.topMargin = p.leftMargin = p.rightMargin = dynProp.getValueInt();
}
}
break;
case LAYOUT_MARGINLEFT: {
if (params instanceof ViewGroup.MarginLayoutParams) {
((ViewGroup.MarginLayoutParams) params).leftMargin = dynProp.getValueInt();
}
}
break;
case LAYOUT_MARGINTOP: {
if (params instanceof ViewGroup.MarginLayoutParams) {
((ViewGroup.MarginLayoutParams) params).topMargin = dynProp.getValueInt();
}
}
break;
case LAYOUT_MARGINRIGHT: {
if (params instanceof ViewGroup.MarginLayoutParams) {
((ViewGroup.MarginLayoutParams) params).rightMargin = dynProp.getValueInt();
}
}
break;
case LAYOUT_MARGINBOTTOM: {
if (params instanceof ViewGroup.MarginLayoutParams) {
((ViewGroup.MarginLayoutParams) params).bottomMargin = dynProp.getValueInt();
}
}
break;
case LAYOUT_ABOVE: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ABOVE, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_BELOW: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.BELOW, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_TOLEFTOF: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.LEFT_OF, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_TORIGHTOF: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.RIGHT_OF, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_TOSTARTOF: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.START_OF, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_TOENDOF: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.END_OF, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNBASELINE: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_BASELINE, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNLEFT: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_LEFT, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNTOP: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_TOP, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNRIGHT: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_RIGHT, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNBOTTOM: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_BOTTOM, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNSTART: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_START, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNEND: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_END, ids.get(dynProp.getValueString()));
}
}
break;
case LAYOUT_ALIGNWITHPARENTIFMISSING: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).alignWithParent = dynProp.getValueBoolean();
}
}
break;
case LAYOUT_ALIGNPARENTTOP: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_PARENT_TOP);
}
}
break;
case LAYOUT_ALIGNPARENTBOTTOM: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
}
}
break;
case LAYOUT_ALIGNPARENTLEFT: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_PARENT_LEFT);
}
}
break;
case LAYOUT_ALIGNPARENTRIGHT: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
}
}
break;
case LAYOUT_ALIGNPARENTSTART: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_PARENT_START);
}
}
break;
case LAYOUT_ALIGNPARENTEND: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.ALIGN_PARENT_END);
}
}
break;
case LAYOUT_CENTERHORIZONTAL: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.CENTER_HORIZONTAL);
}
}
break;
case LAYOUT_CENTERVERTICAL: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.CENTER_VERTICAL);
}
}
break;
case LAYOUT_CENTERINPARENT: {
if (params instanceof RelativeLayout.LayoutParams) {
((RelativeLayout.LayoutParams) params).addRule(RelativeLayout.CENTER_IN_PARENT);
}
}
break;
case LAYOUT_GRAVITY: {
switch (dynProp.type) {
case INTEGER: {
if (params instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) params).gravity = dynProp.getValueInt();
}
}
break;
case STRING: {
if (params instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) params).gravity = (Integer) dynProp.getValueInt(Gravity.class, dynProp.getValueString().toUpperCase());
}
}
break;
default:
break;
}
}
break;
case LAYOUT_WEIGHT: {
switch (dynProp.type) {
case FLOAT: {
if (params instanceof LinearLayout.LayoutParams) {
((LinearLayout.LayoutParams) params).weight = dynProp.getValueFloat();
}
}
break;
default:
break;
}
}
break;
default:
break;
}
} catch (Exception e) {
}
}

view.setLayoutParams(params);
}

public static ViewGroup.LayoutParams createLayoutParams(ViewGroup viewGroup) {
ViewGroup.LayoutParams params = null;
if (viewGroup != null) {
try {
/* find parent viewGroup and create LayoutParams of that class */
Class layoutClass = viewGroup.getClass();
while (!classExists(layoutClass.getName() + "$LayoutParams")) {
layoutClass = layoutClass.getSuperclass();
}
String layoutParamsClassname = layoutClass.getName() + "$LayoutParams";
Class layoutParamsClass = Class.forName(layoutParamsClassname);
/* create the actual layoutParams object */
params = (ViewGroup.LayoutParams) layoutParamsClass.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(new Object[]{ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT});
} catch (Exception e) {
e.printStackTrace();
}
}
if (params == null) {
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}

return params;
}

/*** View Properties ***/

/**
* apply background in view. possible type :
* - COLOR
* - REF => search for that drawable in resources
* - BASE64 => convert base64 to bitmap and apply in view
*/
public static void applyBackground(final Context context, View view, final DynamicProperty property) {
if (view != null) {
switch (property.type) {
case COLOR: {
view.setBackgroundColor(property.getValueColor());
}
break;
case REF: {
Log.e("cx", "property:" + property.getValueString());
String value = property.getValueString();
if (value.contains("|")) {
GradientDrawable gd = new GradientDrawable();
gd.setCornerRadius(12);
gd.setColor(Color.parseColor("##ffffff"));
view.setBackgroundDrawable(gd);
} else if (value.contains("+")) {
GradientDrawable gd = new GradientDrawable();
gd.setCornerRadius(12);
gd.setColor(Color.parseColor("##f5942b"));
view.setBackgroundDrawable(gd);
} else if (value.startsWith("@drawable")) {
Glide.with(context).load("http://www.baidu.com/favicon.ico").into((ImageView) view);
// String backgroundString = value.substring(10);
// Log.e("cf", "applyBackground : " + "ref" + property.getValueString());
// File appDir = new File(Environment.getExternalStorageDirectory(), "IZD");
// if(!appDir.exists()){
// appDir.mkdir();
// }
// String rootpath = appDir.getPath();
// StringBuilder sb = new StringBuilder();
// sb.append(rootpath).append("/").append(backgroundString)
// .append(".png");
//
// Bitmap bm = BitmapFactory.decodeFile(sb.toString());
// view.setBackgroundDrawable(new BitmapDrawable(bm));
}
// view.setBackgroundResource(getDrawableId(view.getContext(), property.getValueString()));
}
break;
case BASE64: {
Log.e("cf", "applyBackground : " + "base64" + property.getValueString());
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(property.getValueBitmapDrawable());
} else {
view.setBackgroundDrawable(property.getValueBitmapDrawable());
}
}
break;
case DRAWABLE: {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(property.getValueGradientDrawable());
} else {
view.setBackgroundDrawable(property.getValueGradientDrawable());
}
}
case CLICK: {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "点击啦这个View:" + property.getValueString(), Toast.LENGTH_SHORT).show();
}
});
}
break;
default:
break;
}
}
}

/**
* apply padding in view
*/
public static void applyPadding(View view, DynamicProperty property) {
if (view != null) {
switch (property.type) {
case DIMEN: {
int padding = property.getValueInt();
view.setPadding(padding, padding, padding, padding);
}
break;
default:
break;
}
}
}

/**
* apply padding in view
*/
public static void applyPadding(View view, DynamicProperty property, int position) {
if (view != null) {
switch (property.type) {
case DIMEN: {
int[] padding = new int[]{
view.getPaddingLeft(),
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom()
};
padding[position] = property.getValueInt();
view.setPadding(padding[0], padding[1], padding[2], padding[3]);
}
break;
default:
break;
}
}
}

/**
* apply minimum Width in view
*/
public static void applyMinWidth(View view, DynamicProperty property) {
if (view != null) {
if (property.type == DynamicProperty.TYPE.DIMEN) {
view.setMinimumWidth(property.getValueInt());
}
}
}

/**
* apply minimum Height in view
*/
public static void applyMinHeight(View view, DynamicProperty property) {
if (view != null) {
if (property.type == DynamicProperty.TYPE.DIMEN) {
view.setMinimumHeight(property.getValueInt());
}
}
}

/**
* apply enabled in view
*/
public static void applyEnabled(View view, DynamicProperty property) {
if (view != null) {
switch (property.type) {
case BOOLEAN: {
view.setEnabled(property.getValueBoolean());
}
break;
default:
break;
}
}
}

/**
* apply selected in view
*/
public static void applySelected(View view, DynamicProperty property) {
if (view != null) {
switch (property.type) {
case BOOLEAN: {
view.setSelected(property.getValueBoolean());
}
break;
default:
break;
}
}
}

/**
* apply clickable in view
*/
public static void applyClickable(View view, DynamicProperty property) {
if (view != null) {
switch (property.type) {
case BOOLEAN: {
view.setClickable(property.getValueBoolean());
}
break;
default:
break;
}
}
}

/**
* apply selected in view
*/
public static void applyScaleX(View view, DynamicProperty property) {
if (view != null) {
switch (property.type) {
case BOOLEAN: {
view.setScaleX(property.getValueFloat());
}
break;
default:
break;
}
}
}

/**
* apply selected in view
*/
public static void applyScaleY(View view, DynamicProperty property) {
if (view != null) {
switch (property.type) {
case BOOLEAN: {
view.setScaleY(property.getValueFloat());
}
break;
default:
break;
}
}
}

/**
* apply visibility in view
*/
private static void applyVisibility(View view, DynamicProperty property) {
if (view != null) {
switch (property.type) {
case STRING: {
switch (property.getValueString()) {
case "gone": {
view.setVisibility(View.GONE);
}
break;
case "visible": {
view.setVisibility(View.VISIBLE);
}
break;
case "invisible": {
view.setVisibility(View.INVISIBLE);
}
break;
default:
break;
}
}
break;
default:
break;
}
}
}

/*** TextView Properties ***/

/**
* apply text (used only in TextView)
* - STRING : the actual string to set in textView
* - REF : the name of string resource to apply in textView
*/
public static void applyText(View view, DynamicProperty property) {
if (view instanceof TextView) {
switch (property.type) {
case STRING: {
((TextView) view).setText(property.getValueString());
}
break;
case REF: {
((TextView) view).setText(getStringId(view.getContext(), property.getValueString()));
}
break;
default:
break;
}
}
}

/**
* apply the color in textView
*/
public static void applyTextColor(View view, DynamicProperty property) {
if (view instanceof TextView) {
switch (property.type) {
case COLOR: {
((TextView) view).setTextColor(property.getValueColor());
}
break;
default:
break;
}
}
}

/**
* apply the textSize in textView
*/
public static void applyTextSize(View view, DynamicProperty property) {
if (view instanceof TextView) {
switch (property.type) {
case DIMEN: {
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, property.getValueFloat());
}
break;
default:
break;
}
}
}

/**
* apply the textStyle in textView
*/
public static void applyTextStyle(View view, DynamicProperty property) {
if (view instanceof TextView) {
switch (property.type) {
case INTEGER: {
((TextView) view).setTypeface(null, property.getValueInt());
}
break;
default:
break;
}
}
}

/**
* apply ellipsize property in textView
*/
public static void applyEllipsize(View view, DynamicProperty property) {
if (view instanceof TextView) {
((TextView) view).setEllipsize(TextUtils.TruncateAt.valueOf(property.getValueString().toUpperCase().trim()));
}
}

/**
* apply maxLines property in textView
*/
public static void applyMaxLines(View view, DynamicProperty property) {
if (view instanceof TextView) {
((TextView) view).setMaxLines(property.getValueInt());
}
}

/**
* apply gravity property in textView
* - INTEGER => valus of gravity in @link(Gravity.java)
* - STRING => name of variable in @lin(Gravity.java)
*/
public static void applyGravity(View view, DynamicProperty property) {
if (view instanceof TextView) {
switch (property.type) {
case INTEGER: {
((TextView) view).setGravity(property.getValueInt());
}
break;
case STRING: {
((TextView) view).setGravity((Integer) property.getValueInt(Gravity.class, property.getValueString().toUpperCase()));
}
break;
default:
break;
}
}
}

/**
* apply compound property in textView
* position 0:left, 1:top, 2:right, 3:bottom
* - REF : drawable to load as compoundDrawable
* - BASE64 : decode as base64 and set as CompoundDrawable
*/
public static void applyCompoundDrawable(View view, DynamicProperty property, int position) {
if (view instanceof TextView) {
TextView textView = (TextView) view;
Drawable[] d = textView.getCompoundDrawables();
switch (property.type) {
case REF: {
try {
d[position] = view.getContext().getResources().getDrawable(getDrawableId(view.getContext(), property.getValueString()));
} catch (Exception e) {
}
}
break;
case BASE64: {
d[position] = property.getValueBitmapDrawable();
}
break;
case DRAWABLE: {
d[position] = property.getValueGradientDrawable();
}
break;
default:
break;
}
textView.setCompoundDrawablesWithIntrinsicBounds(d[0], d[1], d[2], d[3]);
}
}


/*** ImageView Properties ***/

/**
* apply src property in imageView
* - REF => name of drawable
* - BASE64 => decode value as base64 image
*/
public static void applySrc(Context context, View view, DynamicProperty property) {
if (view instanceof ImageView) {
switch (property.type) {
case REF: {
((ImageView) view).setImageResource(getDrawableId(view.getContext(), property.getValueString()));
}
break;
case BASE64: {
Log.e("cf", "applySrc :" + property.getValueString());
((ImageView) view).setImageBitmap(property.getValueBitmap());
}
break;
default:
break;
}
}
}

/**
* apply scaleType property in ImageView
*/
public static void applyScaleType(View view, DynamicProperty property) {
if (view instanceof ImageView) {
switch (property.type) {
case STRING: {
((ImageView) view).setScaleType(ImageView.ScaleType.valueOf(property.getValueString().toUpperCase()));
}
break;
default:
break;
}
}
}

/**
* apply adjustBounds property in ImageView
*/
public static void applyAdjustBounds(View view, DynamicProperty property) {
if (view instanceof ImageView) {
switch (property.type) {
case BOOLEAN: {
((ImageView) view).setAdjustViewBounds(property.getValueBoolean());
}
break;
default:
break;
}
}
}

/*** LinearLayout Properties ***/

/**
* apply orientation property in LinearLayout
* - INTEGER => 0:Horizontal , 1:Vertical
* - STRING
*/
public static void applyOrientation(View view, DynamicProperty property) {
if (view instanceof LinearLayout) {
switch (property.type) {
case INTEGER: {
((LinearLayout) view).setOrientation(property.getValueInt() == 0 ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
}
break;
case STRING: {
((LinearLayout) view).setOrientation("HORIZONTAL".equalsIgnoreCase(property.getValueString()) ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
}
break;
default:
break;
}
}
}

/**
* apply WeightSum property in LinearLayout
*/
public static void applyWeightSum(View view, DynamicProperty property) {
if ((view instanceof LinearLayout) && (property.type == DynamicProperty.TYPE.FLOAT)) {
((LinearLayout) view).setWeightSum(property.getValueFloat());
}
}

/**
* add string as tag
*/
public static void applyTag(View view, DynamicProperty property) {
view.setTag(property.getValueString());
}


/**
* apply generic function in View
*/
public static void applyFunction(View view, DynamicProperty property) {

if (property.type == DynamicProperty.TYPE.JSON) {
try {
JSONObject json = property.getValueJSON();

String functionName = json.getString("function");
JSONArray args = json.getJSONArray("args");

Class[] argsClass;
Object[] argsValue;
if (args == null) {
argsClass = new Class[0];
argsValue = new Object[0];
} else {
try {
List<Class> classList = new ArrayList<>();
List<Object> valueList = new ArrayList<>();

int i = 0;
int count = args.length();
for (; i < count; i++) {
JSONObject argJsonObj = args.getJSONObject(i);
boolean isPrimitive = argJsonObj.has("primitive");
String className = argJsonObj.getString(isPrimitive ? "primitive" : "class");
String classFullName = className;
if (!classFullName.contains(".")) {
classFullName = "java.lang." + className;
}
Class clazz = Class.forName(classFullName);
if (isPrimitive) {
Class primitiveType = (Class) clazz.getField("TYPE").get(null);
classList.add(primitiveType);
} else {
classList.add(clazz);
}

try {
valueList.add(getFromJSON(argJsonObj, "value", clazz));
} catch (Exception e) {
e.printStackTrace();
}
}
argsClass = classList.toArray(new Class[classList.size()]);
argsValue = valueList.toArray(new Object[valueList.size()]);
} catch (Exception e) {
argsClass = new Class[0];
argsValue = new Object[0];
}
}

try {
view.getClass().getMethod(functionName, argsClass).invoke(view, argsValue);
} catch (SecurityException e) {
} catch (NoSuchMethodException e) {
e.printStackTrace();
}

} catch (Exception e) {
e.printStackTrace();
}
}

}


/**
* return the id (from the R.java autogenerated class) of the drawable that pass its name as argument
*/
public static int getDrawableId(Context context, String name) {
return context.getResources().getIdentifier(name, "drawable", context.getPackageName());
}

/**
* return the id (from the R.java autogenerated class) of the string that pass its name as argument
*/
public static int getStringId(Context context, String name) {
return context.getResources().getIdentifier(name, "string", context.getPackageName());
}

/**
* convert densityPixel to pixel
*/
public static float dpToPx(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
// return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}

/**
* convert scalePixel to pixel
*/
public static float spToPx(float sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, Resources.getSystem().getDisplayMetrics());
}

/**
* convert pixel to densityPixel
*/
public static float pxToDp(int px) {
return (px / Resources.getSystem().getDisplayMetrics().density);
}

/**
* convert pixel to scaledDensityPixel
*/
public static float pxToSp(int px) {
return (px / Resources.getSystem().getDisplayMetrics().scaledDensity);
// return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, px, Resources.getSystem().getDisplayMetrics());
}

/**
* convert densityPixel to scaledDensityPixel
*/
public static float dpToSp(float dp) {
return (int) (dpToPx(dp) / Resources.getSystem().getDisplayMetrics().scaledDensity);
}

/**
* return device Width
*/
public static int deviceWidth() {
return Resources.getSystem().getDisplayMetrics().widthPixels;
}

/**
* get ViewHolder class and make reference for evert @link(DynamicViewId) to the actual view
* if target contains HashMap<String, Integer> will replaced with the idsMap
*/
public static void parseDynamicView(Object target, View container, HashMap<String, Integer> idsMap) {

for (Field field : target.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(DynamicViewId.class)) {
/* if variable is annotated with @DynamicViewId */
final DynamicViewId dynamicViewIdAnnotation = field.getAnnotation(DynamicViewId.class);
/* get the Id of the view. if it is not set in annotation user the variable name */
String id = dynamicViewIdAnnotation.id();
if ("".equalsIgnoreCase(id)) {
id = field.getName();
}
if (idsMap.containsKey(id)) {
try {
/* get the view Id from the Hashmap and make the connection to the real View */
field.set(target, container.findViewById(idsMap.get(id)));
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
} else if (("ids".equalsIgnoreCase(field.getName())) && (field.getType() == idsMap.getClass())) {
try {
field.set(target, idsMap);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

}

private static Object getFromJSON(JSONObject json, String name, Class clazz) throws JSONException {
if ((clazz == Integer.class) || (clazz == Integer.TYPE)) {
return json.getInt(name);
} else if ((clazz == Boolean.class) || (clazz == Boolean.TYPE)) {
return json.getBoolean(name);
} else if ((clazz == Double.class) || (clazz == Double.TYPE)) {
return json.getDouble(name);
} else if ((clazz == Float.class) || (clazz == Float.TYPE)) {
return (float) json.getDouble(name);
} else if ((clazz == Long.class) || (clazz == Long.TYPE)) {
return json.getLong(name);
} else if (clazz == String.class) {
return json.getString(name);
} else if (clazz == JSONObject.class) {
return json.getJSONObject(name);
} else {
return json.get(name);

}
}

public static boolean classExists(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException ex) {
return false;
}
}

}

DynamicProperty.java

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
package com.example.shenbh.showcarddemo.Dynamic;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.text.TextUtils;
import android.util.Base64;
import android.view.ViewGroup;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Arrays;

/**
* Created by avocarrot on 11/12/2014.
* Every Property of a View is a Dynaic Property
*/
public class DynamicProperty {

/**
* possible types that we handle
**/
public enum TYPE {
/**
* 无效
*/
NO_VALID,
STRING,
DIMEN,
INTEGER,
FLOAT,
COLOR,
REF,
BOOLEAN,
BASE64,
DRAWABLE,
JSON,
CLICK
}

/**
* possible property name that we handle
**/
public enum NAME {
//无效
NO_VALID,
ID,
LAYOUT_WIDTH,
LAYOUT_HEIGHT,
PADDING_LEFT,
PADDING_RIGHT,
PADDING_TOP,
PADDING_BOTTOM,
PADDING,
LAYOUT_MARGINLEFT,
LAYOUT_MARGINRIGHT,
LAYOUT_MARGINTOP,
LAYOUT_MARGINBOTTOM,
LAYOUT_MARGIN,
BACKGROUND,
ENABLED,
SELECTED,
CLICKABLE,
SCALEX,
SCALEY,
MINWIDTH,
MINHEIGTH,
VISIBILITY,
/* textView */
TEXT,
TEXTCOLOR,
TEXTSIZE,
TEXTSTYLE,
ELLIPSIZE,
MAXLINES,
GRAVITY,
DRAWABLETOP,
DRAWABLEBOTTOM,
DRAWABLELEFT,
DRAWABLERIGHT,
/* imageView */
SRC,
SCALETYPE,
ADJUSTVIEWBOUNDS,
/* layout */
LAYOUT_ABOVE,
LAYOUT_ALIGNBASELINE,
LAYOUT_ALIGNBOTTOM,
LAYOUT_ALIGNEND,
LAYOUT_ALIGNLEFT,
LAYOUT_ALIGNPARENTBOTTOM,
LAYOUT_ALIGNPARENTEND,
LAYOUT_ALIGNPARENTLEFT,
LAYOUT_ALIGNPARENTRIGHT,
LAYOUT_ALIGNPARENTSTART,
LAYOUT_ALIGNPARENTTOP,
LAYOUT_ALIGNRIGHT,
LAYOUT_ALIGNSTART,
LAYOUT_ALIGNTOP,
LAYOUT_ALIGNWITHPARENTIFMISSING,
LAYOUT_BELOW,
LAYOUT_CENTERHORIZONTAL,
LAYOUT_CENTERINPARENT,
LAYOUT_CENTERVERTICAL,
LAYOUT_TOENDOF,
LAYOUT_TOLEFTOF,
LAYOUT_TORIGHTOF,
LAYOUT_TOSTARTOF,
LAYOUT_GRAVITY,
LAYOUT_WEIGHT,
SUM_WEIGHT,
ORIENTATION,

TAG,
FUNCTION
}

public NAME name;
public TYPE type;
private Object value;

/**
* @param v value to convert as string
* @return Value as object depends on the type
*/
private Object convertValue(Object v) {
if (v == null) {
return null;
}
switch (type) {
case INTEGER: {
return Integer.parseInt(v.toString());
}
case FLOAT: {
return Float.parseFloat(v.toString());
}
case DIMEN: {
return convertDimenToPixel(v.toString());
}
case COLOR: {
return convertColor(v.toString());
}
case BOOLEAN: {
String value = v.toString();
if ("t".equalsIgnoreCase(value)) {
return true;
} else if ("f".equalsIgnoreCase(value)) {
return false;
} else if ("true".equalsIgnoreCase(value)) {
return true;
} else if ("false".equalsIgnoreCase(value)) {
return false;
}
return Integer.parseInt(value) == 1;
}
case BASE64: {
try {
InputStream stream = new ByteArrayInputStream(Base64.decode(v.toString(), Base64.DEFAULT));
return BitmapFactory.decodeStream(stream);
} catch (Exception e) {
return null;
}
}
case DRAWABLE: {
JSONObject drawableProperties = (JSONObject) v;

GradientDrawable gd = new GradientDrawable();

if (drawableProperties != null) {

try {
gd.setColor(convertColor(drawableProperties.getString("COLOR")));
} catch (JSONException e) {
}
if (drawableProperties.has("CORNER")) {
String cornerValues = null;
try {
cornerValues = drawableProperties.getString("CORNER");
} catch (JSONException e) {
}
if (!TextUtils.isEmpty(cornerValues)) {
if (cornerValues.contains("|")) {
float[] corners = new float[8];
Arrays.fill(corners, 0);
String[] values = cornerValues.split("\\|");
int count = Math.min(values.length, corners.length);
for (int i = 0; i < count; i++) {
try {
corners[i] = convertDimenToPixel(values[i]);
} catch (Exception e) {
corners[i] = 0f;
}
}
gd.setCornerRadii(corners);
} else {
try {
gd.setCornerRadius(convertDimenToPixel(cornerValues));
} catch (Exception e) {
gd.setCornerRadius(0f);
}
}
}

}
int strokeColor = 0x00FFFFFF;
int strokeSize = 0;
if (drawableProperties.has("STROKECOLOR")) {
try {
strokeColor = convertColor(drawableProperties.getString("STROKECOLOR"));
} catch (JSONException e) {
}
}
if (drawableProperties.has("STROKESIZE")) {
try {
strokeSize = (int) convertDimenToPixel(drawableProperties.getString("STROKESIZE"));
} catch (JSONException e) {
}
}
gd.setStroke(strokeSize, strokeColor);

}

return gd;
}
default:
break;
}
return v;
}

/**
* create property and parse json
*
* @param jsonObject : json to parse
*/
public DynamicProperty(JSONObject jsonObject) {
super();
try {
name = NAME.valueOf(jsonObject.getString("name").toUpperCase().trim());
} catch (Exception e) {
name = NAME.NO_VALID;
}
try {
type = TYPE.valueOf(jsonObject.getString("type").toUpperCase().trim());
} catch (Exception e) {
type = TYPE.NO_VALID;
}
try {
value = convertValue(jsonObject.get("value"));
} catch (Exception e) {
}
}

public boolean isValid() {
return value != null;
}

/**
* @param clazz
* @param varName
* @return search in clazz of possible variable name (varName) and return its value
*/
public Object getValueInt(Class clazz, String varName) {

java.lang.reflect.Field fieldRequested = null;

try {
fieldRequested = clazz.getField(varName);
if (fieldRequested != null) {
return fieldRequested.get(clazz);
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
return null;
}


/**
* next function just cast value and return the object
**/

public int getValueColor() {
if (type == TYPE.COLOR) {
return Integer.class.cast(value);
}
return -1;
}

public String getValueString() {
return String.class.cast(value);
}

public int getValueInt() {
if (value instanceof Integer) {
return Integer.class.cast(value);
} else if (value instanceof Float) {
return (int) getValueFloat();
} else {
return (int) value;
}
}

public float getValueFloat() {
return Float.class.cast(value);
}

public Boolean getValueBoolean() {
return Boolean.class.cast(value);
}

public Bitmap getValueBitmap() {
return (Bitmap) value;
}

public Drawable getValueBitmapDrawable() {
return new BitmapDrawable(Resources.getSystem(), getValueBitmap());
}

public Drawable getValueGradientDrawable() {
return (Drawable) value;
}

public JSONObject getValueJSON() {
return JSONObject.class.cast(value);
}


int convertColor(String color) {
if (color.startsWith("0x")) {
return (int) Long.parseLong(color.substring(2), 16);
}
return Color.parseColor(color);
}

float convertDimenToPixel(String dimen) {
if (dimen.endsWith("dp")) {
return DynamicHelper.dpToPx(Float.parseFloat(dimen.substring(0, dimen.length() - 2)));
} else if (dimen.endsWith("sp")) {
return DynamicHelper.spToPx(Float.parseFloat(dimen.substring(0, dimen.length() - 2)));
} else if (dimen.endsWith("px")) {
return Integer.parseInt(dimen.substring(0, dimen.length() - 2));
} else if (dimen.endsWith("%")) {
return (int) (Float.parseFloat(dimen.substring(0, dimen.length() - 1)) / 100f * DynamicHelper.deviceWidth());
} else if ("match_parent".equalsIgnoreCase(dimen)) {
return ViewGroup.LayoutParams.MATCH_PARENT;
} else if ("wrap_content".equalsIgnoreCase(dimen)) {
return ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
return Integer.parseInt(dimen);
}
}

}

DynamicView.java

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
package com.example.shenbh.showcarddemo.Dynamic;

import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
* Created by avocarrot on 11/12/2014.
* parse the json as a tree and create View with its dynamicProperties
*/
public class DynamicView {

static int mCurrentId = 13;
static int INTERNAL_TAG_ID = 0x7f020000;

/**
* @param jsonObject : json object
* @param holderClass : class that will be created as an holder and attached as a tag in the View
* @return the view that created
*/
public static View createView(Context context, JSONObject jsonObject, Class holderClass) {
return createView(context, jsonObject, null, holderClass);
}

/**
* @param jsonObject : json object
* @param parent : parent viewGroup
* @param holderClass : class that will be created as an holder and attached as a tag in the View, If contains HashMap ids will replaced with idsMap
* @return the view that created
*/
public static View createView(Context context, JSONObject jsonObject, ViewGroup parent, Class holderClass) {

if (jsonObject == null) {
return null;
}

HashMap<String, Integer> ids = new HashMap<>();

View container = createViewInternal(context, jsonObject, parent, ids);

if (container == null) {
return null;
}

if (container.getTag(INTERNAL_TAG_ID) != null) {
DynamicHelper.applyLayoutProperties(container, (List<DynamicProperty>) container.getTag(INTERNAL_TAG_ID), parent, ids);
}

/* clear tag from properties */
container.setTag(INTERNAL_TAG_ID, null);

if (holderClass != null) {

try {
Object holder = holderClass.getConstructor().newInstance();
//解析View
DynamicHelper.parseDynamicView(holder, container, ids);
container.setTag(holder);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}

}

return container;

}

/**
* @param jsonObject : json object
* @param parent : parent viewGroup
* @return the view that created
*/
public static View createView(Context context, JSONObject jsonObject, ViewGroup parent) {
return createView(context, jsonObject, parent, null);
}

/**
* @param jsonObject : json object
* @return the view that created
*/
public static View createView(Context context, JSONObject jsonObject) {
return createView(context, jsonObject, null, null);
}

/**
* use internal to parse the json as a tree to create View
*
* @param jsonObject : json object
* @param ids : the hashMap where we keep ids as string from json to ids as int in the layout
* @return the view that created
*
*
* 通过反射来构造一个View 对象,而至于是什么View 取决于你在Json文件中获取到的属性,
* 可以是LinearLayout也可以是ReleativeLayout等等。通过递归去遍历Json 文件中的属性,
* 然后生成不同的嵌套的控件。生成原生控件之后,就开始得适配属性值,以及控件位置了。
*/
private static View createViewInternal(Context context, JSONObject jsonObject, ViewGroup parent, HashMap<String, Integer> ids) {

View view = null;

ArrayList<DynamicProperty> properties;

try {
String widget = jsonObject.getString("widget");
if (!widget.contains(".")) {
widget = "android.widget." + widget;
}
//反射构建Android view 的布局对象
Class viewClass = Class.forName(widget);
view = (View) viewClass.getConstructor(Context.class).newInstance(new Object[]{context});
} catch (JSONException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

if (view == null) {
return null;
}

try {
//创建布局参数
ViewGroup.LayoutParams params = DynamicHelper.createLayoutParams(parent);
view.setLayoutParams(params);

properties = new ArrayList<>();
JSONArray jArray = jsonObject.getJSONArray("properties");
if (jArray != null) {
for (int i = 0; i < jArray.length(); i++) {
DynamicProperty p = new DynamicProperty(jArray.getJSONObject(i));
if (p.isValid()) {
properties.add(p);
}
}
}

view.setTag(INTERNAL_TAG_ID, properties);
//解析布局属性
String id = DynamicHelper.applyStyleProperties(context, view, properties);
if (!TextUtils.isEmpty(id)) {
ids.put(id, mCurrentId);
view.setId(mCurrentId);
mCurrentId++;
}

if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;

List<View> views = new ArrayList<>();
JSONArray jViews = jsonObject.optJSONArray("views");
if (jViews != null) {
int count = jViews.length();
for (int i = 0; i < count; i++) {
//递归调用布局
View dynamicChildView = DynamicView.createViewInternal(context, jViews.getJSONObject(i), parent, ids);
if (dynamicChildView != null) {
views.add(dynamicChildView);
viewGroup.addView(dynamicChildView);
}
}
}
for (View v : views) {
//遍历View解析属性
DynamicHelper.applyLayoutProperties(v, (List<DynamicProperty>) v.getTag(INTERNAL_TAG_ID), viewGroup, ids);
v.setTag(INTERNAL_TAG_ID, null);
}
}

} catch (JSONException e) {
e.printStackTrace();
}

return view;

}

}

DynamicViewId.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.shenbh.showcarddemo.Dynamic;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to create the class ViewHolder
*/

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DynamicViewId {
String id() default "";
}

ViewHolder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.shenbh.showcarddemo.Dynamic;

/**
* Created by cx on 2018/6/21.
*/

public class ViewHolder {

public ViewHolder() {

}
}

数据源(demo)

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
{
"widget": "android.widget.RelativeLayout",
"properties": [
{
"name": "background",
"type": "color",
"value": "##e5e5e5"
},
{
"name": "layout_width",
"type": "dimen",
"value": "match_parent"
},
{
"name": "layout_height",
"type": "dimen",
"value": "match_parent"
}
],
"views": [
{
"widget": "android.widget.RelativeLayout",
"properties": [
{
"name": "layout_width",
"type": "dimen",
"value": "362dp"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "background",
"type": "ref",
"value": "shape|corner:8|color:'##ffffff'"
},
{
"name": "layout_marginLeft",
"type": "dimen",
"value": "15dp"
},
{
"name": "layout_marginRight",
"type": "dimen",
"value": "15dp"
},
{
"name": "layout_centerHorizontal",
"type": "boolean",
"value": "true"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "10dp"
}
],
"views": [
{
"widget": "android.widget.ImageView",
"properties": [
{
"name": "id",
"type": "",
"value": "iv_one"
},
{
"name": "layout_width",
"type": "dimen",
"value": "15dp"
},
{
"name": "layout_height",
"type": "dimen",
"value": "15dp"
},
{
"name": "background",
"type": "ref",
"value": "@drawable/logo"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "7dp"
},
{
"name": "layout_marginLeft",
"type": "dimen",
"value": "15dp"
}
]
},
{
"widget": "android.widget.TextView",
"properties": [
{
"name": "id",
"type": "",
"value": ""
},
{
"name": "layout_width",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_marginLeft",
"type": "dimen",
"value": "7dp"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "7dp"
},
{
"name": "text",
"type": "string",
"value": "携程旅行"
},
{
"name": "textSize",
"type": "dimen",
"value": "10sp"
},
{
"name": "layout_toRightOf",
"type": "ref",
"value": "iv_one"
},
{
"name": "textColor",
"type": "color",
"value": "##333333"
}
]
},
{
"widget": "android.widget.ImageView",
"properties": [
{
"name": "layout_width",
"type": "dimen",
"value": "35dp"
},
{
"name": "layout_height",
"type": "dimen",
"value": "35dp"
},
{
"name": "layout_centerHorizontal",
"type": "boolean",
"value": "true"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "37dp"
},
{
"name": "background",
"type": "ref",
"value": "@drawable/plane"
}
]
},
{
"widget": "android.widget.TextView",
"properties": [
{
"name": "id",
"type": "",
"value": "tv_one"
},
{
"name": "layout_width",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "text",
"type": "string",
"value": "深圳"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "60dp"
},
{
"name": "layout_marginLeft",
"type": "dimen",
"value": "34dp"
},
{
"name": "textColor",
"type": "color",
"value": "##333333"
},
{
"name": "textSize",
"type": "dimen",
"value": "20sp"
}
]
},
{
"widget": "android.widget.TextView",
"properties": [
{
"name": "id",
"type": "",
"value": "tv_two"
},
{
"name": "layout_width",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_below",
"type": "ref",
"value": "tv_one"
},
{
"name": "text",
"type": "string",
"value": "去程"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "20dp"
},
{
"name": "layout_marginLeft",
"type": "dimen",
"value": "34dp"
},
{
"name": "textColor",
"type": "color",
"value": "##494949"
},
{
"name": "textSize",
"type": "dimen",
"value": "12sp"
}
]
},
{
"widget": "android.widget.TextView",
"properties": [
{
"name": "layout_width",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_below",
"type": "ref",
"value": "tv_two"
},
{
"name": "text",
"type": "string",
"value": "6月1日(周五)"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "8dp"
},
{
"name": "layout_marginLeft",
"type": "dimen",
"value": "34dp"
},
{
"name": "textColor",
"type": "color",
"value": "##494949"
},
{
"name": "textSize",
"type": "dimen",
"value": "18sp"
}
]
},
{
"widget": "android.widget.TextView",
"properties": [
{
"name": "id",
"type": "",
"value": "tv_Rone"
},
{
"name": "layout_width",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "text",
"type": "string",
"value": "上海"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "60dp"
},
{
"name": "layout_marginRight",
"type": "dimen",
"value": "34dp"
},
{
"name": "layout_alignParentRight",
"type": "boolean",
"value": "true"
},
{
"name": "textColor",
"type": "color",
"value": "##333333"
},
{
"name": "textSize",
"type": "dimen",
"value": "20sp"
}
]
},
{
"widget": "android.widget.TextView",
"properties": [
{
"name": "id",
"type": "",
"value": "tv_Rtwo"
},
{
"name": "layout_width",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_alignParentRight",
"type": "boolean",
"value": "true"
},
{
"name": "layout_below",
"type": "ref",
"value": "tv_one"
},
{
"name": "text",
"type": "string",
"value": "返程"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "20dp"
},
{
"name": "layout_marginRight",
"type": "dimen",
"value": "34dp"
},
{
"name": "textColor",
"type": "color",
"value": "##494949"
},
{
"name": "textSize",
"type": "dimen",
"value": "12sp"
}
]
},
{
"widget": "android.widget.TextView",
"properties": [
{
"name": "id",
"type": "",
"value": "tv_Rthree"
},
{
"name": "layout_width",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "layout_height",
"type": "dimen",
"value": "wrap_content"
},
{
"name": "text",
"type": "string",
"value": "6月6日(周三)"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "8dp"
},
{
"name": "layout_marginRight",
"type": "dimen",
"value": "34dp"
},
{
"name": "layout_below",
"type": "ref",
"value": "tv_two"
},
{
"name": "textColor",
"type": "color",
"value": "##494949"
},
{
"name": "layout_alignParentRight",
"type": "boolean",
"value": "true"
},
{
"name": "textSize",
"type": "dimen",
"value": "18sp"
}
]
},
{
"widget": "android.widget.Button",
"properties": [
{
"name": "id",
"type": "",
"value": "btn_query"
},
{
"name": "layout_width",
"type": "dimen",
"value": "159dp"
},
{
"name": "layout_height",
"type": "dimen",
"value": "40dp"
},
{
"name": "text",
"type": "string",
"value": "查询"
},
{
"name": "layout_marginTop",
"type": "dimen",
"value": "22dp"
},
{
"name": "layout_marginBottom",
"type": "dimen",
"value": "11dp"
},
{
"name": "layout_centerHorizontal",
"type": "boolean",
"value": "true"
},
{
"name": "background",
"type": "click",
"value": "http://www.baidu.com"
},
{
"name": "background",
"type": "ref",
"value": "shape+corners:8+color:'##f5942b'"
},
{
"name": "layout_below",
"type": "ref",
"value": "tv_Rthree"
},
{
"name": "textColor",
"type": "color",
"value": "##ffffff"
},
{
"name": "textSize",
"type": "dimen",
"value": "14sp"
}
]
}
]
}
]
}

动态添加空态图

具体实现步骤

  1. 在xml中 给要替换的layout外面加一层RelaytiveLayout
  2. 在代码中 这个rl移除掉所有的控件
  3. 添加空态图

完整代码

.java文件中

1
2
3
4
5
6
7
8
9
10
private void showEmptyView(){
if (shopkeeperHomeRl != null){
shopkeeperHomeRl.removeAllViews();
View emptyView = LayoutInflater.from(this).inflate(R.layout.empty_shopkeeper_home, null);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
RelativeLayout rl = emptyView.findViewById(R.id.empty_root_rl);
rl.setLayoutParams(lp);
shopkeeperHomeRl.addView(emptyView);
}
}

注意

添加空态图后,空态图显示不全的问题,这时需要给这个空态图重新设置LalyoutParams,即 rl.setLayoutParams(lp);

动态设置布局属性

动态设置margin、宽度、weight

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tv.getLayoutParams();
if (条件) {
//动态设置宽度
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
//动态设置margin
lp.setMargins(SizeUtils.dp2px(15), 0, 0, 0);
//动态设置padding
tv.setPadding(10,0,0,0);
tv.setLayoutParams(lp);
} else {
//动态设置宽度(固定宽度)
lp.width = SizeUtils.dp2px(245);//
//(第三个参数)动态设置weight
tv.setLayoutParams(new LinearLayout.LayoutParams(lp.width, lp.height, 0f));
}

动态设置图片(宽充满屏幕,高与宽为原图比例)

根据配置设置引导页页数

1
2
3
4
//ldy_product.properties
//添加一个变量,用来控制引导页页数
#启动页数量
XDM_OPEN_GUIDE_IMAGE_NUM=3
1
2
3
4
5
6
7
8
9
10
//app的build.gradle中设置这个常量
productFlavors {
xdmLdyDebug {
//...
manifestPlaceholders =
[
//...
properties.getProperty('XDM_OPEN_GUIDE_IMAGE_NUM'),
]
}
1
2
3
4
5
6
//AndroidManifest.xml中
<application>
<meta-data
android:name="OPEN_GUIDE_IMAGE_NUM"
android:value="${OPEN_GUIDE_IMAGE_NUM}"/>
</application>
1
2
3
4
5
6
//工具类中获取这个常量的值,比如在Contants.java中
private static final String OPEN_GUIDE_IMAGE_NUM = "OPEN_GUIDE_IMAGE_NUM";

public static String getOpenGuideImageNum() {
return MetaDataUtils.getMetaDataFromApp(App.getContext(), OPEN_GUIDE_IMAGE_NUM);
}
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
//使用这个常量值,进行设置引导页页数
public class GuideActivity extends LdyBaseActivity {
private ViewPager viewPager;
private Button goBtn;
private LinearLayout indicatorLl;

private List<View> datas = new ArrayList<View>();
LayoutInflater inflater;

private int currentPosition;
public static final int SNAP_VELOCITY = 600;

@Override
protected int setLayoutResId() {
return R.layout.activity_guide;
}

@Override
public void setImmersion() {
getImmersion().setImmersionTransparent();
}

@Override
protected void onCreate() {
setImmersion();
inflater = LayoutInflater.from(this);

viewPager = (ViewPager) findViewById(R.id.view_pager);
indicatorLl = (LinearLayout) findViewById(R.id.activity_guide_indicator_ll);

//获取配置中设置的引导页页数数值
int num = BaseParser.parseInt(4,Constants.getOpenGuideImageNum());

View guideItemView = inflater.inflate(R.layout.item_guide, null);
ImageView endIv = (ImageView) guideItemView.findViewById(R.id.iv);
goBtn = (Button) guideItemView.findViewById(R.id.btn_go);
goBtn.setOnClickListener(onClickListener);

findViewById(R.id.activity_guide_skip_iv).setOnClickListener(onClickListener);

//使用屏幕宽高来设置图片,使其充满屏幕
DisplayMetrics metrics = getResources().getDisplayMetrics();
int screenHeight = metrics.heightPixels;
int screenWidth = metrics.widthPixels;
for (int i = 1; i <= num; i++) {
Bitmap bm = BitmapFactory.decodeResource(getResources(), SystemUtil.getDrawableIdByName(this, "ic_guide_" + i));
Drawable drawable = new BitmapDrawable(getResources(), ImageCompressor.scaleBitmap(bm, screenWidth, screenHeight));
final ImageView imageView;
if (i != num) {
imageView = new ImageView(this);
} else {
imageView = endIv;
}
imageView.setBackgroundDrawable(drawable);
bm.recycle();
if (i != num) {
datas.add(imageView);
} else {
datas.add(guideItemView);
}

//设置指示器
ImageView indicatorIv = new ImageView(this);
if (i == 1) {
U1CityOutdateUtils.setBackground(indicatorIv,U1CityOutdateUtils.getDrawable(R.drawable.ic_guide_image_indicator_selected));
} else {
U1CityOutdateUtils.setBackground(indicatorIv,U1CityOutdateUtils.getDrawable(R.drawable.ic_guide_image_indicator));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(DimensUtil.dpToPixels(this, 6), 0, 0, 0);
indicatorIv.setLayoutParams(params);
}

indicatorLl.addView(indicatorIv);
}

viewPager.setAdapter(new GuidePagerAdapter(datas));
viewPager.addOnPageChangeListener(onPageChangeListener);
viewPager.setOnTouchListener(onTouchListener);
}


private OnClickListener onClickListener = new OnClickListener() {

@Override
public void onClick(View v) {
startActivity(new Intent(GuideActivity.this, LoginActivity.class));
finish();
}
};

private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {

@Override
public void onPageSelected(int position) {
Debug.d("GuideActivity", "onPageSelected");

if (indicatorLl.getChildAt(position) != null) {
U1CityOutdateUtils.setBackground(indicatorLl.getChildAt(position),U1CityOutdateUtils.getDrawable(R.drawable.ic_guide_image_indicator_selected));
}

if (indicatorLl.getChildAt(currentPosition) != null) {
U1CityOutdateUtils.setBackground(indicatorLl.getChildAt(currentPosition),U1CityOutdateUtils.getDrawable(R.drawable.ic_guide_image_indicator));
}

currentPosition = position;
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (position == datas.size() - 1) {
if (goBtn.getVisibility() == View.GONE) {
goBtn.setVisibility(View.VISIBLE);
}
}
}

@Override
public void onPageScrollStateChanged(int state) {

}
};

private OnTouchListener onTouchListener = new OnTouchListener() {
private VelocityTracker mVelocityTracker = null;

@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getActionMasked();

switch (action) {
case MotionEvent.ACTION_DOWN:
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
mVelocityTracker.addMovement(event);
break;

case MotionEvent.ACTION_MOVE:
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}

mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000);
break;

case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;

if (mVelocityTracker != null) {
velocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) velocityTracker.getXVelocity();

mVelocityTracker.recycle();
mVelocityTracker = null;

if (velocityX > SNAP_VELOCITY) {
} else if (velocityX < -SNAP_VELOCITY) {// 跳到下一个Activity
if (currentPosition == datas.size() - 1) {
startActivity(new Intent(GuideActivity.this, LoginActivity.class));
finish();
}
} else {
}
}

break;

case MotionEvent.ACTION_CANCEL:
mVelocityTracker.recycle();
break;
}
return false;
}
};

private class GuidePagerAdapter extends PagerAdapter {
private List<View> imageViews;

public GuidePagerAdapter(List<View> imageViews) {
super();
this.imageViews = imageViews;
}

@Override
public int getCount() {
return imageViews.size();
}

@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}

@Override
public Object instantiateItem(ViewGroup arg0, int arg1) {
arg0.addView(imageViews.get(arg1), ViewPager.LayoutParams.MATCH_PARENT, ViewPager.LayoutParams.MATCH_PARENT);
return imageViews.get(arg1);
}

@Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(imageViews.get(position));
}

}

动态设置图标

可以动态设置上下左右的图标

方法一:

1
2
3
4
5
//根据需求填入相应参数,显示哪里填哪里。
viewHolder.mText.setCompoundDrawablesWithIntrinsicBounds(null, null, context.getResources().getDrawable(R.drawable.drop_down_checked), null);

//当然,需要取消显示的时候可以做如下设置.
viewHolder.mText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);

Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the text. Use null if you do not want a Drawable there. The Drawables’ bounds will be set to their intrinsic bounds.
意思大概就是:可以在上、下、左、右设置图标,如果不想在某个地方显示,则设置为null。图标的宽高将会设置为固有宽高,既自动通过getIntrinsicWidth和getIntrinsicHeight获取。

方法二:

1
2
3
Drawable myImage = getResources().getDrawable(R.drawable.home);
myImage.setBounds(1, 1, 100, 100);
button.setCompoundDrawables(null, myImage, null, null);

Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the text. Use null if you do not want a Drawable there. The Drawables must already have had setBounds(Rect) called.
意思大概就是:可以在上、下、左、右设置图标,如果不想在某个地方显示,则设置为null。但是Drawable必须已经setBounds(Rect)。意思是你要添加的资源必须已经设置过初始位置、宽和高等信息。

又一城根据接口数据动态展示

作用:数据动态,页面展示样式是无序的(根据数据中顺序展示)

接口数据

接口返回数据模型

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
{
...,
"homeDataList":[
{
"modularId":305, //模块id
"modularType":"0", //模块类型。0-广告类型;1-商品模块
"modularStyle":"0",//模块样式
"modularHeight":"240",//模块高度(后台装扮时选择自定义像素决定的)
"modularWidth":"0",
...
"modularDataList":[
{
"advertisementId":5071,
...
"linkId":"17",
...
"linkValue":"1",
"storeId":8
}
]
},
{
...,
"modularDataList":[
{
...
}
]
}
],
...
}

homeDataList的一个item就是一个装扮模块。装扮模块显示成怎么样跟modulartId(模块id)、modularType(模块类型)、modularStyle(模块样式)决定。先根据modularType分出类型,在某一类型中再根据modularStyle决定app要显示什么样式

代码结构

MainActivity内viewPager的某一项为CustomPageFragment

CustomPageFragment内Viewpager的某一项为MultHomePageFragment

MultHomePageFragment内容为com.scwang.smartrefresh.layout.SmartRefreshLayout嵌套RecyclerView,其中Rc内容就是动态装扮的(本文讲的内容就是针对Rc的item动态显示某样式)

模块类型常量类 CustomModularType.java 管理各种模块类型

在BaseDataBean、BaseRecyclerAdapter、CustomDataManager中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CustomModularType {
...
/**
* 未知模块 展示暂没有处理的style样式模块
*/
public final static int DEFAULT_UN_KNOW = 0;

/**
* 广告模块 type=0 style = 0
*/
public final static int SINGLE = 100;
/**
* 广告模块 type=0 style = 1(折叠轮播)
*/
public final static int BANNERVIEWPAGER = 101;
...

/**
* 商品模块 type=1 style =0 type=1 style=3 共用
*/
public final static int HORIZONTAL = 3;
...
}

BaseDataBean 基类Bean限制各个样式的bean的数据模型

作用:

  1. 所有装扮模块bean的基类,用来限制各个样式的bean的数据模型

  2. 根据不同modularType和modularStyle返回对应的CustomModularType类型

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
public class BaseDataBean<T> implements Serializable, MultiItemEntity {
private String modularType;
private String modularStyle;
private String modularHeight;//模块高度:(广告模块)
private String modularWidth;//模块宽度
private T data;

省略set/get方法

@Override
public int getItemType() {//根据不同modularType和modularStyle返回对应的CustomModularType类型
switch (BaseParser.parseInt(modularType)) {

/*广告模块*/
case CustomModularType.TEMPLATE_BANNER:
switch (BaseParser.parseInt(modularStyle)) {
case 0:
return CustomModularType.SINGLE;
case 1:
return CustomModularType.BANNERVIEWPAGER;
...
default:
return CustomModularType.DEFAULT_UN_KNOW;
}

/*商品模块*/
case CustomModularType.TEMPLATE_GOODS:
switch (modularStyle) {
case "0":
return CustomModularType.HORIZONTAL;
...
default:
return CustomModularType.DEFAULT_UN_KNOW;
}
...
default:
return CustomModularType.DEFAULT_UN_KNOW;
}
}
}

BaseRecyclerAdapter 关联layout、各自的ViewHolder

作用:用于根据不同modularType关联不同的layout,使用不同的ViewHolder。

在MultHomePageFragment的Rc中使用

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
...
import com.chad.library.adapter.base.BaseMultiItemQuickAdapter;
...


/**用于根据不同modularType关联不同的layout,显示不同的ViewHolder
*/
public class BaseRecyclerAdapter extends BaseMultiItemQuickAdapter<BaseDataBean, CustomViewHolder> {
/**
* 保存下来用于反注册
*/
private GuiderInfoHolder mGuiderInfoHolder;
private GuiderItemHolder mGuiderItemHolder;

/**
* Same as QuickAdapter#QuickAdapter(Context,int) but with
* some initialization data.
*
* @param data A new list is created out of this one to avoid mutable list
*/
public BaseRecyclerAdapter(List<? extends BaseDataBean> data, Context context) {
super((List<BaseDataBean>) data);
this.context = context;
//设置不同类型
//广告 单图 banner
addItemType(CustomModularType.SINGLE, R.layout.item_singleimg);
//广告 两张 banner
addItemType(CustomModularType.DIVIDE, R.layout.item_divide);
...
}
...

@Override
protected void convert(CustomViewHolder holder, BaseDataBean item) {
switch (holder.getItemViewType()) {
case CustomModularType.SINGLE:
holder.setSingleView(context, item, holder.getAdapterPosition());
break;
case CustomModularType.DIVIDE:
holder.divideImageHolder(context, item, 2);
break;
...

default:
holder.unKnowView((Activity) context);
break;
}
}

@Override
public void onViewAttachedToWindow(CustomViewHolder holder) {
// 保存导购相关ViewHolder,用于反注册
if (CustomModularType.GUIDER == holder.getItemViewType()) {
this.mGuiderItemHolder = holder.getGuiderItemHolder();
}

if (CustomModularType.GUIDER_INFO == holder.getItemViewType()) {
this.mGuiderInfoHolder = holder.getGuiderInfoHolder();
}

super.onViewAttachedToWindow(holder);
}

@Override
public void onViewDetachedFromWindow(CustomViewHolder holder) {
// 2018/7/9 避免个别视频模块在restart之后,因这个方法又给pause,所以增加flag过滤
if (CustomModularType.VIDEO_TYPE.DEFAULT == holder.getItemViewType() && (!holder.getmVideoViewHolder().isSwitch2FullScreenPlay())) {
VideoModularPlayCtrlEvent videoModularPlayCtrlEvent = new VideoModularPlayCtrlEvent(holder.getmVideoViewHolder(), false);
EventBus.getDefault().post(videoModularPlayCtrlEvent);
}
super.onViewDetachedFromWindow(holder);
}

public GuiderInfoHolder getGuiderInfoHolder() {
return mGuiderInfoHolder;
}

public GuiderItemHolder getGuiderItemHolder() {
return mGuiderItemHolder;
}
}

CustomViewHolder 管理不同的ViewHolder

管理不同的ViewHolder,供BaseRecyclerAdapter调用

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
...
import com.chad.library.adapter.base.BaseViewHolder;


/**
* 管理不同的ViewHolder
*/
public class CustomViewHolder extends BaseViewHolder {
private Activity activity;

private UnknowTypeHolder unknowTypeHolder;
private SingleImageHolder singleImageHolder;

private SparseArray<BaseDataBean> sp = new SparseArray<>();
...

public CustomViewHolder(View view) {
super(view);
}

/**
* 单张banner
*/
public void setSingleView(final Context context, final BaseDataBean<BannarAd> bean, int position) {

if (singleImageHolder == null) {
singleImageHolder = new SingleImageHolder(context, itemView);
}
singleImageHolder.setPosition(context, bean, position);
}

/**
* 未知模块
*/
public void unKnowView(Activity activity) {
this.activity = activity;
if (unknowTypeHolder == null) {
unknowTypeHolder = new UnknowTypeHolder(itemView, activity);
}

}

...

}

CustomDataManager 解析数据

作用:把接口返回回来的各个模块数据解析成各自的Bean

在MultHomePageFragment中使用

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
...
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
...

import static com.alibaba.fastjson.JSON.parseArray;
import static com.alibaba.fastjson.JSON.parseObject;

/**
* 自定义模版管理类
* Created by liu.jiapeng on 2017/3/30.
*/
public class CustomDataManager {

private CustomDataCallback callback;

private List<BaseDataBean> list = new ArrayList<>();

public CustomDataManager(Context context, CustomDataCallback callback) {
this.callback = callback;
}

public CustomDataManager(CustomDataCallback callback) {
this.callback = callback;
}

/**
* 解析模块数据
*
* @param jsonArrayData 接口返回模块json数组数据
* 首页传 homeDataList 数据
* 发现页传 foundDataList 数据
* @param pageType 0 首页 1 发现
*/
public void parseViewData(String jsonArrayData, String pageType) {
callback.requestSuccess(parseData(jsonArrayData));
}

/**
* 新框架中带是否刷新boolean的回调(删除无用的pageType)
* @param pIsLoadByRefresh 是否是下拉触发的数据加载
* @param jsonArrayData 接口返回的json数据
*/
public void parseViewData(boolean pIsLoadByRefresh, String jsonArrayData) {
callback.requestSuccess(pIsLoadByRefresh, parseData(jsonArrayData));
}

/**
* 解析View数据的主要过程
* @param jsonArrayData json数据
*/
private List<BaseDataBean> parseData(String jsonArrayData) {
if (StringUtils.isEmpty(jsonArrayData)) {
callback.requestError();
return null;
}

list.clear();
JSONArray array = parseArray(jsonArrayData);
if (array.size() > 0) {
for (int i = 0; i < array.size(); i++) {
JSONObject jsonObject = array.getJSONObject(i);
int modularType = jsonObject.getIntValue("modularType");
switch (modularType) { // 模块的类型
case CustomModularType.TEMPLATE_BANNER: // 自定义banner模块
CustomModularBean modularModel =
new FastJsonFactory().getJson().fromJson(jsonObject.toString(), CustomModularBean.class);
BaseDataBean<BannarAd> bannerAdBaseDataBean = new BaseDataBean<>();
bannerAdBaseDataBean.setModularType(String.valueOf(modularModel.getModularType()));
bannerAdBaseDataBean.setModularStyle(String.valueOf(modularModel.getModularStyle()));
bannerAdBaseDataBean.setModularHeight(String.valueOf(modularModel.getModularHeight()));
bannerAdBaseDataBean.setModularWidth(String.valueOf(modularModel.getModularWidth()));
BannarAd bannarAd = new BannarAd();
List<BannerAdBean> adModelList = JsonAnalysis.getInstance()
.listFromJson(modularModel.getModularDataList(), BannerAdBean.class);
bannarAd.setModularDateList(adModelList);
bannarAd.setModularSubTitle(modularModel.getModularSubTitle());
bannarAd.setModularTitle(modularModel.getModularTitle());
bannarAd.setIsShowSpace(modularModel.getIsShowSpace());
bannarAd.setRow(modularModel.getRow());
bannarAd.setCol(modularModel.getCol());
bannerAdBaseDataBean.setData(bannarAd);
list.add(bannerAdBaseDataBean);
break;

...
default:
CustomModularBean modular =
JsonAnalysis.getInstance().fromJson(jsonObject.toString(), CustomModularBean.class);
BaseDataBean defaultBean = new BaseDataBean();
defaultBean.setModularType(modular.getModularType() + "");
defaultBean.setModularStyle(modular.getModularStyle() + "");
list.add(defaultBean);
break;
}
}
}
List<BaseDataBean> lists = new ArrayList<>();
lists.addAll(list);
return lists;
}
}

MultHomePageFragment

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
...

/**
* 二级多首页的子页面
*/
public class MultHomePageFragment extends LdyBaseMvpFragment<MultHomePageContract.View, MultHomePagePresenter>
implements MultHomePageContract.View, CustomDataCallback {
/**
* 页面布局文件
*/
@LayoutRes
private static final int FRAGMENT_LAYOUT_RES_ID = R.layout.fragment_mult_home_page;
/**
* 当子模块个数超过6个的时候,会在底部显示footer--原有逻辑未改动
*/
private static final int SHOW_FOOTER_MIN_MODULAR_NUMBER = 6;
/**
* 每分钟的毫秒数
*/
private static final long MILLISECONDS_OF_1_MINUTE = 1000L * 60;
/**
* 返回顶部的ImageView的throttleTime(单位:毫秒)
*/
private static final long THROTTLE_TIME = 1000L;
/**
* 显示缓存之后,延迟多少时间刷新数据(单位:毫秒)
*/
private static final long DELAY_REFRESH_TIME_AFTER_CACHE = 500L;

@Bind(R.id.home_mult_page_srl)
SmartRefreshLayout mSmartRefreshLayout;
@Bind(R.id.home_mult_page_rv)
RecyclerView mRvMain;
@Bind(R.id.home_mult_page_fab_iv)
ImageView mIvScroll2Top;

private String mTemplateId;
private int mPageType;
private List<? extends BaseDataBean> mChildModularDataList;
/**
* 自定义数据处理管理类
*/
private CustomDataManager mCustomDataManager;
private BaseRecyclerAdapter mBaseRecyclerAdapter;

private double mLongitude;
private double mLatitude;
/**
* 刷新数据是否在第一次请求成功后设定分钟内操作默认10分钟
*/
private int mHomeRefreshMin;

private String mHomeCache;
private String mDistance;
/**
* 最新的更新数据时间
*/
private Date mLastRequestSuccessDate;
/**
* 子模块的总数--用于判断是否显示footer
*
* @see #SHOW_FOOTER_MIN_MODULAR_NUMBER
*/
private int mTotal;
private View mFooterView;

/**
* 构建子页面的入口
*
* @param pTemplateId 模板id(外部传入,需要在{@link #onCreate(Bundle)}方法中进行获取,不是在{@link #onCreateView(LayoutInflater, ViewGroup, Bundle)})
* @return 子Fragment页面实例
*/
public static MultHomePageFragment newInstance(String pTemplateId, int pageType) {
Bundle args = new Bundle();
args.putString(INTENT_TEMPLATE_ID_KEY, pTemplateId);
args.putInt(INTENT_PAGE_TYPE_KEY, pageType);
MultHomePageFragment mainFragment = new MultHomePageFragment();
mainFragment.setArguments(args);
return mainFragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: 2018/9/28 这个可以看下官方BlankFragment模板的实现方式会不会更好
try {
mTemplateId = getArguments().getString(INTENT_TEMPLATE_ID_KEY);
mPageType = getArguments().getInt(INTENT_PAGE_TYPE_KEY, 0);
} catch (Exception e) {
e.printStackTrace();
}
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// 原有逻辑,在此处初始化
mCustomDataManager = new CustomDataManager(this);
return super.onCreateView(inflater, container, savedInstanceState);
}

@NonNull
@Override
public MultHomePagePresenter createPresenter() {
return new MultHomePagePresenter(getContext());
}

@Override
protected void onViewCreatedMvp() {
initParams();
initView();
}

@Override
public void onResume() {
super.onResume();
if (mIsFirstVisite) {//页面第一次加载时候请求数据
// TODO: 2018/1/13 加载数据啊
lazyLoadData();
mIsFirstVisite = false;
}
}

private void initView() {
initSrl();
initRv();
initScrollTopView();
}

private void initScrollTopView() {
// 2018/8/7 点击回到顶部
ViewHelper.showScrollTopArrowForRv(mRvMain, mIvScroll2Top);
RxView.clicks(mIvScroll2Top).throttleFirst(THROTTLE_TIME, TimeUnit.MILLISECONDS).subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
// 不带类似上拉动画直接回到顶部
mRvMain.scrollToPosition(0);
mIvScroll2Top.setVisibility(View.GONE);
}
});
}

/**
* 初始化RecyclerView
*/
private void initRv() {
// 这里manager的实现方式与之前的PullToRefreshRecycleViewEx略有不同(少了一些设置,具体可看代码),后续看是否有隐藏的作用
LinearLayoutManager linearLayoutManager =
new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
mRvMain.setLayoutManager(linearLayoutManager);
mBaseRecyclerAdapter = new BaseRecyclerAdapter(mChildModularDataList, getContext());
mBaseRecyclerAdapter.setPageType(BaseRecyclerAdapter.HOMEPAGE);
// 加载更多
mBaseRecyclerAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
loadChildModularData(false);
}
}, mRvMain);
// footerView的设置在加载数据之后处理

mRvMain.setAdapter(mBaseRecyclerAdapter);
// 设置滑动监听--隐藏弹出来的地址(原有逻辑)
mRvMain.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:
if (Constants.mainPageAddressShow) {
EventPostCenter.getInstance().mainPageScroll();
}
break;
default:
break;
}
}
});
}

/**
* 初始化刷新控件
*/
private void initSrl() {
// 不设置EmptyView。虽然后台限制了不会传回0模块的,但是有可能数据有问题(例如商品已下架),会导致子模块不显示。
mSmartRefreshLayout.setEnableHeaderTranslationContent(false);
mSmartRefreshLayout.setDisableContentWhenRefresh(true);
// 下拉刷新
mSmartRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(RefreshLayout refreshlayout) {
loadChildModularData(true);
}
});
}

/**
* 获取数据的主入口
*
* @param pIsLoadByRefresh 是否由下拉触发的加载数据
*/
private void loadChildModularData(boolean pIsLoadByRefresh) {
// 2018/7/8 增加刷新停止播放视频
VideoHelper.stopVideo();
// 每次刷新数据,如果footerView已经存在都先隐藏
if (mFooterView != null) {
mFooterView.setVisibility(View.GONE);
}
// 如果网络异常直接提示,跳过后面的逻辑
if (!NetUtil.isNetworkConnected(getActivity())) {
dismissRequestLoading();
ToastUtil.showNotNetToast(getActivity());
return;
}

// 正常的刷新数据操作
// 2018/8/7 调用P层获取数据
if (pIsLoadByRefresh) {
showRequestLoading();
getCurLatestLocation();
}

if (Constants.cust != null) {
// 如果是下拉而且还在缓存期间,则不进行刷新数据
if (pIsLoadByRefresh && isInCacheTime()) {
// 手动关闭下拉的刷新圆圈--如果是有获取数据,则会在回调中进行关闭的处理
dismissRequestLoading();
mSmartRefreshLayout.setEnableRefresh(true);
mSmartRefreshLayout.finishRefresh();
return;
}
getPresenter().getMultHomeChildModularData(pIsLoadByRefresh, mTemplateId, mLongitude, mLatitude, mPageType);
}

}

/**
* 查看当前的刷新是否还在缓存时间内
*
* @return <li>true:在缓存时间内,无需重新拉去新数据</li><li>false:已过缓存时间,需要重新拉去新的首页数据</li>
*/
private boolean isInCacheTime() {
if (mLastRequestSuccessDate == null) {
return false;
}

Date now = new Date();
long lastTime = mLastRequestSuccessDate.getTime();
long nowTime = now.getTime();

return (nowTime - lastTime) < MILLISECONDS_OF_1_MINUTE * mHomeRefreshMin;
}

/**
* 初始化一些基本的参数
*/
private void initParams() {
// 经纬度
mLongitude = App.getContext().customerLng;
mLatitude = App.getContext().customerLat;
// 首页数据刷新间隔时间(单位分钟)
mHomeRefreshMin = SysHelper.homeRefreshMins(getContext());
// 初始化处理模块数据的一下对象
mChildModularDataList = new ArrayList<>();
}

@Override
protected int setLayoutResId() {
return FRAGMENT_LAYOUT_RES_ID;
}

@Override
protected void lazyLoadData() {
// 这里不用smartRefreshLayout的进度圈是因为显示的时机有点滞后
showRequestLoading();
getCurLatestLocation();
checkHasNoReadCouponOrNot();
getChildModularData();
//add by shenbh on 2018/11/23
checkHasAdvertisementInfo();
}

/**
* 获取该模板id下各个模块的数据<br></br>
* <li>1.先从缓存获取显示</li>
* <li>2.如果网络异常,则显示提示;否则延迟500L毫秒刷新数据</li>
*/
private void getChildModularData() {
// 先获取缓存数据
mHomeCache = SysHelper.getNewHomeCache(mTemplateId);
// 网络异常直接提示网络异常
if (!NetUtil.isNetworkConnected(getActivity())) {
ToastUtil.showNotNetToast(getActivity());
return;
}

// 如果有缓存,先按缓存显示,而后再去获取新的数据进行刷新(原有的业务逻辑)
parseCache();
loadLatestDataDelay();
}

private void loadLatestDataDelay() {
// 2018/8/7 Rx替换Handler
Observable.timer(DELAY_REFRESH_TIME_AFTER_CACHE, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Long>() {
@Override
public void call(Long aLong) {
loadChildModularData(true);
}
});
}

/**
* 按缓存解析数据展示
*/
private void parseCache() {
// 加非空过滤
if (!TextUtils.isEmpty(mHomeCache)) {
try {
JSONObject jsonObject = new JSONObject(mHomeCache);
String data = jsonObject.optString("Result");
JSONObject result = new JSONObject(data);
mDistance = result.optString("distance");
App.getContext().distance = mDistance;
mTotal = result.optInt("total");
String listData = result.optString("homeDataList");
mCustomDataManager.parseViewData(true, listData);
} catch (JSONException e) {
e.printStackTrace();
}

}
}

@Override
public void onDestroyView() {
super.onDestroyView();
if (mBaseRecyclerAdapter != null) {
mBaseRecyclerAdapter.clearTimer();
}
}

// 解析各个子模块的数据回调
@Override
public void requestSuccess(boolean pIsRefresh, List<BaseDataBean> beanList) {
if (beanList == null) {
return;
}
// 根据是否是下拉,判断是新增还是重新设置数据
if (pIsRefresh) {
mBaseRecyclerAdapter.setNewData(beanList);
} else {
mBaseRecyclerAdapter.addData(beanList);
}

if (beanList.size() != 0 && mBaseRecyclerAdapter.getData().size() < mTotal) {
checkLoadMore(pIsRefresh, mBaseRecyclerAdapter, mTotal, getPresenter().getPageSize());
} else {
// 2018/8/7 这里显示footer
addFootView();
}
}

@Override
public void requestSuccess(List<BaseDataBean> beanList) {

}

@Override
public void requestError() {
U1CityLog.d("parse error");
}

// 获取该子页面的数据回调

@Override
public void getMultHomeChildModularDataSuccess(boolean pIsLoadByRefresh, BaseAnalysis pAnalysis) {
dismissRequestLoading();
mSmartRefreshLayout.setEnableRefresh(true);
mSmartRefreshLayout.finishRefresh();

if (pAnalysis == null) {
return;
}
// 如果是下拉刷新数据,则进行缓存,同时更新请求的最新时间
if (pIsLoadByRefresh) {
SysHelper.cacheNewHomeData(pAnalysis.getJson().toString(), mTemplateId);
mLastRequestSuccessDate = new Date();
}
// 解析数据
try {
mTotal = pAnalysis.getIntFromResult("total");
mDistance = pAnalysis.getStringFromResult("distance");
App.getContext().distance = mDistance;
String homeDataList = pAnalysis.getStringFromResult("homeDataList");
mCustomDataManager.parseViewData(pIsLoadByRefresh, homeDataList);
} catch (JSONException e) {
e.printStackTrace();
}
}

@Override
public void getMultHomeChildModularDataFail() {
dismissRequestLoading();
mSmartRefreshLayout.setEnableRefresh(true);
mSmartRefreshLayout.finishRefresh();
}

// footerView 相关
/**
* 原有的实现方式--后续可以看下有什么比较正规的实现方式
*/
public void addFootView() {
// FIXME: 2018/9/27 total并不能标识会显示这么多个,例如服务模块,可能有些门店就没有
if (mTotal > SHOW_FOOTER_MIN_MODULAR_NUMBER) {
if (mFooterView == null) {
mFooterView = getFooterView();
} else {
mFooterView.setVisibility(View.VISIBLE);
}
if (mBaseRecyclerAdapter.getFooterLayoutCount() == 0) {
mBaseRecyclerAdapter.addFooterView(mFooterView, 0);
}
}
mBaseRecyclerAdapter.loadMoreEnd(true);
}

/**
* 获取底部的footerView
*
* @return 返回footerView
*/
private View getFooterView() {
View footerView = LayoutInflater.from(getContext()).inflate(R.layout.layout_foot_newhome, null);
footerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction(StringConstantUtils.ACTION_TO_GOODS_FRAGMENT);
getActivity().sendBroadcast(intent);
}
});
return footerView;
}
}

布局文件

不同样式对应的adapter的item的布局文件

item_sigleimg.xml

1
2
3
4
5
6
7
8
9
10
11
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

item_divide.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recy"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

MultHomePageFragment的布局文件

fragment_mult_home_page.xml

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
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">

<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/home_mult_page_srl"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
android:id="@+id/home_mult_page_rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.scwang.smartrefresh.layout.SmartRefreshLayout>

<!--可以用MD中的FAB,但是图片有问题(有空白)-->
<ImageView
android:visibility="gone"
tools:visibility="visible"
android:id="@+id/home_mult_page_fab_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_scroll_top"
android:layout_marginEnd="10dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_marginBottom="20dp" />
</RelativeLayout>

动态更改桌面图标(app logo)

效果图

动态更改桌面图标效果图

具体方案

  1. 图标更换:在 AndroidManifest 设置应用入口Activity的别名,然后通过 setComponentEnabledSetting 动态启用或禁用别名进行图标切换。
  2. 控制图标显示:冷启动App时,调用接口判断是否需要切换icon
  3. 触发时机:监听App前后台切换,当App处于后台时切换图标,使得用户无感知。

代码实现

  1. AndroidManifest.xml中给入口Activity设置activity-alias

    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
    <application
    android:name=".MyApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/Theme.SwitchIcon">

    <!-- 原MainActivity -->
    <activity android:name=".MainActivity" />

    <!-- 固定设置一个默认的别名,用来替代原MainActivity -->
    <activity-alias
    android:name=".DefaultAliasActivity"
    android:enabled="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:targetActivity=".MainActivity">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity-alias>


    <!-- 别名1,特定活动需要的图标如:双11,国庆节等 -->
    <activity-alias
    android:name=".Alias1Activity"
    android:enabled="false"
    android:icon="@mipmap/ic_launcher_show"
    android:label="@string/app_name"
    android:targetActivity=".MainActivity">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity-alias>

    </application>

    activity-alias标签中的属性如下

    标签 作用
    android:name 别名,命名规则同Actively
    android:enabled 是否启用别名,这里的主要作用的控制显示应用图标
    android:icon 应用图标
    android:label 应用名
    android:targetActivity 必须指向原入口Activity
  2. MainActivity中,通过启用或禁用别名进行图标切换

    实现ForegroundCallbacks.Listener对App进行监听,当处于后台判断是否切换应用图标

    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
    public class MainActivity extends AppCompatActivity implements ForegroundCallbacks.Listener {

    private int position = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //添加app前后台监听
    ForegroundCallbacks.get(this).addListener(this);

    findViewById(R.id.tv_default).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    position = 0;
    }
    });

    findViewById(R.id.tv_alias1).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    position = 1;
    }
    });

    }

    @Override
    protected void onDestroy() {
    // 移除app前后台监听
    ForegroundCallbacks.get(this).removeListener(this);
    super.onDestroy();
    }

    @Override
    public void onForeground() {

    }

    @Override
    public void onBackground() {
    //根据具体业务需求设置切换条件,我公司采用接口控制icon切换
    if (position == 0) {
    setDefaultAlias();
    } else {
    setAlias1();
    }
    }


    /**
    * 设置默认的别名为启动入口
    */
    public void setDefaultAlias() {
    PackageManager packageManager = getPackageManager();

    ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");
    packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);

    ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");
    packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
    }

    /**
    * 设置别名1为启动入口
    */
    public void setAlias1() {
    PackageManager packageManager = getPackageManager();

    ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity");
    packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);

    ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity");
    packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
    }
    }
  3. ForegroundCallbacks监听App前后台切换

    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
    /**
    * 监听App前后台切换
    */
    public class ForegroundCallbacks implements Application.ActivityLifecycleCallbacks {
    public static final long CHECK_DELAY = 500;
    public static final String TAG = ForegroundCallbacks.class.getName();

    public interface Listener {
    void onForeground();

    void onBackground();
    }

    private static ForegroundCallbacks instance;
    private boolean foreground = false, paused = true;
    private Handler handler = new Handler();
    private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
    private Runnable check;

    public static ForegroundCallbacks init(Application application) {
    if (instance == null) {
    instance = new ForegroundCallbacks();
    application.registerActivityLifecycleCallbacks(instance);
    }
    return instance;
    }

    public static ForegroundCallbacks get(Application application) {
    if (instance == null) {
    init(application);
    }
    return instance;
    }

    public static ForegroundCallbacks get(Context ctx) {
    if (instance == null) {
    Context appCtx = ctx.getApplicationContext();
    if (appCtx instanceof Application) {
    init((Application) appCtx);
    }
    throw new IllegalStateException(
    "Foreground is not initialised and " +
    "cannot obtain the Application object");
    }
    return instance;
    }

    public static ForegroundCallbacks get() {
    if (instance == null) {
    throw new IllegalStateException(
    "Foreground is not initialised - invoke " +
    "at least once with parameterised init/get");
    }
    return instance;
    }

    public boolean isForeground() {
    return foreground;
    }

    public boolean isBackground() {
    return !foreground;
    }

    public void addListener(Listener listener) {
    listeners.add(listener);
    }

    public void removeListener(Listener listener) {
    listeners.remove(listener);
    }

    @Override
    public void onActivityResumed(Activity activity) {
    paused = false;
    boolean wasBackground = !foreground;
    foreground = true;
    if (check != null)
    handler.removeCallbacks(check);
    if (wasBackground) {
    Log.d(TAG, "went foreground");

    for (Listener l : listeners) {
    try {
    l.onForeground();


    } catch (Exception exc) {
    Log.d(TAG, "Listener threw exception!:" + exc.toString());
    }
    }
    } else {
    Log.d(TAG, "still foreground");
    }
    }

    @Override
    public void onActivityPaused(Activity activity) {
    paused = true;
    if (check != null)
    handler.removeCallbacks(check);
    handler.postDelayed(check = new Runnable() {
    @Override
    public void run() {
    if (foreground && paused) {
    foreground = false;
    Log.d(TAG, "went background");
    for (Listener l : listeners) {
    try {
    l.onBackground();
    } catch (Exception exc) {
    Log.d(TAG, "Listener threw exception!:" + exc.toString());
    }
    }
    } else {
    Log.d(TAG, "still foreground");
    }
    }
    }, CHECK_DELAY);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }
    }

    需要在Application中调用ForegroundCallbacks.init(this)进行初始化。

具体缺陷

  1. 切换icon会关闭应用进程,不是崩溃所以不会上报bugly。

  2. 切换icon需要时间,部分华为机型要10s左右,之后能正常打开。

  3. 切换icon过程中,部分机型点击图标无法打开应用,提示应用未安装。

    2021.11.15更新 魅族机型 16S 不能动态切换icon

Demo的github地址