Added support library for TreeViewList. Previously this code was subsumed in the android app.

This commit is contained in:
Kevin Ruland 2012-05-27 12:48:32 +00:00
parent af01c8482b
commit 100b54ae8f
75 changed files with 2009 additions and 0 deletions

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>TreeViewList</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.polidea.treeview"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
<application/>
</manifest>

View File

@ -0,0 +1,24 @@
<html>
<body>
This is a small utility that provides quite configurable tree view list.
It is based on standard android list view. It separates out different
aspects of the tree: there is a separate list view, tree adapter, tree
state manager and tree state builder.
<p>
<ul>
<li>Tree view provides the frame to display the view.</li>
<li>Adapter allows to create visual representation of each tree
node.</li>
<li>State manager provides storage for tree state (connections
between parents and children, collapsed/expanded state). It provides
all the low-level tree manipulation methods.</li>
<li>Tree builder allows to build tree easily providing higher
level methods. The tree can be build either from prepared sequentially
prepared list of nodes (node id, level) or using (parent/child
relationships).</li>
<p>For now only in-memory state manager is provided, but Tree State
Manger interface is done in the way that database tree manager even for
large trees is potentially supported.
</ul>
</body>
</html>

View File

@ -0,0 +1,3 @@
# cache for current jar dependecy. DO NOT EDIT.
# format is <lastModified> <length> <SHA-1> <path>
# Encoding is UTF-8

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

View File

@ -0,0 +1,6 @@
/** Automatically generated file. DO NOT MODIFY */
package pl.polidea.treeview;
public final class BuildConfig {
public final static boolean DEBUG = true;
}

View File

