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 }
工具類

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM