preface

We all know that jingdong, Taobao and other apps will display different bottom menu bar in holidays, such as Double 11, Spring Festival and so on. How is this implemented? I have two ideas:

  • Push the hotfix directly, pushing the packaged patch up, but this is expensive, and the product manager is not necessarily willing to do this.
  • The background provides the interface, when entering the page to obtain the picture List, local cache, provided by the interface flag to control whether to display this special menu bar.

This is the second method.

implementation

Take a look at my page layout:

The page layout

The overall layout is roughly as follows:

The overall layout is roughly


<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>Copy the code

Previous menu icon

The previous menu icon used a static resource file, r.map.xxxx, and used Selector to control the icon toggle:

<?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>Copy the code

So the problem is, the ICONS are downloaded from the web, the selectors are written in XML, so what do we do? If selector could be written in Java and if web images could be saved as local images (next time you use them) then it would be great!

To solve

Java code generates selector

Dynamically generate selectors through Java code

 /** * Create Selector background resource file **@param context
     * @paramResource file * when checked@paramUnchecked resource files *@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;
    }Copy the code

Set the radioButton drawableTop based on the generated Selector

 /** * Generates selector ** from the bitmap bitmap file@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);
    }Copy the code

See how to use

  createButtonSelector(BitmapFactory.decodeResource(getResources(), R.mipmap.trad_normal),
                BitmapFactory.decodeResource(getResources(), R.mipmap.trad_selected), mRdoBtnMainTrad);Copy the code

Trad_normal and trad_selected are image resources in MIpmap. MRdoBtnMainTrad is the RadioButton to be set.

Ok, so at this point, the Java code generates the selector and the setup is done.

Get images from the web

Get the bitmap only

If we just need to get the bitmap, we can call the Drawable method:

  /** * get the image from the network **@paramClazz calls the method class *@paramNetUrl gets links to images *@returnReturn a drawable image */
    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;
    }Copy the code

But!!!!! That’s not good, because if we just did that, we’d have to get the resource from the network every time, regenerate the selector, and set it up again, and that would be too expensive, and at the very least, we’d have to cache the image! Glide is used here to download images. Of course, any Other Picasso or Imageloader has a similar approach:

 /** * save the picture to the phone **@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();
                    // Save the image first
                    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();
    }Copy the code

Where the method of copyFile is:

 /**
         * 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);
        }Copy the code
 /**
         * 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);
        }Copy the code
  /**
         * 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) {
            returnwriteFile(filePath ! =null ? new java.io.File(filePath) : null, stream, append);
        }Copy the code
/**
         * 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); }}Copy the code
  /**
         * 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();
        }Copy the code

Of course, sharp-eyed students will find: there is a problem here —->

 String fileName = System.currentTimeMillis() + ".jpg";Copy the code

There’s no rule about how to name an image, so how do we find the one that we want from the image that we get, and then generate a selector for it? Here the blogger’s idea is to create a SQL, establish a mapping, but now the background has not developed the interface, and so on, the blogger will release a demo.

conclusion

The above is the blogger’s own ideas, if you have a better way, welcome private letter and message.

The above?