动态加载网络数据的布局
实现原理
把json字符串数据(指定格式的json字符串),按照规则通过类反射机制解析成所属的属性类,把这些属性类生成不同的View,包含嵌套View、同级View(设置好它们的属性)。最后添加到父View,调用setContentView这个view展示出来。
ps:其中解析成属性类的需要注意空指针异常、类转换异常
数据源
因为是demo,直接把数据写到本地的json文件中(assets/TextIT.json)
调用
在MainActivity.java
中的onCreate()
中调用
1 | //根据json 解析得到的json对象,来创建View |
DynamicView
的createView()
重点代码
1 | //创建View |
demo的完整源码
DynamicHelper.java
1 | package com.example.shenbh.showcarddemo.Dynamic; |
DynamicProperty.java
1 | package com.example.shenbh.showcarddemo.Dynamic; |
DynamicView.java
1 | package com.example.shenbh.showcarddemo.Dynamic; |
DynamicViewId.java
1 | package com.example.shenbh.showcarddemo.Dynamic; |
ViewHolder.java
1 | package com.example.shenbh.showcarddemo.Dynamic; |
数据源(demo)
1 | { |
动态添加空态图
具体实现步骤
- 在xml中 给要替换的layout外面加一层RelaytiveLayout
- 在代码中 这个rl移除掉所有的控件
- 添加空态图
完整代码
.java文件中
1 | private void showEmptyView(){ |
注意
添加空态图后,空态图显示不全的问题,这时需要给这个空态图重新设置LalyoutParams,即 rl.setLayoutParams(lp);
动态设置布局属性
动态设置margin、宽度、weight
1 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tv.getLayoutParams(); |
动态设置图片(宽充满屏幕,高与宽为原图比例)
根据配置设置引导页页数
1 | //ldy_product.properties |
1 | //app的build.gradle中设置这个常量 |
1 | //AndroidManifest.xml中 |
1 | //工具类中获取这个常量的值,比如在Contants.java中 |
1 | //使用这个常量值,进行设置引导页页数 |
动态设置图标
可以动态设置上下左右的图标
方法一:
1 | //根据需求填入相应参数,显示哪里填哪里。 |
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 | Drawable myImage = getResources().getDrawable(R.drawable.home); |
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 | { |
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 | public class CustomModularType { |
BaseDataBean 基类Bean限制各个样式的bean的数据模型
作用:
所有装扮模块bean的基类,用来限制各个样式的bean的数据模型
根据不同modularType和modularStyle返回对应的CustomModularType类型
1 | public class BaseDataBean<T> implements Serializable, MultiItemEntity { |
BaseRecyclerAdapter 关联layout、各自的ViewHolder
作用:用于根据不同modularType关联不同的layout,使用不同的ViewHolder。
在MultHomePageFragment的Rc中使用
1 | ... |
CustomViewHolder 管理不同的ViewHolder
管理不同的ViewHolder,供BaseRecyclerAdapter调用
1 | ... |
CustomDataManager 解析数据
作用:把接口返回回来的各个模块数据解析成各自的Bean
在MultHomePageFragment中使用
1 | ... |
MultHomePageFragment
1 | ... |
布局文件
不同样式对应的adapter的item的布局文件
item_sigleimg.xml
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
item_divide.xml
1 |
|
MultHomePageFragment的布局文件
fragment_mult_home_page.xml
1 |
|
动态更改桌面图标(app logo)
效果图
具体方案
- 图标更换:在
AndroidManifest
设置应用入口Activity
的别名,然后通过setComponentEnabledSetting
动态启用或禁用别名进行图标切换。 - 控制图标显示:冷启动
App
时,调用接口判断是否需要切换icon
。 - 触发时机:监听
App
前后台切换,当App
处于后台时切换图标,使得用户无感知。
代码实现
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 在
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
76public class MainActivity extends AppCompatActivity implements ForegroundCallbacks.Listener {
private int position = 0;
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() {
public void onClick(View v) {
position = 0;
}
});
findViewById(R.id.tv_alias1).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
position = 1;
}
});
}
protected void onDestroy() {
// 移除app前后台监听
ForegroundCallbacks.get(this).removeListener(this);
super.onDestroy();
}
public void onForeground() {
}
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);
}
}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);
}
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");
}
}
public void onActivityPaused(Activity activity) {
paused = true;
if (check != null)
handler.removeCallbacks(check);
handler.postDelayed(check = new Runnable() {
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);
}
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
public void onActivityStarted(Activity activity) {
}
public void onActivityStopped(Activity activity) {
}
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
public void onActivityDestroyed(Activity activity) {
}
}需要在
Application
中调用ForegroundCallbacks.init(this)
进行初始化。
具体缺陷
切换icon会关闭应用进程,不是崩溃所以不会上报bugly。
切换icon需要时间,部分华为机型要10s左右,之后能正常打开。
切换icon过程中,部分机型点击图标无法打开应用,提示应用未安装。
2021.11.15更新 魅族机型 16S 不能动态切换icon