Android--底部导航栏的动态替换方案

1、通常来说,一般情况下,我们的app的BottomTab会有集中实现方式。

  1. 自定义view,然后自己写逻辑去实现互斥。
    • 自由度最高,因为啥都是自己写的。
  2. 使用RadioGroup+RadioButton去实现底部的Tab。
    • 自由度比极高,如果想实现搞复杂度的话可以重写RadioButton。
  3. 使用google design包里面的 TabLayout去实现。
    • 可上、可下、可以滑动
    • 偷懒的话可以根据已有api来设置一些资源,也可以setCustomView()
  4. 使用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 }
工具类
原文地址:https://www.cnblogs.com/819158327fan/p/9304888.html