根據介面動態修改應用底部選單欄

Mr_凌宇發表於2017-11-02

前言

我們都知道在京東,淘寶這些App,在節假日,比如雙11,春節之類的,都會顯示不同的底部選單欄,那這種是怎麼實現的呢?
我有2個思路:

  • 直接推熱更,把打包好的補丁推上去,不過這種成本比較高,而且產品經理還不一定答應這麼做。
  • 後臺提供介面,在進入頁面的時候獲取圖片List,本地快取,由介面提供的flag進行控制是否顯示這種特殊的選單欄。

這邊使用的是第二種方法。

實現

這邊先看下我這邊的頁面佈局:

頁面佈局
頁面佈局

整體的佈局大概為:

整體的佈局大概
整體的佈局大概

一個vertical的LinearLayout裡面放置了一個ViewPager以及一個RadioGroup,RadioGroup作為底部的選單控制元件,RadioButton作為選單選項。

<RadioGroup
        android:id="@+id/rdoG_main_menu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/rdoBtn_main_index"
            style="@style/base_radio_button"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:drawableTop="@drawable/sl_main_index"
            android:text="@string/text_index"/>

        <RadioButton
            android:id="@+id/rdoBtn_main_game"
            style="@style/base_radio_button"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:drawableTop="@drawable/sl_main_game"
            android:text="@string/text_game"/>

        <RadioButton
            android:id="@+id/rdoBtn_main_trad"
            style="@style/base_radio_button"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:drawableTop="@drawable/sl_main_trad"
            android:text="@string/text_trad"/>

        <RadioButton
            android:id="@+id/rdoBtn_main_center"
            style="@style/base_radio_button"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:drawableTop="@drawable/sl_main_personal"
            android:text="@string/text_person"/>
    </RadioGroup>複製程式碼

之前的選單圖示

之前的選單圖示是用靜態的資原始檔,也就是R.mipmap.xxxx圖片資源,再通過Selector來控制圖示切換:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="false" android:drawable="@mipmap/personal_normal"/>
    <item android:state_checked="true" android:drawable="@mipmap/personal_selected"/>
</selector>複製程式碼

那問題來了,圖示是從網上下載的,selector是xml寫的,那我們要怎麼辦呢?
如果selector可以用java來寫
如果網路圖片可以存為本地圖片(下次使用)
那就天下太平,一見生財了!

解決

java程式碼生成selector

通過java程式碼動態生成selector

 /**
     * 建立Selector背景資原始檔
     *
     * @param context
     * @param checked   確認時候的資原始檔
     * @param unchecked 非確認時候的資原始檔
     * @return
     */
    private StateListDrawable createDrawableSelector(Context context, Drawable unchecked, Drawable checked) {
        StateListDrawable stateList = new StateListDrawable();
        int statePressed = android.R.attr.state_pressed;
        int stateChecked = android.R.attr.state_checked;
        int stateFocused = android.R.attr.state_focused;
        stateList.addState(new int[]{stateChecked}, checked);
        stateList.addState(new int[]{statePressed}, checked);
        stateList.addState(new int[]{stateFocused}, checked);
        stateList.addState(new int[]{}, unchecked);
        return stateList;
    }複製程式碼

根據生成的Selector給radioButton設定drawableTop

 /**
     * 根據bitmap點陣圖檔案生成selector
     *
     * @param bitmapDefault
     * @param bitmapChecked
     */

    private void createButtonSelector(Bitmap bitmapDefault, Bitmap bitmapChecked, RadioButton radioButton) {
        BitmapDrawable drawableDefault = new BitmapDrawable(bitmapDefault);
        BitmapDrawable drawableChecked = new BitmapDrawable(bitmapChecked);
        Drawable drawable = createDrawableSelector(this, drawableDefault, drawableChecked);
        drawable.setBounds(0, 0, ICON_WIDTH,
                ICON_WIDTH);
        radioButton.setCompoundDrawables(null, drawable, null, null);
    }複製程式碼

檢視如何使用

  createButtonSelector(BitmapFactory.decodeResource(getResources(), R.mipmap.trad_normal),
                BitmapFactory.decodeResource(getResources(), R.mipmap.trad_selected), mRdoBtnMainTrad);複製程式碼

其中 trad_normal以及trad_selected為mipmap中的圖片資源;mRdoBtnMainTrad為所要設定的RadioButton。

Ok,到這裡為止,java程式碼生成selector並且設定已經完成了。

從網路上獲取圖片資源

單單獲取bitmap

如果我們只是需要單單獲取bitmap,那麼我們可以呼叫Drawable的方法:

  /**
     * 從網路獲取圖片
     *
     * @param clazz  呼叫方法的類
     * @param netUrl 獲取圖片的連結
     * @return 返回一個 drawable 型別的圖片
     */
    private static Drawable loadImageFromNet(Class clazz, String netUrl) {
        Drawable drawable = null;
        try {
            drawable = Drawable.createFromStream(new URL(netUrl).openStream(), "netUrl.jpg");
        } catch (IOException e) {
            XLog.e(clazz.getName() + e.getMessage());
        }

        return drawable;
    }複製程式碼

