美文网首页AndroidUIAndroid开发经验谈Android开发
根据接口动态修改应用底部菜单栏

根据接口动态修改应用底部菜单栏

作者: 间歇性丶神经病患者 | 来源:发表于2017-11-01 18:48 被阅读132次

    前言

    我们都知道在京东,淘宝这些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。

    总结

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

    以上 互勉

    相关文章

      网友评论

      • Blz:我之前做的逻辑是 获取后台图片地址,下载到/data/data/包名/cache/下,使用文件转bitmap 之后,代码设置selector,,,
        间歇性丶神经病患者:@Blz 你这种更好,不会污染sd卡
      • 请叫我章鱼哥:厉害厉害:+1:

      本文标题:根据接口动态修改应用底部菜单栏

      本文链接:https://www.haomeiwen.com/subject/dabgpxtx.html