ChatGPT解决这个技术问题 Extra ChatGPT

Can the Android layout folder contain subfolders?

Right now, I'm storing every XML layout file inside the 'res/layout' folder, so it is feasible and simple to manage small projects, but when there is a case of large and heavy projects, then there should be a hierarchy and sub-folders needed inside the layout folder.

for e.g.

layout
-- layout_personal
   -- personal_detail.xml
   -- personal_other.xml
--layout_address
  -- address1.xml
  -- address2.xml

Like the same way, we would like to have sub-folders for the large application, so is there any way to do so inside the Android project?

I am able to create layout-personal and layout_address sub-folders inside the layout folder, but when the time comes to access the XML layout file using R.layout._______ , at that time there is no any XML layout pop-up inside the menu.

I think this link might help you in detail. blog.mindorks.com/…

T
Tim

You CAN do this with gradle. I've made a demo project showing how.

The trick is to use gradle's ability to merge multiple resource folders, and set the res folder as well as the nested subfolders in the sourceSets block.

The quirk is that you can't declare a container resource folder before you declare that folder's child resource folders.

Below is the sourceSets block from the build.gradle file from the demo. Notice that the subfolders are declared first.

sourceSets {
    main {
        res.srcDirs =
        [
                'src/main/res/layouts/layouts_category2',
                'src/main/res/layouts',
                'src/main/res'
        ]
    }
}

https://i.stack.imgur.com/yzj01.png

Also, the direct parent of your actual resource files (pngs, xml layouts, etc..) does still need to correspond with the specification.


Is it possible to do this with the drawable folders? I just tried with no luck, even taking into account the declaration ordering.
Nice! Just remember that you are really only exploiting gradle's ability to merge resource folders and it'll make sense.
The file names in the folder still have to be unique and in code you have to know that e.g. R.layout.list_item is coming from the movies or the cinemas folder, so flat naming still applies.
Unfortunately that does not work for me with build:gradle:0.14.4 and buildToolsVersion "21.1.1".
This works, however the answer does a woefully inadequate job of explaining that each subfolder that you add needs to have a second subfolder inside of it, which can only be called "layout", and all of your layout files need to go inside of the second subfolder(s). Reading through dozens of comments to work that out is far from ideal. And for anyone still looking, setting up the extra "layout" subfolders is the solution to the URI is not registered problem.
G
Gareth Latty

The answer is no.

I would like to draw your attention towards this book Pro Android 2 that states:

It is also worth noting a few constraints regarding resources. First, Android supports only a linear list of files within the predefined folders under res. For example, it does not support nested folders under the layout folder (or the other folders under res). Second, there are some similarities between the assets folder and the raw folder under res. Both folders can contain raw files, but the files within raw are considered resources and the files within assets are not. Note that because the contents of the assets folder are not considered resources, you can put an arbitrary hierarchy of folders and files within it.


I guest our next best hope would be an Eclipse or IntelliJ plugin that could collapse the files based on filename prefixes.
@Justin check out the solution I've posted for gradle.
I guess one solution could be to name the files as you would like them categorized, so it looks like it's in subfolders. I add a "subfolder tag" to the beginning of the name. For example. all activities would be "activity_activityname.xml" and a child activity could be "activity_activityname_childactivity.xml" and so on. Any other file would be "other_filename.xml". That's what I do to avoid confusion. Damn, someone beat me to it. By three years >.< My bad, hadn't seen that.
It's important to note here that by considering raw as resources you get density/orientation/version/etc dependent striping, while with assets you have to do that manually.
Chosen this answer as an argument against figting the Android mainstream.
N
Nick H

I just wanted to add onto eskis' fantastic answer for people having trouble. (Note: This will only work and look like separate directories inside the 'project' view, not the 'android' view unfortunately.)

Tested with the following. BuildToolsVersion = 23.0.0 gradle 1.2.3 & 1.3.0

This is how I got mine to work with an already built project.