但是!!這樣是不好的,如果單單這樣做的話,那我們每次都需要從網路獲取資源,再重新生成selector,再設定上去,這樣的消耗太大了,至少,圖片我們需要快取起來!
這裡我們使用了Glide來做圖片下載,當然,其他的畢卡索或者imageloader也都是有類似的方法的:

 /**
     * 儲存圖片到手機
     *
     * @param url
     */
    public static void download(final String url) {
        new AsyncTask<Void, Integer, File>() {
            @Override
            protected File doInBackground(Void... params) {
                File file = null;
                try {
                    FutureTarget<File> future = Glide
                            .with(CatApplication.getInstance())
                            .load(url)
                            .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
                    file = future.get();
                    // 首先儲存圖片
                    File pictureFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsoluteFile();
                    File appDir = new File(pictureFolder, AppConf.DownLoadConf.DOWNLOAD_DIR);
                    if (!appDir.exists()) {
                        appDir.mkdirs();
                    }
                    String fileName = System.currentTimeMillis() + ".jpg";
                    File destFile = new File(appDir, fileName);
                    Kits.File.copyFile(file.getPath(), destFile.getPath());
                    XLog.e(destFile.getAbsolutePath());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return file;
            }

            @Override
            protected void onPostExecute(File file) {
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
            }
        }.execute();
    }複製程式碼

其中copyFile的方法為:

 /**
         * copy file
         *
         * @param sourceFilePath
         * @param destFilePath
         * @return
         * @throws RuntimeException if an error occurs while operator FileOutputStream
         */
        public static boolean copyFile(String sourceFilePath, String destFilePath) {
            InputStream inputStream = null;
            try {
                inputStream = new FileInputStream(sourceFilePath);
            } catch (FileNotFoundException e) {
                throw new RuntimeException("FileNotFoundException occurred. ", e);
            }
            return writeFile(destFilePath, inputStream);
        }複製程式碼
 /**
         * write file, the bytes will be written to the begin of the file
         *
         * @param filePath
         * @param stream
         * @return
         * @see {@link #writeFile(String, InputStream, boolean)}
         */
        public static boolean writeFile(String filePath, InputStream stream) {
            return writeFile(filePath, stream, false);
        }複製程式碼
  /**
         * write file
         *
         * @param stream the input stream
         * @param append if <code>true</code>, then bytes will be written to the end of the file rather than the beginning
         * @return return true
         * @throws RuntimeException if an error occurs while operator FileOutputStream
         */
        public static boolean writeFile(String filePath, InputStream stream, boolean append) {
            return writeFile(filePath != null ? new java.io.File(filePath) : null, stream, append);
        }複製程式碼
/**
         * write file
         *
         * @param file   the file to be opened for writing.
         * @param stream the input stream
         * @param append if <code>true</code>, then bytes will be written to the end of the file rather than the beginning
         * @return return true
         * @throws RuntimeException if an error occurs while operator FileOutputStream
         */
        public static boolean writeFile(java.io.File file, InputStream stream, boolean append) {
            OutputStream o = null;
            try {
                makeDirs(file.getAbsolutePath());
                o = new FileOutputStream(file, append);
                byte data[] = new byte[1024];
                int length = -1;
                while ((length = stream.read(data)) != -1) {
                    o.write(data, 0, length);
                }
                o.flush();
                return true;
            } catch (FileNotFoundException e) {
                throw new RuntimeException("FileNotFoundException occurred. ", e);
            } catch (IOException e) {
                throw new RuntimeException("IOException occurred. ", e);
            } finally {
                IO.close(o);
                IO.close(stream);
            }
        }複製程式碼
  /**
         * Creates the directory named by the trailing filename of this file, including the complete directory path required
         * to create this directory. <br/>
         * <br/>
         * <ul>
         * <strong>Attentions:</strong>
         * <li>makeDirs("C:\\Users\\Trinea") can only create users folder</li>
         * <li>makeFolder("C:\\Users\\Trinea\\") can create Trinea folder</li>
         * </ul>
         *
         * @param filePath
         * @return true if the necessary directories have been created or the target directory already exists, false one of
         * the directories can not be created.
         * <ul>
         * <li>if {@link File#getFolderName(String)} return null, return false</li>
         * <li>if target directory already exists, return true</li>
         * </ul>
         */
        public static boolean makeDirs(String filePath) {
            String folderName = getFolderName(filePath);
            if (TextUtils.isEmpty(folderName)) {
                return false;
            }

            java.io.File folder = new java.io.File(folderName);
            return (folder.exists() && folder.isDirectory()) || folder.mkdirs();
        }複製程式碼

當然,眼尖的同學會發現:
這裡有問題啊---->

 String fileName = System.currentTimeMillis() + ".jpg";複製程式碼

圖片的命名是沒規則的,那我們怎麼從獲取的圖片中找到自己要的,然後對應生成selector呢?
這裡博主的思路是建立一個sql,建立一個對映,不過現在後臺還沒開發出介面,等接好後,博主會放出一個demo。

總結

以上是博主自己的思路想法,如果各位大佬有更好的方法,歡迎私信以及留言。

以上 互勉

相關文章