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 tim开发者_运维知识库e there is no any XML layout pop-up inside the menu.
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'
]
}
}
Also, the direct parent of your actual resource files (pngs, xml layouts, etc..) does still need to correspond with the specification.
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 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.
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.
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 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.
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.
Now we can easily do with JetBrains plugin called "Android File Grouping"
check out this link
Android File Grouping
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()]
}
}
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).
Check Bash Flatten Folder script that converts folder hierarchy to a single folder
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.
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.
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.
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.
- 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.
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.
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 id
s (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.
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']
}
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 insrc/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
精彩评论