@ -0,0 +1,276 @@
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package pl.polidea.treeview;
public final class R {
public static final class attr {
/** <p>Must be a boolean value, either "<code>true</code>" or "<code>false</code>".
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
*/
public static int collapsible=0x7f010000;
/** <p>Must be a boolean value, either "<code>true</code>" or "<code>false</code>".
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
*/
public static int handle_trackball_press=0x7f010004;
/** <p>Must be a dimension value, which is a floating point number appended with a unit such as "<code>14.5sp</code>".
Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),
in (inches), mm (millimeters).
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
*/
public static int indent_width=0x7f010003;
/** <p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
*/
public static int indicator_background=0x7f010006;
/** <p>Must be one or more (separated by '|') of the following constant values.</p>
<table>
<colgroup align="left" />
<colgroup align="left" />
<colgroup align="left" />
<tr><th>Constant</th><th>Value</th><th>Description</th></tr>
<tr><td><code>top</code></td><td>0x30</td><td> Push object to the top of its container, not changing its size. </td></tr>
<tr><td><code>bottom</code></td><td>0x50</td><td> Push object to the bottom of its container, not changing its size. </td></tr>
<tr><td><code>left</code></td><td>0x03</td><td> Push object to the left of its container, not changing its size. </td></tr>
<tr><td><code>right</code></td><td>0x05</td><td> Push object to the right of its container, not changing its size. </td></tr>
<tr><td><code>center_vertical</code></td><td>0x10</td><td> Place object in the vertical center of its container, not changing its size. </td></tr>
<tr><td><code>fill_vertical</code></td><td>0x70</td><td> Grow the vertical size of the object if needed so it completely fills its container. </td></tr>
<tr><td><code>center_horizontal</code></td><td>0x01</td><td> Place object in the horizontal center of its container, not changing its size. </td></tr>
<tr><td><code>fill_horizontal</code></td><td>0x07</td><td> Grow the horizontal size of the object if needed so it completely fills its container. </td></tr>
<tr><td><code>center</code></td><td>0x11</td><td> Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. </td></tr>
<tr><td><code>fill</code></td><td>0x77</td><td> Grow the horizontal and vertical size of the object if needed so it completely fills its container. </td></tr>
<tr><td><code>clip_vertical</code></td><td>0x80</td><td> Additional option that can be set to have the top and/or bottom edges of the child clipped to its container's bounds.
The clip will be based on the vertical gravity: a top gravity will clip the bottom edge, a bottom gravity will clip the top
edge, and neither will clip both edges. </td></tr>
<tr><td><code>clip_horizontal</code></td><td>0x08</td><td> Additional option that can be set to have the left and/or right edges of the child clipped to its container's bounds.
The clip will be based on the horizontal gravity: a left gravity will clip the right edge, a right gravity will clip the
left edge, and neither will clip both edges. </td></tr>
</table>
*/
public static int indicator_gravity=0x7f010005;
/** <p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
*/
public static int row_background=0x7f010007;
/** <p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
*/
public static int src_collapsed=0x7f010002;
/** <p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
*/
public static int src_expanded=0x7f010001;
}
public static final class drawable {
public static int collapsed=0x7f020000;
public static int divider=0x7f020001;
public static int expanded=0x7f020002;
public static int ic_launcher=0x7f020003;
public static int list_selector_background=0x7f020004;
public static int list_selector_background_disabled=0x7f020005;
public static int list_selector_background_focus=0x7f020006;
public static int list_selector_background_longpress=0x7f020007;
public static int list_selector_background_pressed=0x7f020008;
public static int list_selector_background_transition=0x7f020009;
}
public static final class id {
public static int bottom=0x7f040001;
public static int center=0x7f040008;
public static int center_horizontal=0x7f040006;
public static int center_vertical=0x7f040004;
public static int clip_horizontal=0x7f04000b;
public static int clip_vertical=0x7f04000a;
public static int fill=0x7f040009;
public static int fill_horizontal=0x7f040007;
public static int fill_vertical=0x7f040005;
public static int left=0x7f040002;
public static int right=0x7f040003;
public static int top=0x7f040000;
public static int treeview_list_item_frame=0x7f04000e;
public static int treeview_list_item_image=0x7f04000d;
public static int treeview_list_item_image_layout=0x7f04000c;
}
public static final class layout {
public static int tree_list_item_wrapper=0x7f030000;
}
public static final class style {
public static int treeViewListStyle=0x7f050000;
}
public static final class styleable {
/** Attributes that can be used with a TreeViewList.
<p>Includes the following attributes:</p>
<table>
<colgroup align="left" />
<colgroup align="left" />
<tr><th>Attribute</th><th>Description</th></tr>
<tr><td><code>{@link #TreeViewList_collapsible pl.polidea.treeview:collapsible}</code></td><td></td></tr>
<tr><td><code>{@link #TreeViewList_handle_trackball_press pl.polidea.treeview:handle_trackball_press}</code></td><td></td></tr>
<tr><td><code>{@link #TreeViewList_indent_width pl.polidea.treeview:indent_width}</code></td><td></td></tr>
<tr><td><code>{@link #TreeViewList_indicator_background pl.polidea.treeview:indicator_background}</code></td><td></td></tr>
<tr><td><code>{@link #TreeViewList_indicator_gravity pl.polidea.treeview:indicator_gravity}</code></td><td></td></tr>
<tr><td><code>{@link #TreeViewList_row_background pl.polidea.treeview:row_background}</code></td><td></td></tr>
<tr><td><code>{@link #TreeViewList_src_collapsed pl.polidea.treeview:src_collapsed}</code></td><td></td></tr>
<tr><td><code>{@link #TreeViewList_src_expanded pl.polidea.treeview:src_expanded}</code></td><td></td></tr>
</table>
@see #TreeViewList_collapsible
@see #TreeViewList_handle_trackball_press
@see #TreeViewList_indent_width
@see #TreeViewList_indicator_background
@see #TreeViewList_indicator_gravity
@see #TreeViewList_row_background
@see #TreeViewList_src_collapsed
@see #TreeViewList_src_expanded
*/
public static final int[] TreeViewList = {
0x7f010000, 0x7f010001, 0x7f010002, 0x7f010003,
0x7f010004, 0x7f010005, 0x7f010006, 0x7f010007
};
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#collapsible}
attribute's value can be found in the {@link #TreeViewList} array.
<p>Must be a boolean value, either "<code>true</code>" or "<code>false</code>".
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
@attr name android:collapsible
*/
public static final int TreeViewList_collapsible = 0;
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#handle_trackball_press}
attribute's value can be found in the {@link #TreeViewList} array.
<p>Must be a boolean value, either "<code>true</code>" or "<code>false</code>".
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
@attr name android:handle_trackball_press
*/
public static final int TreeViewList_handle_trackball_press = 4;
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#indent_width}
attribute's value can be found in the {@link #TreeViewList} array.
<p>Must be a dimension value, which is a floating point number appended with a unit such as "<code>14.5sp</code>".
Available units are: px (pixels), dp (density-independent pixels), sp (scaled pixels based on preferred font size),
in (inches), mm (millimeters).
<p>This may also be a reference to a resource (in the form
"<code>@[<i>package</i>:]<i>type</i>:<i>name</i></code>") or
theme attribute (in the form
"<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>")
containing a value of this type.
@attr name android:indent_width
*/
public static final int TreeViewList_indent_width = 3;
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#indicator_background}
attribute's value can be found in the {@link #TreeViewList} array.
<p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
@attr name android:indicator_background
*/
public static final int TreeViewList_indicator_background = 6;
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#indicator_gravity}
attribute's value can be found in the {@link #TreeViewList} array.
<p>Must be one or more (separated by '|') of the following constant values.</p>
<table>
<colgroup align="left" />
<colgroup align="left" />
<colgroup align="left" />
<tr><th>Constant</th><th>Value</th><th>Description</th></tr>
<tr><td><code>top</code></td><td>0x30</td><td> Push object to the top of its container, not changing its size. </td></tr>
<tr><td><code>bottom</code></td><td>0x50</td><td> Push object to the bottom of its container, not changing its size. </td></tr>
<tr><td><code>left</code></td><td>0x03</td><td> Push object to the left of its container, not changing its size. </td></tr>
<tr><td><code>right</code></td><td>0x05</td><td> Push object to the right of its container, not changing its size. </td></tr>
<tr><td><code>center_vertical</code></td><td>0x10</td><td> Place object in the vertical center of its container, not changing its size. </td></tr>
<tr><td><code>fill_vertical</code></td><td>0x70</td><td> Grow the vertical size of the object if needed so it completely fills its container. </td></tr>
<tr><td><code>center_horizontal</code></td><td>0x01</td><td> Place object in the horizontal center of its container, not changing its size. </td></tr>
<tr><td><code>fill_horizontal</code></td><td>0x07</td><td> Grow the horizontal size of the object if needed so it completely fills its container. </td></tr>
<tr><td><code>center</code></td><td>0x11</td><td> Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. </td></tr>
<tr><td><code>fill</code></td><td>0x77</td><td> Grow the horizontal and vertical size of the object if needed so it completely fills its container. </td></tr>
<tr><td><code>clip_vertical</code></td><td>0x80</td><td> Additional option that can be set to have the top and/or bottom edges of the child clipped to its container's bounds.
The clip will be based on the vertical gravity: a top gravity will clip the bottom edge, a bottom gravity will clip the top
edge, and neither will clip both edges. </td></tr>
<tr><td><code>clip_horizontal</code></td><td>0x08</td><td> Additional option that can be set to have the left and/or right edges of the child clipped to its container's bounds.
The clip will be based on the horizontal gravity: a left gravity will clip the right edge, a right gravity will clip the
left edge, and neither will clip both edges. </td></tr>
</table>
@attr name android:indicator_gravity
*/
public static final int TreeViewList_indicator_gravity = 5;
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#row_background}
attribute's value can be found in the {@link #TreeViewList} array.
<p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
@attr name android:row_background
*/
public static final int TreeViewList_row_background = 7;
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#src_collapsed}
attribute's value can be found in the {@link #TreeViewList} array.
<p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
@attr name android:src_collapsed
*/
public static final int TreeViewList_src_collapsed = 2;
/**
<p>This symbol is the offset where the {@link pl.polidea.treeview.R.attr#src_expanded}
attribute's value can be found in the {@link #TreeViewList} array.
<p>May be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
<p>May be a color value, in the form of "<code>#<i>rgb</i></code>", "<code>#<i>argb</i></code>",
"<code>#<i>rrggbb</i></code>", or "<code>#<i>aarrggbb</i></code>".
@attr name android:src_expanded
*/
public static final int TreeViewList_src_expanded = 1;
};
}

View File

@ -0,0 +1,40 @@
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

View File

