├── .gitignore ├── README.md ├── build.gradle ├── compoundadapter-sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── negusoft │ │ └── compoundadapter │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── negusoft │ │ │ └── compoundadapter │ │ │ ├── MainActivity.java │ │ │ ├── SampleActivity.java │ │ │ ├── adapter │ │ │ ├── DynamicDataAdapter.java │ │ │ ├── StaticDataAdapter.java │ │ │ └── TreeNodeAdapter.java │ │ │ ├── data │ │ │ └── Samples.java │ │ │ └── fragment │ │ │ ├── AdapterGroupTreeFragment.java │ │ │ ├── AdapterGroupWithChangingData.java │ │ │ ├── AdapterGroupWithHeaderFragment.java │ │ │ ├── AdapterGroupWithStableIds.java │ │ │ └── MainListFragment.java │ └── res │ │ ├── drawable-xxhdpi │ │ └── ic_github.png │ │ ├── drawable │ │ └── selectable_item.xml │ │ ├── layout │ │ ├── item_content.xml │ │ ├── item_sample_list_title.xml │ │ ├── main_content.xml │ │ ├── main_layout.xml │ │ ├── recyclerview.xml │ │ ├── sample_layout.xml │ │ └── tree_fragment.xml │ │ ├── menu │ │ └── adaptergroup_with_header.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── negusoft │ └── compoundadapter │ └── ExampleUnitTest.java ├── compoundadapter ├── .gitignore ├── bintray-upload.gradle ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── negusoft │ │ └── compountadapter │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── negusoft │ │ └── compountadapter │ │ └── recyclerview │ │ ├── AdapterGroup.java │ │ ├── AdapterPosition.java │ │ └── SingleAdapter.java │ └── test │ └── java │ └── com │ └── negusoft │ └── compountadapter │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Intellij project files 26 | *.iml 27 | *.ipr 28 | *.iws 29 | .idea/ 30 | *.prefs 31 | 32 | # Gradle 33 | build/ 34 | .gradle 35 | 36 | # Mac files 37 | .DS_Store 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | - Project: CompoundAdapter 2 | - Developer: Borja Lopez Urkidi 3 | - Organization: Negusoft 4 | - Web: http://www.negusoft.com 5 | 6 | 7 | Description 8 | =========== 9 | 10 | Android library that provides a way to define a RecyclerView.Adapter out of subadapters (AdapterGroup). 11 | 12 | 13 | Features 14 | ======== 15 | - AdapterGroup: Adapter containing inner addapters. 16 | - Nesting: AdapterGroups can have other AdapterGroups 17 | - Performance: Child adapters of the same type can recycle ViewHolders (as expected) 18 | - Edit the adapter structure while the AdapterGroup is attached to the RecyclerView 19 | 20 | 21 | Basic Setup 22 | =========== 23 | 24 | You can add CompoundAdapter to your project using the following gradle dependency: 25 | 26 | or Gradle: 27 | ```groovy 28 | compile 'com.negusoft.compoundadapter:compoundadapter:0.8.0' 29 | ``` 30 | 31 | You can then use the AdapterGroup class to combine RecyclerView.Adapters. For example the following code will display the content of two adapters in the same RecyclerView: 32 | 33 | ``` java 34 | RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recyclerview); 35 | 36 | AdapterGroup adapterGroup = new AdapterGroup(); 37 | adapterGroup.addAdapter(new FirstAdapter()); 38 | adapterGroup.addAdapter(new SecondAdapter()); 39 | 40 | recyclerView.setAdapter(adapterGroup); 41 | ``` 42 | 43 | 44 | Relative Item Positions 45 | ======================= 46 | 47 | Keep in mind that if you are using the ViewHolders position (getAdapterPosition(), getLayoutPosition()), the reported values are relative to the origin. That is, in the previous example, the onClick() implemented in the first item of the SecondAdapter will not report a position 0. 48 | 49 | 50 | Adapter Types and Performance 51 | ============================= 52 | 53 | A good thing about RecyclerViews is that they reuse ViewHolder to optimize performance. In order not to loose this optimizations, AdapterGroup groups RecyclerView.Adapters by type, such a way that they can reuse each others ViewHolders. This way, if you add two instances of MyBookAdapter, for example, the second instance might bind a ViewHolder that was instantiated by the first one. 54 | 55 | By default, adapters are grouped by their java class. If you want more control on this, you may add them to the AdapterGroup by using addAdapter(adapter, type). This way, you can make adapters of different type share ViewHolders. Or the oposite, avoid that two adapters share ViewHolders. 56 | 57 | 58 | Updateing Data 59 | ============== 60 | 61 | You can update the data with the usual methods: notifyDataSetChanged(), notifyItemInserted()... You can use this methods on the AdapterGroup directly or call the child Adapters. 62 | 63 | Just make sure that the positions passed to the notifyItemXxx() method are relative to that specific adapter. That is, to remove the fist item in the Second adapter might be done like this: 64 | 65 | ``` java 66 | // Remove the first item of secondAdapter 67 | secondAdapter.notifyItemRangeRemoved(0); 68 | 69 | // Same operation, given that firstAdapter has 5 items 70 | adapterGroup.notifyItemRangeRemoved(5); 71 | ``` 72 | 73 | 74 | License 75 | ======= 76 | 77 | Copyright 2016 Negusoft 78 | 79 | Licensed under the Apache License, Version 2.0 (the "License"); 80 | you may not use this file except in compliance with the License. 81 | You may obtain a copy of the License at 82 | 83 | http://www.apache.org/licenses/LICENSE-2.0 84 | 85 | Unless required by applicable law or agreed to in writing, software 86 | distributed under the License is distributed on an "AS IS" BASIS, 87 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 88 | See the License for the specific language governing permissions and 89 | limitations under the License. 90 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.1.2' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | jcenter() 17 | } 18 | } 19 | 20 | task clean(type: Delete) { 21 | delete rootProject.buildDir 22 | } 23 | -------------------------------------------------------------------------------- /compoundadapter-sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /compoundadapter-sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "24.0.0" 6 | 7 | defaultConfig { 8 | applicationId "com.negusoft.compoundadapter" 9 | minSdkVersion 17 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile project(":compoundadapter") 24 | 25 | compile 'com.android.support:appcompat-v7:23.4.0' 26 | compile 'com.android.support:design:23.4.0' 27 | 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /compoundadapter-sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/blurkidi/dev/android_sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/androidTest/java/com/negusoft/compoundadapter/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/java/com/negusoft/compoundadapter/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.View; 11 | 12 | public class MainActivity extends AppCompatActivity { 13 | 14 | private static final String REPO_URL = "https://github.com/negusoft/CompoundAdapter-android"; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.main_layout); 20 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 21 | setSupportActionBar(toolbar); 22 | 23 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 24 | fab.setOnClickListener(new View.OnClickListener() { 25 | @Override 26 | public void onClick(View view) { 27 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(REPO_URL)); 28 | startActivity(intent); 29 | } 30 | }); 31 | 32 | findViewById(R.id.adapterGroupWithHeader).setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | Intent intent = SampleActivity.makeIntent(MainActivity.this, SampleActivity.SampleType.ADAPTER_GROUP_WITH_HEADER); 36 | startActivity(intent); 37 | } 38 | }); 39 | findViewById(R.id.adapterGroupWithChangingData).setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | Intent intent = SampleActivity.makeIntent(MainActivity.this, SampleActivity.SampleType.ADAPTER_GROUP_WITH_CHANGING_DATA); 43 | startActivity(intent); 44 | } 45 | }); 46 | findViewById(R.id.adapterGroupWithStableIds).setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | Intent intent = SampleActivity.makeIntent(MainActivity.this, SampleActivity.SampleType.ADAPTER_GROUP_WITH_STABLE_IDS); 50 | startActivity(intent); 51 | } 52 | }); 53 | findViewById(R.id.adapterGroupTree).setOnClickListener(new View.OnClickListener() { 54 | @Override 55 | public void onClick(View v) { 56 | Intent intent = SampleActivity.makeIntent(MainActivity.this, SampleActivity.SampleType.ADAPTER_GROUP_TREE); 57 | startActivity(intent); 58 | } 59 | }); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/java/com/negusoft/compoundadapter/SampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.design.widget.FloatingActionButton; 7 | import android.support.design.widget.Snackbar; 8 | import android.support.v4.app.Fragment; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.Toolbar; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | 14 | import com.negusoft.compoundadapter.fragment.AdapterGroupTreeFragment; 15 | import com.negusoft.compoundadapter.fragment.AdapterGroupWithChangingData; 16 | import com.negusoft.compoundadapter.fragment.AdapterGroupWithHeaderFragment; 17 | import com.negusoft.compoundadapter.fragment.AdapterGroupWithStableIds; 18 | import com.negusoft.compoundadapter.fragment.MainListFragment; 19 | 20 | public class SampleActivity extends AppCompatActivity { 21 | 22 | private static String EXTRA_SAMPLE_TYPE = "EXTRA_SAMPLE_TYPE"; 23 | 24 | public enum SampleType { 25 | ADAPTER_GROUP_WITH_HEADER, 26 | ADAPTER_GROUP_WITH_CHANGING_DATA, 27 | ADAPTER_GROUP_WITH_STABLE_IDS, 28 | ADAPTER_GROUP_TREE 29 | } 30 | 31 | public static Intent makeIntent(Context c, SampleType sampleType) { 32 | Intent result = new Intent(c, SampleActivity.class) 33 | .putExtra(EXTRA_SAMPLE_TYPE, sampleType.name()); 34 | return result; 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.sample_layout); 41 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 42 | setSupportActionBar(toolbar); 43 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 44 | 45 | if (savedInstanceState == null) { 46 | String typeName = getIntent().getStringExtra(EXTRA_SAMPLE_TYPE); 47 | SampleType type = SampleType.valueOf(typeName); 48 | addFragment(type); 49 | } 50 | } 51 | 52 | @Override 53 | public boolean onOptionsItemSelected(MenuItem item) { 54 | switch (item.getItemId()) { 55 | case android.R.id.home: 56 | finish(); 57 | return true; 58 | default: 59 | return super.onOptionsItemSelected(item); 60 | } 61 | } 62 | 63 | private void addFragment(SampleType type) { 64 | switch (type) { 65 | case ADAPTER_GROUP_WITH_HEADER: 66 | addFragment(AdapterGroupWithHeaderFragment.newInstance()); 67 | break; 68 | case ADAPTER_GROUP_WITH_CHANGING_DATA: 69 | addFragment(AdapterGroupWithChangingData.newInstance()); 70 | break; 71 | case ADAPTER_GROUP_WITH_STABLE_IDS: 72 | addFragment(AdapterGroupWithStableIds.newInstance()); 73 | break; 74 | case ADAPTER_GROUP_TREE: 75 | addFragment(AdapterGroupTreeFragment.newInstance()); 76 | break; 77 | } 78 | } 79 | 80 | private void addFragment(Fragment fragment) { 81 | getSupportFragmentManager() 82 | .beginTransaction() 83 | .add(R.id.content, fragment) 84 | .commit(); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/java/com/negusoft/compoundadapter/adapter/DynamicDataAdapter.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import android.widget.Toast; 9 | 10 | import com.negusoft.compoundadapter.R; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * RecyclerView adapter displaying a static list of items 17 | */ 18 | public class DynamicDataAdapter extends RecyclerView.Adapter { 19 | 20 | public interface ItemSelectedListener { 21 | void onItemSelected(Item item); 22 | } 23 | 24 | public static class Item { 25 | public final String value; 26 | public Item(String value) { 27 | this.value = value; 28 | } 29 | } 30 | 31 | static final class ViewHolder extends RecyclerView.ViewHolder { 32 | 33 | TextView textView; 34 | Item item; 35 | 36 | ViewHolder(View itemView) { 37 | super(itemView); 38 | textView = (TextView)itemView.findViewById(android.R.id.text1); 39 | } 40 | 41 | void setOntItemSelectedListener(final ItemSelectedListener listener) { 42 | if (listener == null) { 43 | textView.setOnClickListener(null); 44 | } else { 45 | textView.setOnClickListener(new View.OnClickListener() { 46 | @Override 47 | public void onClick(View v) { 48 | listener.onItemSelected(item); 49 | } 50 | }); 51 | } 52 | } 53 | 54 | void setItem(Item item) { 55 | this.item = item; 56 | textView.setText(item.value); 57 | } 58 | 59 | String getText() { 60 | return textView.getText().toString(); 61 | } 62 | } 63 | 64 | private final List mItems; 65 | private ItemSelectedListener mItemSelectedListener; 66 | 67 | public DynamicDataAdapter() { 68 | mItems = new ArrayList<>(10); 69 | } 70 | 71 | public DynamicDataAdapter(List values) { 72 | mItems = new ArrayList<>(values.size()); 73 | for (String value : values) { 74 | mItems.add(new Item(value)); 75 | } 76 | } 77 | 78 | public Item addItem(String value) { 79 | return addItem(value, true); 80 | } 81 | 82 | public Item addItem(String value, boolean notify) { 83 | Item item = new Item(value); 84 | mItems.add(item); 85 | if (notify) { 86 | notifyItemInserted(mItems.size() - 1); 87 | } 88 | 89 | return item; 90 | } 91 | 92 | public void removeItem(Item item) { 93 | removeItem(item, true); 94 | } 95 | 96 | public void removeItem(Item item, boolean notify) { 97 | int index = mItems.indexOf(item); 98 | if (index >= 0) { 99 | mItems.remove(index); 100 | if (notify) { 101 | notifyItemRemoved(index); 102 | } 103 | } 104 | } 105 | 106 | public void setItemSelectedListener(ItemSelectedListener listener) { 107 | mItemSelectedListener = listener; 108 | } 109 | 110 | @Override 111 | public DynamicDataAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 112 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 113 | View view = inflater.inflate(R.layout.item_content, parent, false); 114 | return new DynamicDataAdapter.ViewHolder(view); 115 | } 116 | 117 | @Override 118 | public void onBindViewHolder(final DynamicDataAdapter.ViewHolder holder, int position) { 119 | holder.setItem(mItems.get(position)); 120 | holder.setOntItemSelectedListener(mItemSelectedListener); 121 | } 122 | 123 | @Override 124 | public int getItemCount() { 125 | return mItems.size(); 126 | } 127 | 128 | @Override 129 | public long getItemId(int position) { 130 | return mItems.get(position).hashCode(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/java/com/negusoft/compoundadapter/adapter/StaticDataAdapter.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import android.widget.Toast; 9 | 10 | import com.negusoft.compoundadapter.R; 11 | import com.negusoft.compoundadapter.data.Samples; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * RecyclerView adapter displaying a static list of items 18 | */ 19 | public class StaticDataAdapter extends RecyclerView.Adapter { 20 | 21 | public interface ItemSelectedListener { 22 | public void onItemSelected(String value); 23 | } 24 | 25 | static final class ViewHolder extends RecyclerView.ViewHolder { 26 | 27 | TextView textView; 28 | 29 | ViewHolder(View itemView) { 30 | super(itemView); 31 | textView = (TextView)itemView.findViewById(android.R.id.text1); 32 | itemView.setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | Toast.makeText(v.getContext(), textView.getText(), Toast.LENGTH_SHORT).show(); 36 | } 37 | }); 38 | } 39 | 40 | void setText(String text) { 41 | textView.setText(text); 42 | } 43 | 44 | String getText() { 45 | return textView.getText().toString(); 46 | } 47 | } 48 | 49 | private ItemSelectedListener mItemSelectedListener; 50 | 51 | public void setItemSelectedListener(ItemSelectedListener listener) { 52 | mItemSelectedListener = listener; 53 | } 54 | 55 | @Override 56 | public StaticDataAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 57 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 58 | View view = inflater.inflate(R.layout.item_content, parent, false); 59 | return new StaticDataAdapter.ViewHolder(view); 60 | } 61 | 62 | @Override 63 | public void onBindViewHolder(final StaticDataAdapter.ViewHolder holder, int position) { 64 | holder.setText(Samples.VALUES[position]); 65 | holder.textView.setOnClickListener(new View.OnClickListener() { 66 | @Override 67 | public void onClick(View v) { 68 | if (mItemSelectedListener != null) 69 | mItemSelectedListener.onItemSelected(holder.getText()); 70 | } 71 | }); 72 | } 73 | 74 | @Override 75 | public int getItemCount() { 76 | return Samples.VALUES.length; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/java/com/negusoft/compoundadapter/adapter/TreeNodeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter.adapter; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.graphics.drawable.Drawable; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.util.Log; 9 | import android.util.TypedValue; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import com.negusoft.compoundadapter.R; 17 | import com.negusoft.compountadapter.recyclerview.AdapterGroup; 18 | import com.negusoft.compountadapter.recyclerview.AdapterPosition; 19 | 20 | import java.util.Random; 21 | 22 | /** Adapter representing a node in a tree and allows adding/removing child nodes. */ 23 | public class TreeNodeAdapter extends AdapterGroup { 24 | 25 | // The depth of the node, it will determine the indentation 26 | private final int mDepth; 27 | 28 | // Text to be displayed for the node 29 | private final String mName; 30 | 31 | private NodeItemAdapter mNodeItemAdapter; 32 | private TreeNodeAdapter mParentNode; 33 | 34 | private ItemClickListener mListener; 35 | 36 | private boolean mSelected = false; 37 | 38 | public TreeNodeAdapter(String name, ItemClickListener listener) { 39 | this(0, name, listener); 40 | } 41 | 42 | TreeNodeAdapter(int depth, String name, ItemClickListener listener) { 43 | super(); 44 | mDepth = depth; 45 | mName = name; 46 | mListener = listener; 47 | mNodeItemAdapter = new NodeItemAdapter(); 48 | addAdapter(mNodeItemAdapter); 49 | } 50 | 51 | public TreeNodeAdapter addNode(String name) { 52 | TreeNodeAdapter node = new TreeNodeAdapter(mDepth + 1, name, mListener); 53 | addAdapter(node); 54 | 55 | node.mParentNode = this; 56 | 57 | return node; 58 | } 59 | 60 | public void addSibling(String name) { 61 | if (mParentNode == null) 62 | return; 63 | mParentNode.addNode(name); 64 | } 65 | 66 | public void setSelected(boolean selected) { 67 | mSelected = selected; 68 | mNodeItemAdapter.notifyItemChanged(0); 69 | } 70 | 71 | public void delete() { 72 | if (mParentNode == null) 73 | return; 74 | mParentNode.removeAdapter(this); 75 | } 76 | 77 | public TreeNodeAdapter getParentNode() { 78 | return mParentNode; 79 | } 80 | 81 | public interface ItemClickListener { 82 | void onNodeSelected(TreeNodeAdapter node, TreeNodeAdapter parentNode, int index); 83 | } 84 | 85 | private static class ViewHolder extends RecyclerView.ViewHolder { 86 | 87 | View selectableView; 88 | TextView textView; 89 | 90 | public ViewHolder(View itemView) { 91 | super(itemView); 92 | selectableView = itemView.findViewById(R.id.selectable); 93 | textView = (TextView) itemView.findViewById(android.R.id.text1); 94 | } 95 | 96 | public void setDepth(int depth) { 97 | Resources r = textView.getContext().getResources(); 98 | float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, r.getDisplayMetrics()); 99 | textView.setPaddingRelative((int)(px * (depth + 1)), 0, 0, 0); 100 | } 101 | 102 | public void setText(String text) { 103 | textView.setText(text); 104 | } 105 | 106 | public void setClickListener(View.OnClickListener listener) { 107 | textView.setOnClickListener(listener); 108 | } 109 | } 110 | 111 | private class NodeItemAdapter extends RecyclerView.Adapter { 112 | @Override 113 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 114 | LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 115 | View view = inflater.inflate(R.layout.item_content, parent, false); 116 | 117 | return new ViewHolder(view); 118 | } 119 | 120 | @Override 121 | public void onBindViewHolder(final ViewHolder holder, final int position) { 122 | Context c = holder.textView.getContext(); 123 | 124 | holder.setDepth(mDepth); 125 | holder.setText(mName); 126 | holder.setClickListener(new View.OnClickListener() { 127 | @Override 128 | public void onClick(View v) { 129 | mListener.onNodeSelected(TreeNodeAdapter.this, getParentNode(), position); 130 | } 131 | }); 132 | holder.selectableView.setSelected(mSelected); 133 | } 134 | 135 | @Override 136 | public int getItemCount() { 137 | return 1; 138 | } 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/java/com/negusoft/compoundadapter/data/Samples.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter.data; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Some sample dummy data. 7 | */ 8 | public class Samples { 9 | 10 | public static final String[] VALUES = new String[] { 11 | "ONE", 12 | "TWO", 13 | "THREE", 14 | "FOUR", 15 | "FIVE", 16 | "SIX", 17 | "SEVEN", 18 | "EIGHT", 19 | "NINE" 20 | }; 21 | 22 | public static String getRandomSample() { 23 | return VALUES[new Random().nextInt(VALUES.length)]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/java/com/negusoft/compoundadapter/fragment/AdapterGroupTreeFragment.java: -------------------------------------------------------------------------------- 1 | package com.negusoft.compoundadapter.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.Toast; 13 | 14 | import com.negusoft.compoundadapter.R; 15 | import com.negusoft.compoundadapter.adapter.TreeNodeAdapter; 16 | import com.negusoft.compountadapter.recyclerview.AdapterGroup; 17 | import com.negusoft.compountadapter.recyclerview.AdapterPosition; 18 | 19 | import java.util.Random; 20 | 21 | public class AdapterGroupTreeFragment extends Fragment { 22 | 23 | private static final int INITIAL_NODE_COUNT = 2; 24 | private static final int INITIAL_NODE_CHILDREN_COUNT = 3; 25 | 26 | public static AdapterGroupTreeFragment newInstance() { 27 | return new AdapterGroupTreeFragment(); 28 | } 29 | 30 | private RecyclerView mRecyclerView; 31 | private AdapterGroup mAdapterGroup; 32 | private TreeNodeAdapter mTreeNodeAdapter; 33 | 34 | private TreeNodeAdapter mSelectedNode; 35 | 36 | @Nullable 37 | @Override 38 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 39 | View result = inflater.inflate(R.layout.tree_fragment, container, false); 40 | 41 | Context c = getActivity().getApplicationContext(); 42 | mRecyclerView = ((RecyclerView)result.findViewById(R.id.recyclerview)); 43 | mRecyclerView.setLayoutManager(new LinearLayoutManager(c)); 44 | getActivity().setTitle(R.string.sample_adapter_group_tree); 45 | 46 | // Listeners 47 | result.findViewById(R.id.delete).setOnClickListener(new View.OnClickListener() { 48 | @Override 49 | public void onClick(View v) { 50 | if (mSelectedNode == null) 51 | return; 52 | mSelectedNode.delete(); 53 | mSelectedNode = null; 54 | } 55 | }); 56 | result.findViewById(R.id.newChild).setOnClickListener(new View.OnClickListener() { 57 | @Override 58 | public void onClick(View v) { 59 | if (mSelectedNode == null) 60 | return; 61 | mSelectedNode.addNode(getRandomNodeName()); 62 | } 63 | }); 64 | result.findViewById(R.id.newSibling).setOnClickListener(new View.OnClickListener() { 65 | @Override 66 | public void onClick(View v) { 67 | if (mSelectedNode == null) 68 | return; 69 | mSelectedNode.addSibling(getRandomNodeName()); 70 | } 71 | }); 72 | 73 | // Initialize the adapter 74 | mTreeNodeAdapter = new TreeNodeAdapter(getString(R.string.sample_list_title), mListener); 75 | 76 | for (int i=0; i 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/res/layout/item_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 27 | 28 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/res/layout/item_sample_list_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/res/layout/main_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 26 | 27 | 36 | 37 | 46 | 47 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/res/layout/main_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/res/layout/recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/res/layout/sample_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /compoundadapter-sample/src/main/res/layout/tree_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 19 | 20 |