Copy all of the XML files out of your layout directory, and put them into a directory on the desktop or something for backup. Delete the entire layout directory (Make sure you backed everything up from step 1!!!) Right click the res directory and select new > directory. Name this new directory "layouts". (This can be whatever you want, but it will not be a 'fragment' directory or 'activity' directory, that comes later). Right click the new "layouts" directory and select new > directory. (This will be the name of the type of XML files you will have in it, for example, 'fragments' and 'activities'). Right click the 'fragment' or 'activities' directory (Note: this doesn't have to be 'fragment' or 'activities' that's just what i'm using as an example) and select new > directory once again and name this directory "layout". (Note: This MUST be named 'layout'!!! very important). Put the XML files you want inside the new 'layout' directory from the backup you made on your desktop. Repeat steps 5 - 7 for as many custom directories as you desire. Once this is complete, go into your modules gradle.build file and create a sourceSets definition like this...(Make sure 'src/main/res/layouts' & 'src/main/res' are always the bottom two!!!! Like I am showing below). sourceSets { main { res.srcDirs = [ 'src/main/res/layouts/activities', 'src/main/res/layouts/fragments', 'src/main/res/layouts/content', 'src/main/res/layouts', 'src/main/res' ] } } Profit $$$$

But seriously.. this is how I got it to work. Let me know if anyone has any questions.. I can try to help.

Pictures are worth more than words.

https://i.stack.imgur.com/C1c9B.png


What about layout directories such as layout-v21?
I havn't tested it, but I would imagine you just create a layout-v21 folder along side the layout folders.
Works on gradle 2.0.0 and buildToolsVersion 23.0.2! Note that the config value is 'src/main/res/layouts/content' and the path of this config is 'src/main/res/layouts/content/layout'
I'm having problem with this stackoverflow.com/questions/41934004/…
Searched for awhile with no luck, this is the only solution that worked for me. Thanks for posting such a detailed answer with images!
d
delformo

Not possible, but the layout folder is sorted by name. So, I prepend the layout file names with my package names. E.g. for the two packages "buying" and "playing":

buying_bought_tracks.xml
buying_buy_tracks.xml
playing_edit_playlist.xml
playing_play_playlist.xml
playing_show_playlists.xml

ᴛʜᴇᴘᴀᴛᴇʟ

I use Android File Grouping plugin for Android Studio.It doesn't really allows you to create sub-folders, but it can DISPLAY your files and resources AS they are in different folders. And this is exactly what I wanted.

You can install "Android File Grouping" plugin by

Windows:

Android Studio -> File -> Settings -> Plugins.

Mac:

Android Studio -> Android Studio Tab (Top Left) -> Preferences -> Plugins -> Install JetBrains Plugin..

For Mac, I was able to test it and was not able to search for the plugin. So I downloaded the plugin from here and used the Install plugin from disk option from the above setting.


Also share the link of that plugin!
Done. Anyways you always can search for "Android File Grouping" in Android Studio -> File -> Settings -> Plugins
the plugin did not work on the "Android" view, only "Project" View :/
b
botbot

I think the most elegant solution to this problem (given that subfolders are not allowed) is to prepend the file names with the name of the folder you would have placed it inside of. For example, if you have a bunch of layouts for an Activity, Fragment, or just general view called "places" then you should just prepend it with places_my_layout_name. At least this solves the problem of organizing them in a way that they are easier to find within the IDE. It's not the most awesome solution, but it's better than nothing.


C
Community

Small Problem

I am able to achieve subfolders by following the top answer to this question.

However, as the project grows bigger, you will have many sub-folders:

sourceSets {
    main {
        res.srcDirs =
            [
                    'src/main/res/layouts/somethingA',
                    'src/main/res/layouts/somethingB',
                    'src/main/res/layouts/somethingC',
                    'src/main/res/layouts/somethingD',
                    'src/main/res/layouts/somethingE',
                    'src/main/res/layouts/somethingF',
                    'src/main/res/layouts/somethingG',
                    'src/main/res/layouts/somethingH',
                    'src/main/res/layouts/...many more',
                    'src/main/res'
            ]
    }
}

Not a big problem, but:

it's not pretty as the list become very long.

you have to change your app/build.gradle everytime you add a new folder.

Improvement

So I wrote a simple Groovy method to grab all nested folders:

def getLayoutList(path) {
    File file = new File(path)
    def throwAway = file.path.split("/")[0]
    def newPath = file.path.substring(throwAway.length() + 1)
    def array = file.list().collect {
        "${newPath}/${it}"
    }
    array.push("src/main/res");
    return array
}

Paste this method outside of the android {...} block in your app/build.gradle.

How to use

For a structure like this:

<project root>
├── app <---------- TAKE NOTE
├── build
├── build.gradle
├── gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

Use it like this:

android {
    sourceSets {
        main {
            res.srcDirs = getLayoutList("app/src/main/res/layouts/")
        }
    }
}

If you have a structure like this:

<project root>
├── my_special_app_name <---------- TAKE NOTE
├── build
├── build.gradle
├── gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

You will use it like this:

android {
    sourceSets {
        main {
            res.srcDirs = getLayoutList("my_special_app_name/src/main/res/layouts/")
        }
    }
}

Explanation

getLayoutList() takes a relative path as an argument. The relative path is relative to the root of the project. So when we input "app/src/main/res/layouts/", it will return all the subfolders' name as an array, which will be exactly the same as:

            [
                    'src/main/res/layouts/somethingA',
                    'src/main/res/layouts/somethingB',
                    'src/main/res/layouts/somethingC',
                    'src/main/res/layouts/somethingD',
                    'src/main/res/layouts/somethingE',
                    'src/main/res/layouts/somethingF',
                    'src/main/res/layouts/somethingG',
                    'src/main/res/layouts/somethingH',
                    'src/main/res/layouts/...many more',
                    'src/main/res'
            ]

Here's the script with comments for understanding:

def getLayoutList(path) {
    // let's say path = "app/src/main/res/layouts/
    File file = new File(path)

    def throwAway = file.path.split("/")[0]
    // throwAway = 'app'

    def newPath = file.path.substring(throwAway.length() + 1) // +1 is for '/'
    // newPath = src/main/res/layouts/

    def array = file.list().collect {
        // println "filename: ${it}" // uncomment for debugging
        "${newPath}/${it}"
    }

    array.push("src/main/res");
    // println "result: ${array}" // uncomment for debugging

    return array
}

Hope it helps!


I used your approach but how to use it in activity ? i created subfolder in layouts folder called admin and it has admin_home layout when i am in activity and use setContentView(R.layout.Admin.admin_home .) Admin.admin_home isn't resolved
You should be able to use it normally, like R.layout.admin_home. The script should be able to automatically pick up the names for you. Let me know if you can make it work.
it works well but after changing this line array.push("src/main/res"); to def res="src/main/res"; array.push(res); because i have an error as push want Gstring parameter not String one .if any one rather than me found this error you can edit it
Layouts in the subfolders are not able to resolve android namespace. Any ideas ?
Can you uncomment those lines with // uncomment for debugging and then check if the outputs src/main/res/layouts/<your_layout_files> are properly detected?
A
Androiderson

Now with Android Studio and Gradle, you can have multiple resource folders in your project. Allowing to organize not only your layout files but any kind of resources.

It's not exactly a sub-folder, but may separte parts of your application.

The configuration is like this:

sourceSets {
    main {
        res.srcDirs = ['src/main/res', 'src/main/res2']
    }
}

Check the documentation.


I noticed that every time I made changes to a layout under this setting, I always had to do a clean to see it reflected on app.
S
Santhi Bharath

Now we can easily do with JetBrains plugin called "Android File Grouping"

check out this link

Android File Grouping

https://i.stack.imgur.com/nSdnQ.png


Must be marked as correct answer, as only organize the grouping with simple name convention. No need to change gradle, or even default directory structure of an app. All layout still remains in the layout folder but are grouped in Android Studio
Should not be the accepted answer because it's not working in the Android view....(but i love the idea^^)
This does not work for the last last version of android... is any updated version?
F
Fonix

A way i did it was to create a separate res folder at the same level as the actual res folder in your project, then you can use this in your apps build.gradle

android {
    //other stuff

    sourceSets {
        main.res.srcDirs = ['src/main/res', file('src/main/layouts').listFiles()]
    }
}

https://i.stack.imgur.com/zLGhp.png

then each subfolder of your new res folder can be something relating to each particular screen or something in your app, and each folder will have their own layout / drawable / values etc keeping things organised and you dont have to update the gradle file manually like some of these other answers require (Just sync your gradle each time you add a new resource folder so it knows about it, and make sure to add the relevant subfolders before adding your xml files).


S
Sneg

Check Bash Flatten Folder script that converts folder hierarchy to a single folder


Will check it out soon. Thanks
That script puts changes the file name to include the directory hierarchy in the file name. How will that work with your Android IDE with file names changing all over the place?
D
Drusantia

If you use the method in the approved answer, and want to improve it on a bit, then change the gradle setting like this:

    sourceSets {
    main {
        res.srcDirs = [
            file("src/main/res/layouts/").listFiles(),
            "src/main/res/layouts",
            "src/main/res"
        ]
    }
}

So if you add more folders and layouts, you don't need to come back here and append a long list of source folders, let gradle get all the folders for you.


D
Darokthar

If you are developing on a linux or a mac box, a workaround would be, to create subfolders which include symbolic links to your layoutfiles. Just use the ln command with -s

ln -s PATH_TO_YOUR_FILE

The Problem with this is, that your Layout folder still contains all the .xml files. But you could although select them by using the sub-folders. It's the closest thing, to what you would like to have.

I just read, that this might work with Windows, too if you are using Vista or later. There is this mklink command. Just google it, have never used it myself.

Another problem is, if you have the file opened and try to open it again out the plugin throws a NULL Pointer Exception. But it does not hang up.


Putting a symlink in defeats the purpose of wanting to modularise the storage by seperating components into different subdirectories : effectively making it more complex instead of less. Gradle allows the specifications of additional resource directories.
r
rogerhu

While all the proposals for multiple resource sets may work, the problem is that the current logic for the Android Studio Gradle plug-in will not update the resource files after they have changed for nested resource sets. The current implementation attempts to check the resource directories using startsWith(), so a directory structure that is nested (i.e. src/main/res/layout/layouts and src/main/res/layout/layouts_category2) will choose src/main/res/layout/layouts consistently and never actually update the changes. The end result is that you have to rebuild/clean the project each time.

I submitted a patch at https://android-review.googlesource.com/#/c/157971/ to try to help resolve things.


I agree with you that AS doesn't note changes in XML files. But what is the solution? I will try stackoverflow.com/questions/32317822/….
R
Riki

The top answer by @eski is good, but the code is not elegant to use, so I wrote a groovy script in gradle for general use. It's applied to all build type and product flavor and not only can be use for layout, you can also add subfolder for any other resources type such as drawable. Here is the code(put it in android block of project-level gradle file):

sourceSets.each {
    def rootResDir = it.res.srcDirs[0]
    def getSubDirs = { dirName ->
        def layoutsDir = new File(rootResDir, dirName)
        def subLayoutDirs = []
        if (layoutsDir.exists()) {
            layoutsDir.eachDir {
                subLayoutDirs.add it
            }
        }
        return subLayoutDirs
    }
    def resDirs = [
            "anims",
            "colors",
            "drawables",
            "drawables-hdpi",
            "drawables-mdpi",
            "drawables-xhdpi",
            "drawables-xxhdpi",
            "layouts",
            "valuess",
    ]
    def srcDirs = resDirs.collect {
        getSubDirs(it)
    }
    it.res.srcDirs = [srcDirs, rootResDir]
}

How to do in practice?

For example, I want to create subfolder named activity for layout, add a string by any name in resDirs variable such as layouts, then the layout xml file should be put in res\layouts\activity\layout\xxx.xml.

If I want to create subfolder named selectors for drawable, add a string by any name in resDirs variable such as drawables, then the drawable xml file should be put in res\drawables\selectors\drawable\xxx.xml.

The folder name such as layouts and drawables is defined in resDirs variable, it can be any string. All subfolder created by you such as activity or selectors are regarded as the same as res folder. So in selectors folder, we must create drawable folder additionally and put xml files in drawable folder, after that gradle can recognize the xml files as drawable normally.


This one should be considered the correct answer! Simple and working perfectly!
j
jwpfox

Step 1: Right click on layout - show in explorer

Step 2: Open the layout folder and create the subfolders directly: layout_1, layout_2 ...

Step 3: open layout_1 create folder layout (note: mandatory name is layout), open layout_2 folder create layout subdirectory (note: mandatory name is layout) ...

Step 4: Copy the xml files into the layout subdirectories in layout_1 and layout_2

Step 5: Run the code in buid.grade (module app) and hit sync now:

sourceSets {
    main {
        res.srcDirs =
            [
                'src / main / res / layout / layout_1'
                'src / main / res / layout / layout_2',
                'src / main / res'
            ]
    }
}

Step 6: Summary: All the steps above will only help clustering folders and display in 'project' mode, while 'android' mode will display as normal.

So I draw that maybe naming prefixes is as effective as clustering folders.


Thanks for Project mode. I couldn't understand where subfolders disappeared (in Android mode).
s
sravan953

Well, the short answer is no. But you definitely can have multiple res folders. That, I think, is as close as you can get to having subfolders for the layout folder. Here's how you do it.


Check the accepted answer. I haven't tried it personally but if you can check and update then it would be helpful!
C
CoolMind

Top answers have several disadvantages: you have to add new layout paths, AS places new resources to res\layouts folder instead of res\values.

Combining several answers I wrote similar:

sourceSets {
    main {
        res.srcDirs =
                [
                        'src/main/res',
                        file("src/main/res/layouts/").listFiles(),
                        'src/main/res/layouts'
                ]
    }
}

I made folders with this article: http://alexzh.com/tutorials/how-to-store-layouts-in-different-folders-in-android-project/. In order to create subfolders you should use this menu: New > Folder > Res Folder.

UPDATE

After a couple of weeks I found that changes in resources are not noticed by Android Studio. So, some weird bugs appear. For instance, layouts continue to show old sizes, margins. Sometimes AS doesn't find new XML-files (especially during run-time). Sometimes it mixes view ids (references to another XML-file). It's often required to press Build > Clean Project or Build > Rebuild Project. Read Rebuild required after changing xml layout files in Android Studio.


Your comment 'I made folders with this article (attached link)' is not an acceptable way to provide solutions here on S.O. Your answer is vague. You may leave instructions and credit the author with a link. Otherwise, this is just lazy.
@jungledev, well, finally I refused this idea and returned to a traditional res folder, because AS doesn't fully support dividing resources into folders. Probably I will change this answer or even delete. But what do you mean when saying that it is not an acceptable way to provide solutions here on S.O.?
I mean exactly what I said. Don't say 'I did it with this tutorial.... (insert link).' What you should say is: "I followed this tutorial (insert link) and here are the steps that worked for me. Step 1, Step 2, Step 3... etc' You need to include the steps HERE in the answer. Simply linking to a tutorial is unacceptable.
n
netcyrax

Cannot have subdirectories (easily) but you can have additional resource folders. Surprised no one mentioned it already, but to keep the default resource folders, and add some more:

    sourceSets {
        main.res.srcDirs += ['src/main/java/XYZ/ABC']
    }

B
Braian Coronel

Within a module, to have a combination of flavors, flavor resources (layout, values) and flavors resource resources, the main thing to keep in mind are two things:

When adding resource directories in res.srcDirs for flavor, keep in mind that in other modules and even in src/main/res of the same module, resource directories are also added. Hence, the importance of using an add-on assignment (+=) so as not to overwrite all existing resources with the new assignment. The path that is declared as an element of the array is the one that contains the resource types, that is, the resource types are all the subdirectories that a res folder contains normally such as color, drawable, layout, values, etc. The name of the res folder can be changed.

An example would be to use the path "src/flavor/res/values/strings-ES" but observe that the practice hierarchy has to have the subdirectory values:

├── module 
   ├── flavor
      ├── res
         ├── values
            ├── strings-ES
               ├── values
                  ├── strings.xml
               ├── strings.xml
 

The framework recognizes resources precisely by type, that is why normally known subdirectories cannot be omitted.

Also keep in mind that all the strings.xml files that are inside the flavor would form a union so that resources cannot be duplicated. And in turn this union that forms a file in the flavor has a higher order of precedence before the main of the module.

flavor {
        res.srcDirs += [
            "src/flavor/res/values/strings-ES"
        ]
}

Consider the strings-ES directory as a custom-res which contains the resource types.

GL