@ -0,0 +1,12 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "ant.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-7
android.library=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@android:drawable/divider_horizontal_dark"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:scaleType="fitXY" android:paddingLeft="5sp"
android:paddingRight="5sp" android:paddingBottom="2sp" />

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project Licensed under the
Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
OR CONDITIONS OF ANY KIND, either express or implied. See the License for
the specific language governing permissions and limitations under the License. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:drawable="@android:color/transparent" />
<!-- Even though these two point to the same resource, have two states so
the drawable will invalidate itself when coming out of pressed state. -->
<item android:state_focused="true" android:state_enabled="false"
android:state_pressed="true" android:drawable="@drawable/list_selector_background_disabled" />
<item android:state_focused="true" android:state_enabled="false"
android:drawable="@drawable/list_selector_background_disabled" />
<item android:state_focused="true" android:state_pressed="true"
android:drawable="@drawable/list_selector_background_transition" />
<item android:state_focused="false" android:state_pressed="true"
android:drawable="@drawable/list_selector_background_transition" />
<item android:state_focused="true"
android:drawable="@drawable/list_selector_background_focus" />
<!-- !!hack!! to fill StateListDrawable.mStateListState with exactly 10
items otherwise it will throw NPE on Android 1.6 -->
<item android:animationCache="true"
android:drawable="@android:color/transparent" />
<item android:animationCache="false"
android:drawable="@android:color/transparent" />
<item android:alwaysDrawnWithCache="false"
android:drawable="@android:color/transparent" />
<item android:alwaysDrawnWithCache="true"
android:drawable="@android:color/transparent" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2008 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/list_selector_background_pressed" />
<item android:drawable="@drawable/list_selector_background_longpress" />
</transition>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout android:id="@+id/treeview_list_item_image_layout" android:layout_width="80dip"
android:layout_height="fill_parent" android:gravity="right|center_vertical">
<ImageView android:id="@+id/treeview_list_item_image" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:src="@drawable/collapsed" >
</ImageView>
</LinearLayout>
<FrameLayout android:id="@+id/treeview_list_item_frame" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_weight="1">
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TreeViewList">
<attr name="collapsible" format="boolean" />
<attr name="src_expanded" format="reference|color" />
<attr name="src_collapsed" format="reference|color" />
<attr name="indent_width" format="dimension" />
<attr name="handle_trackball_press" format="boolean" />
<attr name="indicator_gravity">
<!-- Push object to the top of its container, not changing its size. -->
<flag name="top" value="0x30" />
<!-- Push object to the bottom of its container, not changing its size. -->
<flag name="bottom" value="0x50" />
<!-- Push object to the left of its container, not changing its size. -->
<flag name="left" value="0x03" />
<!-- Push object to the right of its container, not changing its size. -->
<flag name="right" value="0x05" />
<!-- Place object in the vertical center of its container, not changing its size. -->
<flag name="center_vertical" value="0x10" />
<!-- Grow the vertical size of the object if needed so it completely fills its container. -->
<flag name="fill_vertical" value="0x70" />
<!-- Place object in the horizontal center of its container, not changing its size. -->
<flag name="center_horizontal" value="0x01" />
<!-- Grow the horizontal size of the object if needed so it completely fills its container. -->
<flag name="fill_horizontal" value="0x07" />
<!-- Place the object in the center of its container in both the vertical and horizontal axis, not changing its size. -->
<flag name="center" value="0x11" />
<!-- Grow the horizontal and vertical size of the object if needed so it completely fills its container. -->
<flag name="fill" value="0x77" />
<!-- Additional option that can be set to have the top and/or bottom edges of the child clipped to its container's bounds.
The clip will be based on the vertical gravity: a top gravity will clip the bottom edge, a bottom gravity will clip the top
edge, and neither will clip both edges. -->
<flag name="clip_vertical" value="0x80" />
<!-- Additional option that can be set to have the left and/or right edges of the child clipped to its container's bounds.
The clip will be based on the horizontal gravity: a left gravity will clip the right edge, a right gravity will clip the
left edge, and neither will clip both edges. -->
<flag name="clip_horizontal" value="0x08" />
</attr>
<attr name="indicator_background" format="reference|color" />
<attr name="row_background" format="reference|color" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style parent="@android:attr/listViewStyle" name="treeViewListStyle">
<item name="android:background">@android:color/white</item>
<item name="android:divider">@drawable/divider</item>
</style>
</resources>

View File

