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

前言

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

总结

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

以上 互勉

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,244评论 25 707
  • 工作安全模式 现金流象限对追踪或观察一个人的生活方式很有帮助。许多人终其一生都在寻找财务安全或自由,但最终只不过是...
    fubaba学财商阅读 215评论 0 1
  • 在公众号群发完毛不易《一程山路》,大概过了十几分钟就收到越华老师发来的信息,问我有没有兴趣在“金华之声”开设一档音...
    罗帆文集阅读 266评论 0 0
  • 偶然间看见妹妹发布在简书里的文章,顺便拜读了一下,突然发现读书可以让人心里平静,也会让人跟着情节走。 ...
    不识字的文人阅读 273评论 1 0
  • 事务管理 事务(Transaction): 1、构成单一逻辑工作单元的操作集合 DBMS中的用户程序 DBMS外的...
    画个完美句号阅读 157评论 0 0