1、通常来说,一般情况下,我们的app的BottomTab会有集中实现方式。
- 自定义view,然后自己写逻辑去实现互斥。
- 自由度最高,因为啥都是自己写的。
- 使用RadioGroup+RadioButton去实现底部的Tab。
- 自由度比极高,如果想实现搞复杂度的话可以重写RadioButton。
- 使用google design包里面的 TabLayout去实现。
- 可上、可下、可以滑动
- 偷懒的话可以根据已有api来设置一些资源,也可以setCustomView()
- 使用google design包里面的BottomNavigationView去实现。
- 使用menu设置资源
- 有默认的动画效果。
2、今天来讲一下基于TabLayout来实现的根据后台下发实现动态替换底部导航资源图片的方法。
- 因为动态替换肯定意味着下载资源,所以先讲一下IntentService
- IntentService也是一个service,只不过google帮我们在里面封装并维护了一个HandlerThread,里面的操作都是异步的。
- 当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。
- 如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,使用串行的方式,执行完自动结束。
- 这里面最重要的方法 onHandlerIntent(Intent intent)
@Override protected void onHandleIntent(Intent intent) { if (intent != null) { final String action = intent.getAction(); if (ACTION_FOO.equals(action)) { // 在这里面处理耗时任务,当所有的耗时任务都结束以后,IntentService会自动的finish掉,不需要开发者关心。 } } }
3、选择IntentService的原因(下面的这几个操作都是耗时操作,所以我们干脆都封装到这service里面,我们只需要在合适的时机去启动这个Service就ok了)
- 需要下载资源压缩包
- 因为是动态替换,所以必然涉及到预下载,所以数据格式要先定好(下面是数据格式)。
{
"currentInfo":{//当前样式
"id":"111",
"imageZipUrl":"http://oc8ql3urp.bkt.clouddn.com/currentInfo.zip",
"tabNamesList":[
"首页1","产品1","发现1","我的1"
],
"tabColorNormal":"B0C4DE",
"tabColorHighlight":"F7B62D",
"startTime":"1517846400000",
"deadLineTime":"1518710400000"
},
"nextInfo":{//下一次要展示的样式
"id":"111",
"imageZipUrl":"http://oc8ql3urp.bkt.clouddn.com/nextInfo.zip",
"tabNamesList":[
"首页2","产品2","发现2","我的2"
],
"tabColorNormal":"B0C4DE",
"tabColorHighlight":"FE6246",
"startTime":"1417846400000",
"deadLineTime":"1518710400000"
}
}
- 需要存放资源压缩包
- 下载和存放文件的代码(我这里使用的是Retrofit进行下载的)
// 下载文件
Response<ResponseBody> zipFile = ServiceGenerator.createService(HomeService.class)
.downloadFileRetrofit(getFileDownLoadUrl(homeTabImageInfoBean, type))
.execute();
// 得到文件流
ResponseBody zipBody = zipFile.body();
LogUtils.d("HomeTabImageDownLoadInt", "下载完成---");
// 创建一个文件
File zipDirectory = new File(FilePathUtil.getHuaShengHomeTabZipDirectory(getApplicationContext())
+ createZipFileName(homeTabImageInfoBean, type));
// 如果文件不存在,则创建文件夹
if (!zipDirectory.exists()) {
zipDirectory.createNewFile();
}
// 保存文件
FileUtils.writeFile2Disk(zipBody, zipDirectory);
- 需要解压资源压缩方
- 解压的话就是使用java里面的那几个类进行解压的操作
// 解压文件 并删除文件
if (ZipUtils.unzipFile(zipDirectory.getAbsolutePath(),
CURRENT.equals(type) ? FilePathUtil.getHuaShengHomeTabImgCurrentDirectory(getApplicationContext())
: FilePathUtil.getHuaShengHomeTabImgNextDirectory(getApplicationContext()))) {
// 保存文件解压地址
saveFileDirPath(homeTabImageInfoBean, type,
CURRENT.equals(type) ? FilePathUtil.getHuaShengHomeTabImgCurrentDirectory(getApplicationContext())
: FilePathUtil.getHuaShengHomeTabImgNextDirectory(getApplicationContext()));
LogUtils.d("HomeTabImageDownLoadInt", "解压完成---");
}
4、其实最关键的就是如何创建并获取我们的文件资源
- 最重要的就是资源的两种状态切换(选中 or 不选中),通常我们都是使用drawable来写的
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@mipmap/home_tab_financing_selected" android:state_selected="true" />
<item android:drawable="@mipmap/home_tab_financing_normal" />
</selector>
- 现在我们要根据下载下来的图片(存放在sdcard中)去动态创建drawable,这样我们便能里面系统控件的互斥特性,下面的三个方法代码很重要。
// 构建Drawable选择器
private StateListDrawable createDrawableSelector(Drawable checked, Drawable unchecked) {
StateListDrawable stateList = new StateListDrawable();
int state_selected = android.R.attr.state_selected;
stateList.addState(new int[]{state_selected}, checked);
stateList.addState(new int[]{-state_selected}, unchecked);
return stateList;
}
// 构建颜色选择器
private ColorStateList createColorSelector(int checkedColor, int uncheckedColor) {
return new ColorStateList(
new int[][]{new int[]{android.R.attr.state_selected},
new int[]{-android.R.attr.state_selected}},
new int[]{checkedColor, uncheckedColor});
}
// 将文件转换成Drawable
// pathName就是图片存放的绝对路径
private Drawable getDrawableByFile(String pathName) {
return Drawable.createFromPath(pathName);
}
5、如何给TabLayout设置上资源我觉得就不需要我来说了吧
- 取出TabLayout的所有的Tab,遍历,然后根据特定条件去设置相应的drawable
好了,这样就可以实现底部Tab动态替换了,最后奉上一个解压的工具类
1 import com.blankj.utilcode.util.CloseUtils;
2 import com.blankj.utilcode.util.StringUtils;
3
4 import java.io.BufferedInputStream;
5 import java.io.BufferedOutputStream;
6 import java.io.File;
7 import java.io.FileInputStream;
8 import java.io.FileOutputStream;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.OutputStream;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.Enumeration;
15 import java.util.List;
16 import java.util.zip.ZipEntry;
17 import java.util.zip.ZipFile;
18 import java.util.zip.ZipOutputStream;
19
20 /**
21 * <pre>
22 * author: Blankj
23 * blog : http://blankj.com
24 * time : 2016/08/27
25 * desc : 压缩相关工具类
26 * </pre>
27 */
28 public final class ZipUtils {
29
30 private static final int KB = 1024;
31
32 private ZipUtils() {
33 throw new UnsupportedOperationException("u can't instantiate me...");
34 }
35
36 /**
37 * 批量压缩文件
38 *
39 * @param resFiles 待压缩文件集合
40 * @param zipFilePath 压缩文件路径
41 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
42 * @throws IOException IO错误时抛出
43 */
44 public static boolean zipFiles(Collection<File> resFiles, String zipFilePath)
45 throws IOException {
46 return zipFiles(resFiles, zipFilePath, null);
47 }
48
49 /**
50 * 批量压缩文件
51 *
52 * @param resFiles 待压缩文件集合
53 * @param zipFilePath 压缩文件路径
54 * @param comment 压缩文件的注释
55 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
56 * @throws IOException IO错误时抛出
57 */
58 public static boolean zipFiles(Collection<File> resFiles, String zipFilePath, String comment)
59 throws IOException {
60 return zipFiles(resFiles, FileUtils.getFileByPath(zipFilePath), comment);
61 }
62
63 /**
64 * 批量压缩文件
65 *
66 * @param resFiles 待压缩文件集合
67 * @param zipFile 压缩文件
68 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
69 * @throws IOException IO错误时抛出
70 */
71 public static boolean zipFiles(Collection<File> resFiles, File zipFile)
72 throws IOException {
73 return zipFiles(resFiles, zipFile, null);
74 }
75
76 /**
77 * 批量压缩文件
78 *
79 * @param resFiles 待压缩文件集合
80 * @param zipFile 压缩文件
81 * @param comment 压缩文件的注释
82 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
83 * @throws IOException IO错误时抛出
84 */
85 public static boolean zipFiles(Collection<File> resFiles, File zipFile, String comment)
86 throws IOException {
87 if (resFiles == null || zipFile == null) return false;
88 ZipOutputStream zos = null;
89 try {
90 zos = new ZipOutputStream(new FileOutputStream(zipFile));
91 for (File resFile : resFiles) {
92 if (!zipFile(resFile, "", zos, comment)) return false;
93 }
94 return true;
95 } finally {
96 if (zos != null) {
97 zos.finish();
98 CloseUtils.closeIO(zos);
99 }
100 }
101 }
102
103 /**
104 * 压缩文件
105 *
106 * @param resFilePath 待压缩文件路径
107 * @param zipFilePath 压缩文件路径
108 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
109 * @throws IOException IO错误时抛出
110 */
111 public static boolean zipFile(String resFilePath, String zipFilePath)
112 throws IOException {
113 return zipFile(resFilePath, zipFilePath, null);
114 }
115
116 /**
117 * 压缩文件
118 *
119 * @param resFilePath 待压缩文件路径
120 * @param zipFilePath 压缩文件路径
121 * @param comment 压缩文件的注释
122 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
123 * @throws IOException IO错误时抛出
124 */
125 public static boolean zipFile(String resFilePath, String zipFilePath, String comment)
126 throws IOException {
127 return zipFile(FileUtils.getFileByPath(resFilePath), FileUtils.getFileByPath(zipFilePath), comment);
128 }
129
130 /**
131 * 压缩文件
132 *
133 * @param resFile 待压缩文件
134 * @param zipFile 压缩文件
135 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
136 * @throws IOException IO错误时抛出
137 */
138 public static boolean zipFile(File resFile, File zipFile)
139 throws IOException {
140 return zipFile(resFile, zipFile, null);
141 }
142
143 /**
144 * 压缩文件
145 *
146 * @param resFile 待压缩文件
147 * @param zipFile 压缩文件
148 * @param comment 压缩文件的注释
149 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
150 * @throws IOException IO错误时抛出
151 */
152 public static boolean zipFile(File resFile, File zipFile, String comment)
153 throws IOException {
154 if (resFile == null || zipFile == null) return false;
155 ZipOutputStream zos = null;
156 try {
157 zos = new ZipOutputStream(new FileOutputStream(zipFile));
158 return zipFile(resFile, "", zos, comment);
159 } finally {
160 if (zos != null) {
161 CloseUtils.closeIO(zos);
162 }
163 }
164 }
165
166 /**
167 * 压缩文件
168 *
169 * @param resFile 待压缩文件
170 * @param rootPath 相对于压缩文件的路径
171 * @param zos 压缩文件输出流
172 * @param comment 压缩文件的注释
173 * @return {@code true}: 压缩成功<br>{@code false}: 压缩失败
174 * @throws IOException IO错误时抛出
175 */
176 private static boolean zipFile(File resFile, String rootPath, ZipOutputStream zos, String comment)
177 throws IOException {
178 rootPath = rootPath + (isSpace(rootPath) ? "" : File.separator) + resFile.getName();
179 if (resFile.isDirectory()) {
180 File[] fileList = resFile.listFiles();
181 // 如果是空文件夹那么创建它,我把'/'换为File.separator测试就不成功,eggPain
182 if (fileList == null || fileList.length <= 0) {
183 ZipEntry entry = new ZipEntry(rootPath + '/');
184 if (!StringUtils.isEmpty(comment)) entry.setComment(comment);
185 zos.putNextEntry(entry);
186 zos.closeEntry();
187 } else {
188 for (File file : fileList) {
189 // 如果递归返回false则返回false
190 if (!zipFile(file, rootPath, zos, comment)) return false;
191 }
192 }
193 } else {
194 InputStream is = null;
195 try {
196 is = new BufferedInputStream(new FileInputStream(resFile));
197 ZipEntry entry = new ZipEntry(rootPath);
198 if (!StringUtils.isEmpty(comment)) entry.setComment(comment);
199 zos.putNextEntry(entry);
200 byte buffer[] = new byte[KB];
201 int len;
202 while ((len = is.read(buffer, 0, KB)) != -1) {
203 zos.write(buffer, 0, len);
204 }
205 zos.closeEntry();
206 } finally {
207 CloseUtils.closeIO(is);
208 }
209 }
210 return true;
211 }
212
213 /**
214 * 批量解压文件
215 *
216 * @param zipFiles 压缩文件集合
217 * @param destDirPath 目标目录路径
218 * @return {@code true}: 解压成功<br>{@code false}: 解压失败
219 * @throws IOException IO错误时抛出
220 */
221 public static boolean unzipFiles(Collection<File> zipFiles, String destDirPath)
222 throws IOException {
223 return unzipFiles(zipFiles, FileUtils.getFileByPath(destDirPath));
224 }
225
226 /**
227 * 批量解压文件
228 *
229 * @param zipFiles 压缩文件集合
230 * @param destDir 目标目录
231 * @return {@code true}: 解压成功<br>{@code false}: 解压失败
232 * @throws IOException IO错误时抛出
233 */
234 public static boolean unzipFiles(Collection<File> zipFiles, File destDir)
235 throws IOException {
236 if (zipFiles == null || destDir == null) return false;
237 for (File zipFile : zipFiles) {
238 if (!unzipFile(zipFile, destDir)) return false;
239 }
240 return true;
241 }
242
243 /**
244 * 解压文件
245 *
246 * @param zipFilePath 待解压文件路径
247 * @param destDirPath 目标目录路径
248 * @return {@code true}: 解压成功<br>{@code false}: 解压失败
249 * @throws IOException IO错误时抛出
250 */
251 public static boolean unzipFile(String zipFilePath, String destDirPath) throws IOException {
252 // 判断是否存在这个路径,没有的话就创建这个路径
253 File tempDir = new File(destDirPath);
254 if (!tempDir.exists()) {
255 tempDir.mkdirs();
256 }
257 return unzipFile(FileUtils.getFileByPath(zipFilePath), FileUtils.getFileByPath(destDirPath));
258 }
259
260 /**
261 * 解压文件
262 *
263 * @param zipFile 待解压文件
264 * @param destDir 目标目录
265 * @return {@code true}: 解压成功<br>{@code false}: 解压失败
266 * @throws IOException IO错误时抛出
267 */
268 public static boolean unzipFile(File zipFile, File destDir)
269 throws IOException {
270 return unzipFileByKeyword(zipFile, destDir, null) != null;
271 }
272
273 /**
274 * 解压带有关键字的文件
275 *
276 * @param zipFilePath 待解压文件路径
277 * @param destDirPath 目标目录路径
278 * @param keyword 关键字
279 * @return 返回带有关键字的文件链表
280 * @throws IOException IO错误时抛出
281 */
282 public static List<File> unzipFileByKeyword(String zipFilePath, String destDirPath, String keyword)
283 throws IOException {
284 return unzipFileByKeyword(FileUtils.getFileByPath(zipFilePath),
285 FileUtils.getFileByPath(destDirPath), keyword);
286 }
287
288 /**
289 * 解压带有关键字的文件
290 *
291 * @param zipFile 待解压文件
292 * @param destDir 目标目录
293 * @param keyword 关键字
294 * @return 返回带有关键字的文件链表
295 * @throws IOException IO错误时抛出
296 */
297 public static List<File> unzipFileByKeyword(File zipFile, File destDir, String keyword)
298 throws IOException {
299 if (zipFile == null || destDir == null) return null;
300 List<File> files = new ArrayList<>();
301 ZipFile zf = new ZipFile(zipFile);
302 Enumeration<?> entries = zf.entries();
303 while (entries.hasMoreElements()) {
304 ZipEntry entry = ((ZipEntry) entries.nextElement());
305 String entryName = entry.getName();
306 if (StringUtils.isEmpty(keyword) || FileUtils.getFileName(entryName).toLowerCase().contains(keyword.toLowerCase())) {
307 String filePath = destDir + File.separator + entryName;
308 File file = new File(filePath);
309 files.add(file);
310 if (entry.isDirectory()) {
311 if (!FileUtils.createOrExistsDir(file)) return null;
312 } else {
313 if (!FileUtils.createOrExistsFile(file)) return null;
314 InputStream in = null;
315 OutputStream out = null;
316 try {
317 in = new BufferedInputStream(zf.getInputStream(entry));
318 out = new BufferedOutputStream(new FileOutputStream(file));
319 byte buffer[] = new byte[KB];
320 int len;
321 while ((len = in.read(buffer)) != -1) {
322 out.write(buffer, 0, len);
323 }
324 } finally {
325 CloseUtils.closeIO(in, out);
326 }
327 }
328 }
329 }
330 return files;
331 }
332
333 /**
334 * 获取压缩文件中的文件路径链表
335 *
336 * @param zipFilePath 压缩文件路径
337 * @return 压缩文件中的文件路径链表
338 * @throws IOException IO错误时抛出
339 */
340 public static List<String> getFilesPath(String zipFilePath)
341 throws IOException {
342 return getFilesPath(FileUtils.getFileByPath(zipFilePath));
343 }
344
345 /**
346 * 获取压缩文件中的文件路径链表
347 *
348 * @param zipFile 压缩文件
349 * @return 压缩文件中的文件路径链表
350 * @throws IOException IO错误时抛出
351 */
352 public static List<String> getFilesPath(File zipFile)
353 throws IOException {
354 if (zipFile == null) return null;
355 List<String> paths = new ArrayList<>();
356 Enumeration<?> entries = getEntries(zipFile);
357 while (entries.hasMoreElements()) {
358 paths.add(((ZipEntry) entries.nextElement()).getName());
359 }
360 return paths;
361 }
362
363 /**
364 * 获取压缩文件中的注释链表
365 *
366 * @param zipFilePath 压缩文件路径
367 * @return 压缩文件中的注释链表
368 * @throws IOException IO错误时抛出
369 */
370 public static List<String> getComments(String zipFilePath)
371 throws IOException {
372 return getComments(FileUtils.getFileByPath(zipFilePath));
373 }
374
375 /**
376 * 获取压缩文件中的注释链表
377 *
378 * @param zipFile 压缩文件
379 * @return 压缩文件中的注释链表
380 * @throws IOException IO错误时抛出
381 */
382 public static List<String> getComments(File zipFile)
383 throws IOException {
384 if (zipFile == null) return null;
385 List<String> comments = new ArrayList<>();
386 Enumeration<?> entries = getEntries(zipFile);
387 while (entries.hasMoreElements()) {
388 ZipEntry entry = ((ZipEntry) entries.nextElement());
389 comments.add(entry.getComment());
390 }
391 return comments;
392 }
393
394 /**
395 * 获取压缩文件中的文件对象
396 *
397 * @param zipFilePath 压缩文件路径
398 * @return 压缩文件中的文件对象
399 * @throws IOException IO错误时抛出
400 */
401 public static Enumeration<?> getEntries(String zipFilePath)
402 throws IOException {
403 return getEntries(FileUtils.getFileByPath(zipFilePath));
404 }
405
406 /**
407 * 获取压缩文件中的文件对象
408 *
409 * @param zipFile 压缩文件
410 * @return 压缩文件中的文件对象
411 * @throws IOException IO错误时抛出
412 */
413 public static Enumeration<?> getEntries(File zipFile)
414 throws IOException {
415 if (zipFile == null) return null;
416 return new ZipFile(zipFile).entries();
417 }
418
419 private static boolean isSpace(String s) {
420 if (s == null) return true;
421 for (int i = 0, len = s.length(); i < len; ++i) {
422 if (!Character.isWhitespace(s.charAt(i))) {
423 return false;
424 }
425 }
426 return true;
427 }
428 }
工具类