@ -0,0 +1,315 @@
package pl.polidea.treeview;
import android.app.Activity;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
/**
* Adapter used to feed the table view.
*
* @param <T>
* class for ID of the tree
*/
public abstract class AbstractTreeViewAdapter<T> extends BaseAdapter implements
ListAdapter {
private final TreeStateManager<T> treeStateManager;
private final int numberOfLevels;
private final LayoutInflater layoutInflater;
private int indentWidth = 0;
private int indicatorGravity = 0;
private Drawable collapsedDrawable;
private Drawable expandedDrawable;
private Drawable indicatorBackgroundDrawable;
private Drawable rowBackgroundDrawable;
private final OnClickListener indicatorClickListener = new OnClickListener() {
@Override
public void onClick(final View v) {
@SuppressWarnings("unchecked")
final T id = (T) v.getTag();
expandCollapse(id);
}
};
private boolean collapsible;
private final Activity activity;
public Activity getActivity() {
return activity;
}
protected TreeStateManager<T> getManager() {
return treeStateManager;
}
protected void expandCollapse(final T id) {
final TreeNodeInfo<T> info = treeStateManager.getNodeInfo(id);
if (!info.isWithChildren()) {
// ignore - no default action
return;
}
if (info.isExpanded()) {
treeStateManager.collapseChildren(id);
} else {
treeStateManager.expandDirectChildren(id);
}
}
private void calculateIndentWidth() {
if (expandedDrawable != null) {
indentWidth = Math.max(getIndentWidth(),
expandedDrawable.getIntrinsicWidth());
}
if (collapsedDrawable != null) {
indentWidth = Math.max(getIndentWidth(),
collapsedDrawable.getIntrinsicWidth());
}
}
public AbstractTreeViewAdapter(final Activity activity,
final TreeStateManager<T> treeStateManager, final int numberOfLevels) {
this.activity = activity;
this.treeStateManager = treeStateManager;
this.layoutInflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.numberOfLevels = numberOfLevels;
this.collapsedDrawable = null;
this.expandedDrawable = null;
this.rowBackgroundDrawable = null;
this.indicatorBackgroundDrawable = null;
}
@Override
public void registerDataSetObserver(final DataSetObserver observer) {
treeStateManager.registerDataSetObserver(observer);
}
@Override
public void unregisterDataSetObserver(final DataSetObserver observer) {
treeStateManager.unregisterDataSetObserver(observer);
}
@Override
public int getCount() {
return treeStateManager.getVisibleCount();
}
@Override
public Object getItem(final int position) {
return getItemId(position);
}
public T getTreeId(final int position) {
return treeStateManager.getVisibleList().get(position);
}
public TreeNodeInfo<T> getTreeNodeInfo(final int position) {
return treeStateManager.getNodeInfo(getTreeId(position));
}
@Override
public boolean hasStableIds() { // NOPMD
return true;
}
@Override
public int getItemViewType(final int position) {
return getTreeNodeInfo(position).getLevel();
}
@Override
public int getViewTypeCount() {
return numberOfLevels;
}
@Override
public boolean isEmpty() {
return getCount() == 0;
}
@Override
public boolean areAllItemsEnabled() { // NOPMD
return true;
}
@Override
public boolean isEnabled(final int position) { // NOPMD
return true;
}
protected int getTreeListItemWrapperId() {
return R.layout.tree_list_item_wrapper;
}
@Override
public final View getView(final int position, final View convertView,
final ViewGroup parent) {
final TreeNodeInfo<T> nodeInfo = getTreeNodeInfo(position);
if (convertView == null) {
final LinearLayout layout = (LinearLayout) layoutInflater.inflate(
getTreeListItemWrapperId(), null);
return populateTreeItem(layout, getNewChildView(nodeInfo),
nodeInfo, true);
} else {
final LinearLayout linear = (LinearLayout) convertView;
final FrameLayout frameLayout = (FrameLayout) linear
.findViewById(R.id.treeview_list_item_frame);
final View childView = frameLayout.getChildAt(0);
updateView(childView, nodeInfo);
return populateTreeItem(linear, childView, nodeInfo, false);
}
}
/**
* Called when new view is to be created.
*
* @param treeNodeInfo
* node info
* @return view that should be displayed as tree content
*/
public abstract View getNewChildView(TreeNodeInfo<T> treeNodeInfo);
/**
* Called when new view is going to be reused. You should update the view
* and fill it in with the data required to display the new information. You
* can also create a new view, which will mean that the old view will not be
* reused.
*
* @param view
* view that should be updated with the new values
* @param treeNodeInfo
* node info used to populate the view
* @return view to used as row indented content
*/
public abstract View updateView(View view, TreeNodeInfo<T> treeNodeInfo);
/**
* Retrieves background drawable for the node.
*
* @param treeNodeInfo
* node info
* @return drawable returned as background for the whole row. Might be null,
* then default background is used
*/
public Drawable getBackgroundDrawable(final TreeNodeInfo<T> treeNodeInfo) { // NOPMD
return null;
}
private Drawable getDrawableOrDefaultBackground(final Drawable r) {
if (r == null) {
return activity.getResources()
.getDrawable(R.drawable.list_selector_background).mutate();
} else {
return r;
}
}
public final LinearLayout populateTreeItem(final LinearLayout layout,
final View childView, final TreeNodeInfo<T> nodeInfo,
final boolean newChildView) {
final Drawable individualRowDrawable = getBackgroundDrawable(nodeInfo);
layout.setBackgroundDrawable(individualRowDrawable == null ? getDrawableOrDefaultBackground(rowBackgroundDrawable)
: individualRowDrawable);
final LinearLayout.LayoutParams indicatorLayoutParams = new LinearLayout.LayoutParams(
calculateIndentation(nodeInfo), LayoutParams.FILL_PARENT);
final LinearLayout indicatorLayout = (LinearLayout) layout
.findViewById(R.id.treeview_list_item_image_layout);
indicatorLayout.setGravity(indicatorGravity);
indicatorLayout.setLayoutParams(indicatorLayoutParams);
final ImageView image = (ImageView) layout
.findViewById(R.id.treeview_list_item_image);
image.setImageDrawable(getDrawable(nodeInfo));
image.setBackgroundDrawable(getDrawableOrDefaultBackground(indicatorBackgroundDrawable));
image.setScaleType(ScaleType.CENTER);
image.setTag(nodeInfo.getId());
if (nodeInfo.isWithChildren() && collapsible) {
image.setOnClickListener(indicatorClickListener);
} else {
image.setOnClickListener(null);
}
layout.setTag(nodeInfo.getId());
final FrameLayout frameLayout = (FrameLayout) layout
.findViewById(R.id.treeview_list_item_frame);
final FrameLayout.LayoutParams childParams = new FrameLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
if (newChildView) {
frameLayout.addView(childView, childParams);
}
frameLayout.setTag(nodeInfo.getId());
return layout;
}
protected int calculateIndentation(final TreeNodeInfo<T> nodeInfo) {
return getIndentWidth() * (nodeInfo.getLevel() + (collapsible ? 1 : 0));
}
private Drawable getDrawable(final TreeNodeInfo<T> nodeInfo) {
if (!nodeInfo.isWithChildren() || !collapsible) {
return getDrawableOrDefaultBackground(indicatorBackgroundDrawable);
}
if (nodeInfo.isExpanded()) {
return expandedDrawable;
} else {
return collapsedDrawable;
}
}
public void setIndicatorGravity(final int indicatorGravity) {
this.indicatorGravity = indicatorGravity;
}
public void setCollapsedDrawable(final Drawable collapsedDrawable) {
this.collapsedDrawable = collapsedDrawable;
calculateIndentWidth();
}
public void setExpandedDrawable(final Drawable expandedDrawable) {
this.expandedDrawable = expandedDrawable;
calculateIndentWidth();
}
public void setIndentWidth(final int indentWidth) {
this.indentWidth = indentWidth;
calculateIndentWidth();
}
public void setRowBackgroundDrawable(final Drawable rowBackgroundDrawable) {
this.rowBackgroundDrawable = rowBackgroundDrawable;
}
public void setIndicatorBackgroundDrawable(
final Drawable indicatorBackgroundDrawable) {
this.indicatorBackgroundDrawable = indicatorBackgroundDrawable;
}
public void setCollapsible(final boolean collapsible) {
this.collapsible = collapsible;
}
public void refresh() {
treeStateManager.refresh();
}
private int getIndentWidth() {
return indentWidth;
}
@SuppressWarnings("unchecked")
public void handleItemClick(final View view, final Object id) {
expandCollapse((T) id);
}
}

View File

@ -0,0 +1,116 @@
package pl.polidea.treeview;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
/**
* Node. It is package protected so that it cannot be used outside.
*
* @param <T>
* type of the identifier used by the tree
*/
class InMemoryTreeNode<T> implements Serializable {
private static final long serialVersionUID = 1L;
private final T id;
private final T parent;
private final int level;
private boolean visible = true;
private final List<InMemoryTreeNode<T>> children = new LinkedList<InMemoryTreeNode<T>>();
private List<T> childIdListCache = null;
public InMemoryTreeNode(final T id, final T parent, final int level,
final boolean visible) {
super();
this.id = id;
this.parent = parent;
this.level = level;
this.visible = visible;
}
public int indexOf(final T id) {
return getChildIdList().indexOf(id);
}
/**
* Cache is built lasily only if needed. The cache is cleaned on any
* structure change for that node!).
*
* @return list of ids of children
*/
public synchronized List<T> getChildIdList() {
if (childIdListCache == null) {
childIdListCache = new LinkedList<T>();
for (final InMemoryTreeNode<T> n : children) {
childIdListCache.add(n.getId());
}
}
return childIdListCache;
}
public boolean isVisible() {
return visible;
}
public void setVisible(final boolean visible) {
this.visible = visible;
}
public int getChildrenListSize() {
return children.size();
}
public synchronized InMemoryTreeNode<T> add(final int index, final T child,
final boolean visible) {
childIdListCache = null;
// Note! top levell children are always visible (!)
final InMemoryTreeNode<T> newNode = new InMemoryTreeNode<T>(child,
getId(), getLevel() + 1, getId() == null ? true : visible);
children.add(index, newNode);
return newNode;
}
/**
* Note. This method should technically return unmodifiable collection, but
* for performance reason on small devices we do not do it.
*
* @return children list
*/
public List<InMemoryTreeNode<T>> getChildren() {
return children;
}
public synchronized void clearChildren() {
children.clear();
childIdListCache = null;
}
public synchronized void removeChild(final T child) {
final int childIndex = indexOf(child);
if (childIndex != -1) {
children.remove(childIndex);
childIdListCache = null;
}
}
@Override
public String toString() {
return "InMemoryTreeNode [id=" + getId() + ", parent=" + getParent()
+ ", level=" + getLevel() + ", visible=" + visible
+ ", children=" + children + ", childIdListCache="
+ childIdListCache + "]";
}
T getId() {
return id;
}
T getParent() {
return parent;
}
int getLevel() {
return level;
}
}

View File

@ -0,0 +1,374 @@
package pl.polidea.treeview;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.database.DataSetObserver;
/**
* In-memory manager of tree state.
*
* @param <T>
* type of identifier
*/
public class InMemoryTreeStateManager<T> implements TreeStateManager<T> {
private static final long serialVersionUID = 1L;
private final Map<T, InMemoryTreeNode<T>> allNodes = new HashMap<T, InMemoryTreeNode<T>>();
private final InMemoryTreeNode<T> topSentinel = new InMemoryTreeNode<T>(
null, null, -1, true);
private transient List<T> visibleListCache = null; // lasy initialised
private transient List<T> unmodifiableVisibleList = null;
private boolean visibleByDefault = true;
private final transient Set<DataSetObserver> observers = new HashSet<DataSetObserver>();
private synchronized void internalDataSetChanged() {
visibleListCache = null;
unmodifiableVisibleList = null;
for (final DataSetObserver observer : observers) {
observer.onChanged();
}
}
/**
* If true new nodes are visible by default.
*
* @param visibleByDefault
* if true, then newly added nodes are expanded by default
*/
public void setVisibleByDefault(final boolean visibleByDefault) {
this.visibleByDefault = visibleByDefault;
}
private InMemoryTreeNode<T> getNodeFromTreeOrThrow(final T id) {
if (id == null) {
throw new NodeNotInTreeException("(null)");
}
final InMemoryTreeNode<T> node = allNodes.get(id);
if (node == null) {
throw new NodeNotInTreeException(id.toString());
}
return node;
}
private InMemoryTreeNode<T> getNodeFromTreeOrThrowAllowRoot(final T id) {
if (id == null) {
return topSentinel;
}
return getNodeFromTreeOrThrow(id);
}
private void expectNodeNotInTreeYet(final T id) {
final InMemoryTreeNode<T> node = allNodes.get(id);
if (node != null) {
throw new NodeAlreadyInTreeException(id.toString(), node.toString());
}
}
@Override
public synchronized TreeNodeInfo<T> getNodeInfo(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrow(id);
final List<InMemoryTreeNode<T>> children = node.getChildren();
boolean expanded = false;
if (!children.isEmpty() && children.get(0).isVisible()) {
expanded = true;
}
return new TreeNodeInfo<T>(id, node.getLevel(), !children.isEmpty(),
node.isVisible(), expanded);
}
@Override
public synchronized List<T> getChildren(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
return node.getChildIdList();
}
@Override
public synchronized T getParent(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
return node.getParent();
}
private boolean getChildrenVisibility(final InMemoryTreeNode<T> node) {
boolean visibility;
final List<InMemoryTreeNode<T>> children = node.getChildren();
if (children.isEmpty()) {
visibility = visibleByDefault;
} else {
visibility = children.get(0).isVisible();
}
return visibility;
}
@Override
public synchronized void addBeforeChild(final T parent, final T newChild,
final T beforeChild) {
expectNodeNotInTreeYet(newChild);
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(parent);
final boolean visibility = getChildrenVisibility(node);
// top nodes are always expanded.
if (beforeChild == null) {
final InMemoryTreeNode<T> added = node.add(0, newChild, visibility);
allNodes.put(newChild, added);
} else {
final int index = node.indexOf(beforeChild);
final InMemoryTreeNode<T> added = node.add(index == -1 ? 0 : index,
newChild, visibility);
allNodes.put(newChild, added);
}
if (visibility) {
internalDataSetChanged();
}
}
@Override
public synchronized void addAfterChild(final T parent, final T newChild,
final T afterChild) {
expectNodeNotInTreeYet(newChild);
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(parent);
final boolean visibility = getChildrenVisibility(node);
if (afterChild == null) {
final InMemoryTreeNode<T> added = node.add(
node.getChildrenListSize(), newChild, visibility);
allNodes.put(newChild, added);
} else {
final int index = node.indexOf(afterChild);
final InMemoryTreeNode<T> added = node.add(
index == -1 ? node.getChildrenListSize() : index, newChild,
visibility);
allNodes.put(newChild, added);
}
if (visibility) {
internalDataSetChanged();
}
}
@Override
public synchronized void removeNodeRecursively(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
final boolean visibleNodeChanged = removeNodeRecursively(node);
final T parent = node.getParent();
final InMemoryTreeNode<T> parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
parentNode.removeChild(id);
if (visibleNodeChanged) {
internalDataSetChanged();
}
}
private boolean removeNodeRecursively(final InMemoryTreeNode<T> node) {
boolean visibleNodeChanged = false;
for (final InMemoryTreeNode<T> child : node.getChildren()) {
if (removeNodeRecursively(child)) {
visibleNodeChanged = true;
}
}
node.clearChildren();
if (node.getId() != null) {
allNodes.remove(node.getId());
if (node.isVisible()) {
visibleNodeChanged = true;
}
}
return visibleNodeChanged;
}
private void setChildrenVisibility(final InMemoryTreeNode<T> node,
final boolean visible, final boolean recursive) {
for (final InMemoryTreeNode<T> child : node.getChildren()) {
child.setVisible(visible);
if (recursive) {
setChildrenVisibility(child, visible, true);
}
}
}
@Override
public synchronized void expandDirectChildren(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
setChildrenVisibility(node, true, false);
internalDataSetChanged();
}
@Override
public synchronized void expandEverythingBelow(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
setChildrenVisibility(node, true, true);
internalDataSetChanged();
}
@Override
public synchronized void collapseChildren(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
if (node == topSentinel) {
for (final InMemoryTreeNode<T> n : topSentinel.getChildren()) {
setChildrenVisibility(n, false, true);
}
} else {
setChildrenVisibility(node, false, true);
}
internalDataSetChanged();
}
@Override
public synchronized T getNextSibling(final T id) {
final T parent = getParent(id);
final InMemoryTreeNode<T> parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
boolean returnNext = false;
for (final InMemoryTreeNode<T> child : parentNode.getChildren()) {
if (returnNext) {
return child.getId();
}
if (child.getId().equals(id)) {
returnNext = true;
}
}
return null;
}
@Override
public synchronized T getPreviousSibling(final T id) {
final T parent = getParent(id);
final InMemoryTreeNode<T> parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
final T previousSibling = null;
for (final InMemoryTreeNode<T> child : parentNode.getChildren()) {
if (child.getId().equals(id)) {
return previousSibling;
}
}
return null;
}
@Override
public synchronized boolean isInTree(final T id) {
return allNodes.containsKey(id);
}
@Override
public synchronized int getVisibleCount() {
return getVisibleList().size();
}
@Override
public synchronized List<T> getVisibleList() {
T currentId = null;
if (visibleListCache == null) {
visibleListCache = new ArrayList<T>(allNodes.size());
do {
currentId = getNextVisible(currentId);
if (currentId == null) {
break;
} else {
visibleListCache.add(currentId);
}
} while (true);
}
if (unmodifiableVisibleList == null) {
unmodifiableVisibleList = Collections
.unmodifiableList(visibleListCache);
}
return unmodifiableVisibleList;
}
public synchronized T getNextVisible(final T id) {
final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
if (!node.isVisible()) {
return null;
}
final List<InMemoryTreeNode<T>> children = node.getChildren();
if (!children.isEmpty()) {
final InMemoryTreeNode<T> firstChild = children.get(0);
if (firstChild.isVisible()) {
return firstChild.getId();
}
}
final T sibl = getNextSibling(id);
if (sibl != null) {
return sibl;
}
T parent = node.getParent();
do {
if (parent == null) {
return null;
}
final T parentSibling = getNextSibling(parent);
if (parentSibling != null) {
return parentSibling;
}
parent = getNodeFromTreeOrThrow(parent).getParent();
} while (true);
}
@Override
public synchronized void registerDataSetObserver(
final DataSetObserver observer) {
observers.add(observer);
}
@Override
public synchronized void unregisterDataSetObserver(
final DataSetObserver observer) {
observers.remove(observer);
}
@Override
public int getLevel(final T id) {
return getNodeFromTreeOrThrow(id).getLevel();
}
@Override
public Integer[] getHierarchyDescription(final T id) {
final int level = getLevel(id);
final Integer[] hierarchy = new Integer[level + 1];
int currentLevel = level;
T currentId = id;
T parent = getParent(currentId);
while (currentLevel >= 0) {
hierarchy[currentLevel--] = getChildren(parent).indexOf(currentId);
currentId = parent;
parent = getParent(parent);
}
return hierarchy;
}
private void appendToSb(final StringBuilder sb, final T id) {
if (id != null) {
final TreeNodeInfo<T> node = getNodeInfo(id);
final int indent = node.getLevel() * 4;
final char[] indentString = new char[indent];
Arrays.fill(indentString, ' ');
sb.append(indentString);
sb.append(node.toString());
sb.append(Arrays.asList(getHierarchyDescription(id)).toString());
sb.append("\n");
}
final List<T> children = getChildren(id);
for (final T child : children) {
appendToSb(sb, child);
}
}
@Override
public synchronized String toString() {
final StringBuilder sb = new StringBuilder();
appendToSb(sb, null);
return sb.toString();
}
@Override
public synchronized void clear() {
allNodes.clear();
topSentinel.clearChildren();
internalDataSetChanged();
}
@Override
public void refresh() {
internalDataSetChanged();
}
}

View File

@ -0,0 +1,14 @@
package pl.polidea.treeview;
/**
* The node being added is already in the tree.
*
*/
public class NodeAlreadyInTreeException extends RuntimeException {
private static final long serialVersionUID = 1L;
public NodeAlreadyInTreeException(final String id, final String oldNode) {
super("The node has already been added to the tree: " + id + ". Old node is:" + oldNode);
}
}

View File

@ -0,0 +1,15 @@
package pl.polidea.treeview;
/**
* This exception is thrown when the tree does not contain node requested.
*
*/
public class NodeNotInTreeException extends RuntimeException {
private static final long serialVersionUID = 1L;
public NodeNotInTreeException(final String id) {
super("The tree does not contain the node specified: " + id);
}
}

View File

@ -0,0 +1,126 @@
package pl.polidea.treeview;
import android.util.Log;
/**
* Allows to build tree easily in sequential mode (you have to know levels of
* all the tree elements upfront). You should rather use this class rather than
* manager if you build initial tree from some external data source.
*
* @param <T>
*/
public class TreeBuilder<T> {
private static final String TAG = TreeBuilder.class.getSimpleName();
private final TreeStateManager<T> manager;
private T lastAddedId = null;
private int lastLevel = -1;
public TreeBuilder(final TreeStateManager<T> manager) {
this.manager = manager;
}
public void clear() {
manager.clear();
}
/**
* Adds new relation to existing tree. Child is set as the last child of the
* parent node. Parent has to already exist in the tree, child cannot yet
* exist. This method is mostly useful in case you add entries layer by
* layer - i.e. first top level entries, then children for all parents, then
* grand-children and so on.
*
* @param parent
* parent id
* @param child
* child id
*/
public synchronized void addRelation(final T parent, final T child) {
Log.d(TAG, "Adding relation parent:" + parent + " -> child: " + child);
manager.addAfterChild(parent, child, null);
lastAddedId = child;
lastLevel = manager.getLevel(child);
}
/**
* Adds sequentially new node. Using this method is the simplest way of
* building tree - if you have all the elements in the sequence as they
* should be displayed in fully-expanded tree. You can combine it with add
* relation - for example you can add information about few levels using
* {@link addRelation} and then after the right level is added as parent,
* you can continue adding them using sequential operation.
*
* @param id
* id of the node
* @param level
* its level
*/
public synchronized void sequentiallyAddNextNode(final T id, final int level) {
Log.d(TAG, "Adding sequentiall node " + id + " at level " + level);
if (lastAddedId == null) {
addNodeToParentOneLevelDown(null, id, level);
} else {
if (level <= lastLevel) {
final T parent = findParentAtLevel(lastAddedId, level - 1);
addNodeToParentOneLevelDown(parent, id, level);
} else {
addNodeToParentOneLevelDown(lastAddedId, id, level);
}
}
}
/**
* Find parent of the node at the level specified.
*
* @param node
* node from which we start
* @param levelToFind
* level which we are looking for
* @return the node found (null if it is topmost node).
*/
private T findParentAtLevel(final T node, final int levelToFind) {
T parent = manager.getParent(node);
while (parent != null) {
if (manager.getLevel(parent) == levelToFind) {
break;
}
parent = manager.getParent(parent);
}
return parent;
}
/**
* Adds note to parent at the level specified. But it verifies that the
* level is one level down than the parent!
*
* @param parent
* parent parent
* @param id
* new node id
* @param level
* should always be parent's level + 1
*/
private void addNodeToParentOneLevelDown(final T parent, final T id,
final int level) {
if (parent == null && level != 0) {
throw new TreeConfigurationException("Trying to add new id " + id
+ " to top level with level != 0 (" + level + ")");
}
if (parent != null && manager.getLevel(parent) != level - 1) {
throw new TreeConfigurationException("Trying to add new id " + id
+ " <" + level + "> to " + parent + " <"
+ manager.getLevel(parent)
+ ">. The difference in levels up is bigger than 1.");
}
manager.addAfterChild(parent, id, null);
setLastAdded(id, level);
}
private void setLastAdded(final T id, final int level) {
lastAddedId = id;
lastLevel = level;
}
}

View File

@ -0,0 +1,15 @@
package pl.polidea.treeview;
/**
* Exception thrown when there is a problem with configuring tree.
*
*/
public class TreeConfigurationException extends RuntimeException {
private static final long serialVersionUID = 1L;
public TreeConfigurationException(final String detailMessage) {
super(detailMessage);
}
}

View File

@ -0,0 +1,69 @@
package pl.polidea.treeview;
/**
* Information about the node.
*
* @param <T>
* type of the id for the tree
*/
public class TreeNodeInfo<T> {
private final T id;
private final int level;
private final boolean withChildren;
private final boolean visible;
private final boolean expanded;
/**
* Creates the node information.
*
* @param id
* id of the node
* @param level
* level of the node
* @param withChildren
* whether the node has children.
* @param visible
* whether the tree node is visible.
* @param expanded
* whether the tree node is expanded
*
*/
public TreeNodeInfo(final T id, final int level,
final boolean withChildren, final boolean visible,
final boolean expanded) {
super();
this.id = id;
this.level = level;
this.withChildren = withChildren;
this.visible = visible;
this.expanded = expanded;
}
public T getId() {
return id;
}
public boolean isWithChildren() {
return withChildren;
}
public boolean isVisible() {
return visible;
}
public boolean isExpanded() {
return expanded;
}
public int getLevel() {
return level;
}
@Override
public String toString() {
return "TreeNodeInfo [id=" + id + ", level=" + level
+ ", withChildren=" + withChildren + ", visible=" + visible
+ ", expanded=" + expanded + "]";
}
}

View File

@ -0,0 +1,193 @@
package pl.polidea.treeview;
import java.io.Serializable;
import java.util.List;
import android.database.DataSetObserver;
/**
* Manages information about state of the tree. It only keeps information about
* tree elements, not the elements themselves.
*
* @param <T>
* type of the identifier for nodes in the tree
*/
public interface TreeStateManager<T> extends Serializable {
/**
* Returns array of integers showing the location of the node in hierarchy.
* It corresponds to heading numbering. {0,0,0} in 3 level node is the first
* node {0,0,1} is second leaf (assuming that there are two leaves in first
* subnode of the first node).
*
* @param id
* id of the node
* @return textual description of the hierarchy in tree for the node.
*/
Integer[] getHierarchyDescription(T id);
/**
* Returns level of the node.
*
* @param id
* id of the node
* @return level in the tree
*/
int getLevel(T id);
/**
* Returns information about the node.
*
* @param id
* node id
* @return node info
*/
TreeNodeInfo<T> getNodeInfo(T id);
/**
* Returns children of the node.
*
* @param id
* id of the node or null if asking for top nodes
* @return children of the node
*/
List<T> getChildren(T id);
/**
* Returns parent of the node.
*
* @param id
* id of the node
* @return parent id or null if no parent
*/
T getParent(T id);
/**
* Adds the node before child or at the beginning.
*
* @param parent
* id of the parent node. If null - adds at the top level
* @param newChild
* new child to add if null - adds at the beginning.
* @param beforeChild
* child before which to add the new child
*/
void addBeforeChild(T parent, T newChild, T beforeChild);
/**
* Adds the node after child or at the end.
*
* @param parent
* id of the parent node. If null - adds at the top level.
* @param newChild
* new child to add. If null - adds at the end.
* @param afterChild
* child after which to add the new child
*/
void addAfterChild(T parent, T newChild, T afterChild);
/**
* Removes the node and all children from the tree.
*
* @param id
* id of the node to remove or null if all nodes are to be
* removed.
*/
void removeNodeRecursively(T id);
/**
* Expands all children of the node.
*
* @param id
* node which children should be expanded. cannot be null (top
* nodes are always expanded!).
*/
void expandDirectChildren(T id);
/**
* Expands everything below the node specified. Might be null - then expands
* all.
*
* @param id
* node which children should be expanded or null if all nodes
* are to be expanded.
*/
void expandEverythingBelow(T id);
/**
* Collapse children.
*
* @param id
* id collapses everything below node specified. If null,
* collapses everything but top-level nodes.
*/
void collapseChildren(T id);
/**
* Returns next sibling of the node (or null if no further sibling).
*
* @param id
* node id
* @return the sibling (or null if no next)
*/
T getNextSibling(T id);
/**
* Returns previous sibling of the node (or null if no previous sibling).
*
* @param id
* node id
* @return the sibling (or null if no previous)
*/
T getPreviousSibling(T id);
/**
* Checks if given node is already in tree.
*
* @param id
* id of the node
* @return true if node is already in tree.
*/
boolean isInTree(T id);
/**
* Count visible elements.
*
* @return number of currently visible elements.
*/
int getVisibleCount();
/**
* Returns visible node list.
*
* @return return the list of all visible nodes in the right sequence
*/
List<T> getVisibleList();
/**
* Registers observers with the manager.
*
* @param observer
* observer
*/
void registerDataSetObserver(final DataSetObserver observer);
/**
* Unregisters observers with the manager.
*
* @param observer
* observer
*/
void unregisterDataSetObserver(final DataSetObserver observer);
/**
* Cleans tree stored in manager. After this operation the tree is empty.
*
*/
void clear();
/**
* Refreshes views connected to the manager.
*/
void refresh();
}

View File

@ -0,0 +1,198 @@
package pl.polidea.treeview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
/**
* Tree view, expandable multi-level.
*
* <pre>
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_collapsible
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_src_expanded
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_src_collapsed
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_indent_width
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_handle_trackball_press
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_indicator_gravity
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_indicator_background
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_row_background
* </pre>
*/
public class TreeViewList extends ListView {
private static final int DEFAULT_COLLAPSED_RESOURCE = R.drawable.collapsed;
private static final int DEFAULT_EXPANDED_RESOURCE = R.drawable.expanded;
private static final int DEFAULT_INDENT = 0;
private static final int DEFAULT_GRAVITY = Gravity.LEFT
| Gravity.CENTER_VERTICAL;
private Drawable expandedDrawable;
private Drawable collapsedDrawable;
private Drawable rowBackgroundDrawable;
private Drawable indicatorBackgroundDrawable;
private int indentWidth = 0;
private int indicatorGravity = 0;
private AbstractTreeViewAdapter< ? > treeAdapter;
private boolean collapsible;
private boolean handleTrackballPress;
public TreeViewList(final Context context, final AttributeSet attrs) {
this(context, attrs, R.style.treeViewListStyle);
}
public TreeViewList(final Context context) {
this(context, null);
}
public TreeViewList(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
parseAttributes(context, attrs);
}
private void parseAttributes(final Context context, final AttributeSet attrs) {
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.TreeViewList);
expandedDrawable = a.getDrawable(R.styleable.TreeViewList_src_expanded);
if (expandedDrawable == null) {
expandedDrawable = context.getResources().getDrawable(
DEFAULT_EXPANDED_RESOURCE);
}
collapsedDrawable = a
.getDrawable(R.styleable.TreeViewList_src_collapsed);
if (collapsedDrawable == null) {
collapsedDrawable = context.getResources().getDrawable(
DEFAULT_COLLAPSED_RESOURCE);
}
indentWidth = a.getDimensionPixelSize(
R.styleable.TreeViewList_indent_width, DEFAULT_INDENT);
indicatorGravity = a.getInteger(
R.styleable.TreeViewList_indicator_gravity, DEFAULT_GRAVITY);
indicatorBackgroundDrawable = a
.getDrawable(R.styleable.TreeViewList_indicator_background);
rowBackgroundDrawable = a
.getDrawable(R.styleable.TreeViewList_row_background);
collapsible = a.getBoolean(R.styleable.TreeViewList_collapsible, true);
handleTrackballPress = a.getBoolean(
R.styleable.TreeViewList_handle_trackball_press, true);
}
@Override
public void setAdapter(final ListAdapter adapter) {
if (!(adapter instanceof AbstractTreeViewAdapter)) {
throw new TreeConfigurationException(
"The adapter is not of TreeViewAdapter type");
}
treeAdapter = (AbstractTreeViewAdapter< ? >) adapter;
syncAdapter();
super.setAdapter(treeAdapter);
}
private void syncAdapter() {
treeAdapter.setCollapsedDrawable(collapsedDrawable);
treeAdapter.setExpandedDrawable(expandedDrawable);
treeAdapter.setIndicatorGravity(indicatorGravity);
treeAdapter.setIndentWidth(indentWidth);
treeAdapter.setIndicatorBackgroundDrawable(indicatorBackgroundDrawable);
treeAdapter.setRowBackgroundDrawable(rowBackgroundDrawable);
treeAdapter.setCollapsible(collapsible);
if (handleTrackballPress) {
setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(final AdapterView< ? > parent,
final View view, final int position, final long id) {
treeAdapter.handleItemClick(view, view.getTag());
}
});
} else {
setOnClickListener(null);
}
}
public void setExpandedDrawable(final Drawable expandedDrawable) {
this.expandedDrawable = expandedDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setCollapsedDrawable(final Drawable collapsedDrawable) {
this.collapsedDrawable = collapsedDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setRowBackgroundDrawable(final Drawable rowBackgroundDrawable) {
this.rowBackgroundDrawable = rowBackgroundDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setIndicatorBackgroundDrawable(
final Drawable indicatorBackgroundDrawable) {
this.indicatorBackgroundDrawable = indicatorBackgroundDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setIndentWidth(final int indentWidth) {
this.indentWidth = indentWidth;
syncAdapter();
treeAdapter.refresh();
}
public void setIndicatorGravity(final int indicatorGravity) {
this.indicatorGravity = indicatorGravity;
syncAdapter();
treeAdapter.refresh();
}
public void setCollapsible(final boolean collapsible) {
this.collapsible = collapsible;
syncAdapter();
treeAdapter.refresh();
}
public void setHandleTrackballPress(final boolean handleTrackballPress) {
this.handleTrackballPress = handleTrackballPress;
syncAdapter();
treeAdapter.refresh();
}
public Drawable getExpandedDrawable() {
return expandedDrawable;
}
public Drawable getCollapsedDrawable() {
return collapsedDrawable;
}
public Drawable getRowBackgroundDrawable() {
return rowBackgroundDrawable;
}
public Drawable getIndicatorBackgroundDrawable() {
return indicatorBackgroundDrawable;
}
public int getIndentWidth() {
return indentWidth;
}
public int getIndicatorGravity() {
return indicatorGravity;
}
public boolean isCollapsible() {
return collapsible;
}
public boolean isHandleTrackballPress() {
return handleTrackballPress;
}
}

View File

@ -0,0 +1,24 @@
<html>
<body>
This is a small utility that provides quite configurable tree view list.
It is based on standard android list view. It separates out different
aspects of the tree: there is a separate list view, tree adapter, tree
state manager and tree state builder.
<p>
<ul>
<li>Tree view provides the frame to display the view.</li>
<li>Adapter allows to create visual representation of each tree
node.</li>
<li>State manager provides storage for tree state (connections
between parents and children, collapsed/expanded state). It provides
all the low-level tree manipulation methods.</li>
<li>Tree builder allows to build tree easily providing higher
level methods. The tree can be build either from prepared sequentially
prepared list of nodes (node id, level) or using (parent/child
relationships).</li>
<p>For now only in-memory state manager is provided, but Tree State
Manger interface is done in the way that database tree manager even for
large trees is potentially supported.
</ul>
</body>
</html>

View File

@ -0,0 +1,4 @@
/**
* Provides expandable Tree View implementation.
*/
package pl.polidea.treeview;